var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import { Jws, Message, PermissionScopeMatcher } from '@enbox/dwn-sdk-js';
import { lexicographicalCompare, syncScopeFromRecordsProjectionScopes } from './types/sync.js';
/** Returns a sorted, duplicate-free grant ID set, or `undefined` for owner requests. */
export function toMessagesPermissionGrantIds(permissionGrantIds) {
    if (permissionGrantIds === undefined || permissionGrantIds.length === 0) {
        return undefined;
    }
    return [...new Set(permissionGrantIds)].sort(lexicographicalCompare);
}
/**
 * Gets the active permission grant IDs that authorize a Messages operation.
 *
 * Owner-authored sync does not invoke grants. Delegate full sync requires at
 * least one active unscoped Messages.Read grant. Delegate protocol-set sync
 * requires each requested protocol to be covered by an active Messages.Read
 * grant, then invokes every active grant that participates in the projection.
 * This keeps the authorization epoch tied to grant churn without widening the
 * CID projection being compared.
 */
export function getMessagesPermissionGrantsForScope(_a) {
    return __awaiter(this, arguments, void 0, function* ({ did, delegateDid, protocols, messageType, permissionsApi, }) {
        const requestedScope = protocols === undefined
            ? { kind: 'full' }
            : { kind: 'protocolSet', protocols };
        const resolutions = yield resolveMessagesSyncScopes({
            did,
            delegateDid,
            requestedScope,
            messageType,
            permissionsApi,
        });
        return resolutions
            .flatMap(resolution => resolution.permissionGrants)
            .sort((a, b) => lexicographicalCompare(a.grant.id, b.grant.id));
    });
}
/**
 * Resolves active Messages.Read grants into one or more sync targets.
 *
 * Broad protocol coverage remains on StateIndex full/protocol roots. Exact
 * protocolPath and contextId grants are grouped into a Records-primary
 * projection target so a narrow grant never authorizes a broad protocol root.
 */
