import { KeyDerivationScheme } from './hd-key.js';
import { ProtocolAction } from '../types/protocols-types.js';
import { DwnInterfaceName, DwnMethodName } from '../enums/dwn-interface-method.js';
import { getRuleSetAtPath, parseCrossProtocolRef } from './protocols.js';
const grantKeyRecordsMethods = new Set([
    DwnMethodName.Read,
    DwnMethodName.Write,
]);
const grantKeyDeliveryScopeBuilders = {
    [DwnMethodName.Read]: getReadGrantKeyDeliveryScopes,
    [DwnMethodName.Write]: getWriteGrantKeyDeliveryScopes,
};
const grantKeyCoveragePredicates = {
    [DwnMethodName.Read]: readGrantKeyScopeCoversDeliveredScope,
    [DwnMethodName.Write]: writeGrantKeyScopeCoversDeliveredScope,
};
export function isGrantKeyRecordsScope(scope) {
    const maybeProtocol = scope.protocol;
    return [
        scope.interface === DwnInterfaceName.Records,
        grantKeyRecordsMethods.has(scope.method),
        typeof maybeProtocol === 'string',
    ].every(Boolean);
}
export function isGrantKeyEligibleRecordsScope(scope) {
    return isGrantKeyRecordsScope(scope) &&
        !('contextId' in scope && scope.contextId !== undefined);
}
export function getGrantKeyDeliveryScopes(grantScope, protocolDefinition) {
    return uniqueGrantKeyScopes(grantKeyDeliveryScopeBuilders[grantScope.method](grantScope, protocolDefinition));
}
export function grantKeyScopeCoversDeliveredScope(input) {
    const { grantScope, deliveredScope, protocolDefinition } = input;
    if (grantScope.protocol !== deliveredScope.protocol) {
        return false;
    }
    return grantKeyCoveragePredicates[grantScope.method]({ grantScope, deliveredScope, protocolDefinition });
}
function getReadGrantKeyDeliveryScopes(grantScope, protocolDefinition) {
    if (grantScope.protocolPath === undefined) {
        return [createProtocolGrantKeyScope(grantScope.protocol)];
    }
    const scopes = [
        createProtocolPathGrantKeyScope(grantScope.protocol, grantScope.protocolPath),
    ];
    if (protocolDefinition === undefined) {
        return scopes;
    }
    for (const rolePath of getGrantKeyReadRolePathsForScope(protocolDefinition, grantScope.protocolPath)) {
        scopes.push(createProtocolPathGrantKeyScope(grantScope.protocol, rolePath));
    }
    return scopes;
}
function getWriteGrantKeyDeliveryScopes(grantScope, protocolDefinition) {
    if (protocolDefinition === undefined) {
        throw new Error('getGrantKeyDeliveryScopes: write grants require a protocol definition.');
    }
    return getGrantKeyRolePathsCoveredByScope(protocolDefinition, grantScope.protocolPath)
        .map((rolePath) => createProtocolPathGrantKeyScope(grantScope.protocol, rolePath));
}
function createProtocolGrantKeyScope(protocol) {
    return {
        scheme: KeyDerivationScheme.ProtocolPath,
        protocol,
    };
}
function createProtocolPathGrantKeyScope(protocol, protocolPath) {
    return {
        scheme: KeyDerivationScheme.ProtocolPath,
        protocol,
        protocolPath,
    };
}
function readGrantKeyScopeCoversDeliveredScope(input) {
    const { grantScope, deliveredScope, protocolDefinition } = input;
    const grantProtocolPath = grantScope.protocolPath;
    if (grantProtocolPath === undefined) {
        return true;
    }
    const deliveredProtocolPath = deliveredScope.protocolPath;
    if (deliveredProtocolPath === undefined) {
        return false;
    }
    if (isBoundaryAwareSubtree(grantProtocolPath, deliveredProtocolPath)) {
        return true;
    }
    if (protocolDefinition === undefined) {
        return false;
    }
    return grantKeyReadScopeReferencesRolePath(protocolDefinition, grantProtocolPath, deliveredProtocolPath);
}
function writeGrantKeyScopeCoversDeliveredScope(input) {
    const { grantScope, deliveredScope, protocolDefinition } = input;
    const deliveredProtocolPath = deliveredScope.protocolPath;
    if (deliveredProtocolPath === undefined) {
        return false;
    }
    if (protocolDefinition === undefined) {
        return false;
    }
    if (!isGrantKeyLocalRolePath(protocolDefinition, deliveredProtocolPath)) {
        return false;
    }
    const grantProtocolPath = grantScope.protocolPath;
    if (grantProtocolPath === undefined) {
        return true;
    }
    return isBoundaryAwareSubtree(grantProtocolPath, deliveredProtocolPath);
}
function getGrantKeyReadRolePathsForScope(protocolDefinition, scopeProtocolPath) {
    const scopedRuleSet = getRuleSetAtPath(scopeProtocolPath, protocolDefinition.structure);
    if (scopedRuleSet === undefined) {
        return [];
    }
    return [...collectReadRolePaths(protocolDefinition, scopedRuleSet)];
}
function grantKeyReadScopeReferencesRolePath(protocolDefinition, scopeProtocolPath, rolePath) {
    if (!isGrantKeyLocalRolePath(protocolDefinition, rolePath)) {
        return false;
    }
    return getGrantKeyReadRolePathsForScope(protocolDefinition, scopeProtocolPath).includes(rolePath);
}
function collectReadRolePaths(protocolDefinition, ruleSet) {
    const rolePaths = new Set();
    for (const actionRule of ruleSet.$actions ?? []) {
        if (!actionRule.can.includes(ProtocolAction.Read) || actionRule.role === undefined || parseCrossProtocolRef(actionRule.role) !== undefined) {
            continue;
        }
        if (isGrantKeyLocalRolePath(protocolDefinition, actionRule.role)) {
            rolePaths.add(actionRule.role);
        }
    }
    for (const [key, value] of Object.entries(ruleSet)) {
        if (key.startsWith('$')) {
            continue;
        }
        for (const rolePath of collectReadRolePaths(protocolDefinition, value)) {
            rolePaths.add(rolePath);
        }
    }
    return rolePaths;
}
function getGrantKeyRolePathsCoveredByScope(protocolDefinition, scopeProtocolPath) {
    return collectRolePaths(protocolDefinition, protocolDefinition.structure)
        .filter((rolePath) => scopeProtocolPath === undefined || isBoundaryAwareSubtree(scopeProtocolPath, rolePath));
}
function collectRolePaths(protocolDefinition, ruleSet, parentPath = []) {
    const rolePaths = [];
    for (const [key, value] of Object.entries(ruleSet)) {
        if (key.startsWith('$')) {
            continue;
        }
        const path = [...parentPath, key];
        const protocolPath = path.join('/');
        const childRuleSet = value;
        if (isGrantKeyLocalRolePath(protocolDefinition, protocolPath)) {
            rolePaths.push(protocolPath);
        }
        rolePaths.push(...collectRolePaths(protocolDefinition, childRuleSet, path));
    }
    return rolePaths;
}
function isGrantKeyLocalRolePath(protocolDefinition, protocolPath) {
    const ruleSet = getRuleSetAtPath(protocolPath, protocolDefinition.structure);
    return ruleSet?.$role === true && ruleSet.$keyAgreement !== undefined;
}
function isBoundaryAwareSubtree(scopePath, candidatePath) {
    return `${candidatePath}/`.startsWith(`${scopePath}/`);
}
function getGrantKeyScopeCacheKey(scope) {
    return JSON.stringify([scope.protocol, scope.protocolPath]);
}
function uniqueGrantKeyScopes(scopes) {
    const deliveredScopes = new Map();
    for (const scope of scopes) {
        deliveredScopes.set(getGrantKeyScopeCacheKey(scope), scope);
    }
    return [...deliveredScopes.values()];
}
//# sourceMappingURL=grant-key-coverage.js.map