export function resolveMessagesSyncScopes(_a) {
    return __awaiter(this, arguments, void 0, function* ({ did, delegateDid, requestedScope, messageType, permissionsApi, }) {
        if (!delegateDid) {
            return [{ scope: requestedScope, permissionGrants: [] }];
        }
        const now = new Date().toISOString();
        const permissionGrants = (yield permissionsApi.fetchGrants({
            author: delegateDid,
            target: delegateDid,
            grantor: did,
            grantee: delegateDid,
        })).filter(entry => isActiveMessagesGrant(entry, did, delegateDid, now));
        if (requestedScope.kind === 'full') {
            return [resolveFullScope(permissionGrants, requestedScope, messageType)];
        }
        if (requestedScope.kind === 'protocolSet') {
            return resolveProtocolSetScope(permissionGrants, requestedScope, messageType);
        }
        return [resolveRecordsProjectionScope(permissionGrants, requestedScope, messageType)];
    });
}
function resolveFullScope(permissionGrants, requestedScope, messageType) {
    const grants = permissionGrants
        .filter(entry => grantMatchesProtocol(entry, undefined))
        .sort((a, b) => lexicographicalCompare(a.grant.id, b.grant.id));
    if (grants.length === 0) {
        throw new Error(`SyncPermissions: No active Messages.Read permission found for ${messageType}: all protocols`);
    }
    return { scope: requestedScope, permissionGrants: grants };
}
function resolveProtocolSetScope(permissionGrants, requestedScope, messageType) {
    const broadProtocols = [];
    const narrowScopes = [];
    for (const protocol of requestedScope.protocols) {
        if (permissionGrants.some(entry => grantMatchesProtocol(entry, protocol))) {
            broadProtocols.push(protocol);
            continue;
        }
        const protocolNarrowScopes = permissionGrants
            .map(entry => grantProjectionScopeForProtocol(entry, protocol))
            .filter((scope) => scope !== undefined);
        if (protocolNarrowScopes.length === 0) {
            throw new Error(`SyncPermissions: No active Messages.Read permission found for ${messageType}: ${protocol}`);
        }
        narrowScopes.push(...protocolNarrowScopes);
    }
    return [
        ...broadProtocolResolution(permissionGrants, broadProtocols),
        ...recordsProjectionResolution(permissionGrants, narrowScopes, messageType),
    ];
}
function resolveRecordsProjectionScope(permissionGrants, requestedScope, messageType) {
    if (!requestedScope.scopes.every(scope => isProjectionScopeCovered(permissionGrants, scope))) {
        throw new Error(`SyncPermissions: No active Messages.Read permission found for ${messageType}: projected Records scope`);
    }
    const grants = permissionGrants
        .filter(entry => requestedScope.scopes.some(scope => PermissionScopeMatcher.matches(entry.grant.scope, scope)))
        .sort((a, b) => lexicographicalCompare(a.grant.id, b.grant.id));
    return { scope: requestedScope, permissionGrants: grants };
}
function isProjectionScopeCovered(permissionGrants, scope) {
    return permissionGrants.some(entry => PermissionScopeMatcher.matches(entry.grant.scope, scope));
}
function broadProtocolResolution(permissionGrants, broadProtocols) {
    if (broadProtocols.length === 0) {
        return [];
    }
    const protocols = [...new Set(broadProtocols)].sort(lexicographicalCompare);
    const grants = permissionGrants
        .filter(entry => grantParticipatesInProtocolSet(entry, protocols))
        .sort((a, b) => lexicographicalCompare(a.grant.id, b.grant.id));
    return [{
            scope: {
                kind: 'protocolSet',
                protocols,
            },
            permissionGrants: grants,
        }];
}
function recordsProjectionResolution(permissionGrants, projectionScopes, messageType) {
    const [firstScope, ...remainingScopes] = projectionScopes;
    if (firstScope === undefined) {
        return [];
    }
    const requestedScope = syncScopeFromRecordsProjectionScopes([firstScope, ...remainingScopes]);
    return [resolveRecordsProjectionScope(permissionGrants, requestedScope, messageType)];
}
function grantProjectionScopeForProtocol(entry, protocol) {
    const scope = entry.grant.scope;
    if (!isMessagesReadScope(scope) || scope.protocol !== protocol) {
        return undefined;
    }
    if (scope.protocolPath !== undefined) {
        return { protocol, protocolPath: scope.protocolPath };
    }
    if (scope.contextId !== undefined) {
        return { protocol, contextId: scope.contextId };
    }
}
function isMessagesReadScope(scope) {
    return scope.interface === 'Messages' &&
        scope.method === 'Read';
}
function grantParticipatesInProtocolSet(entry, protocols) {
    return protocols.some(protocol => grantMatchesProtocol(entry, protocol));
}
/** Converts permission grant entries into authorization epoch inputs. */
export function toSyncAuthorizationGrants(permissionGrants) {
    if (permissionGrants.length === 0) {
        throw new Error('SyncPermissions: delegate authorization requires at least one grant.');
    }
    return permissionGrants
        .map(({ grant }) => ({
        dateExpires: grant.dateExpires,
        dateGranted: grant.dateGranted,
        id: grant.id,
    }))
        .sort((a, b) => lexicographicalCompare(a.id, b.id));
}
function isActiveMessagesGrant(entry, grantor, grantee, now) {
    const { grant } = entry;
    if (grant.grantor !== grantor || grant.grantee !== grantee) {
        return false;
    }
    if (grant.dateGranted > now || grant.dateExpires <= now) {
        return false;
    }
    const scope = grant.scope;
    return scope.interface === 'Messages' &&
        scope.method === 'Read';
}
function grantMatchesProtocol(entry, protocol) {
    return PermissionScopeMatcher.matches(entry.grant.scope, { protocol });
}
/** Returns sorted grant IDs from permission grant entries. */
export function permissionGrantIdsFromEntries(permissionGrants) {
    return toMessagesPermissionGrantIds(permissionGrants.map(entry => entry.grant.id));
}
/**
 * Returns the permission grant IDs invoked by a message.
 *
 * Real DWN messages use the author signature payload as the source of truth.
 */
export function getInvokedPermissionGrantIds(message) {
    if (message.authorization === undefined) {
        return [];
    }
    try {
        const signaturePayload = Jws.decodePlainObjectPayload(message.authorization.signature);
        return Message.getPermissionGrantIds(signaturePayload);
    }
    catch (_a) {
        return [];
    }
}
//# sourceMappingURL=sync-permission-grants.js.map