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());
    });
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
import { isRecordsType } from './dwn-api.js';
import { Convert, TtlCache } from '@enbox/common';
import { DwnInterface, DwnPermissionGrant, DwnPermissionRequest } from './types/dwn.js';
import { PermissionScopeMatcher, PermissionsProtocol } from '@enbox/dwn-sdk-js';
export class AgentPermissionsApi {
    get agent() {
        if (!this._agent) {
            throw new Error('AgentPermissionsApi: Agent is not set');
        }
        return this._agent;
    }
    set agent(agent) {
        this._agent = agent;
    }
    constructor({ agent } = {}) {
        /** cache for fetching a permission {@link PermissionGrant}, keyed by a specific MessageType and protocol */
        this._cachedPermissions = new TtlCache({ ttl: 60 * 1000 });
        this._agent = agent;
    }
    getPermissionForRequest(_a) {
        return __awaiter(this, arguments, void 0, function* ({ connectedDid, delegateDid, delegate, messageType, protocol, protocolPath, contextId, cached = false }) {
            const cacheKey = JSON.stringify([connectedDid, delegateDid, messageType, protocol, protocolPath, contextId]);
            const cachedGrant = cached ? this._cachedPermissions.get(cacheKey) : undefined;
            if (cachedGrant) {
                return cachedGrant;
            }
            const permissionGrants = yield this.fetchGrants({
                author: delegateDid,
                target: delegateDid,
                grantor: connectedDid,
                grantee: delegateDid,
            });
            // get the delegate grants that match the messageParams and are associated with the connectedDid as the grantor
            const grant = yield AgentPermissionsApi.matchGrantFromArray(connectedDid, delegateDid, { messageType, protocol, protocolPath, contextId }, permissionGrants, delegate);
            if (!grant) {
                throw new Error(`CachedPermissions: No permissions found for ${messageType}: ${[protocol, protocolPath, contextId].filter(Boolean).join('/') || undefined}`);
            }
            this._cachedPermissions.set(cacheKey, grant);
            return grant;
        });
    }
    fetchGrants(_a) {
        return __awaiter(this, arguments, void 0, function* ({ author, target, grantee, grantor, protocol, remote = false, checkRevoked = true, }) {
            // filter by a protocol using tags if provided
            const tags = protocol ? { protocol } : undefined;
            const params = {
                author: author,
                target: target,
                messageType: DwnInterface.RecordsQuery,
                messageParams: {
                    filter: {
                        author: grantor, // the author of the grant would be the grantor
                        recipient: grantee, // the recipient of the grant would be the grantee
                        protocol: PermissionsProtocol.uri,
                        protocolPath: PermissionsProtocol.grantPath,
                        tags
                    }
                }
            };
            const { reply } = remote ? yield this.agent.sendDwnRequest(params) : yield this.agent.processDwnRequest(params);
            if (reply.status.code !== 200) {
                throw new Error(`PermissionsApi: Failed to fetch grants: ${reply.status.detail}`);
            }
            // Build a set of revoked grant IDs so that revoked grants can be filtered out.
            // Uses a single batch query for all revocations rather than N individual reads.
            const revokedGrantIds = checkRevoked
                ? yield this.fetchRevokedGrantIds({ author, target, grantor, remote, tags })
                : new Set();
            const grants = [];
            for (const entry of reply.entries) {
                if (revokedGrantIds.has(entry.recordId)) {
                    continue;
                }
                const grant = DwnPermissionGrant.parse(entry);
                grants.push({ grant, message: entry });
            }
            return grants;
        });
    }
    /**
     * Fetch all revocation record IDs for grants, returned as a Set of parent grant record IDs.
     * Issues a single RecordsQuery for all revocation records, optionally scoped to a grantor and protocol.
     */
    fetchRevokedGrantIds(_a) {
        return __awaiter(this, arguments, void 0, function* ({ author, target, grantor, remote, tags }) {
            const revocationParams = {
                author,
                target,
                messageType: DwnInterface.RecordsQuery,
                messageParams: {
                    filter: {
                        author: grantor, // revocations are authored by the same grantor
                        protocol: PermissionsProtocol.uri,
                        protocolPath: PermissionsProtocol.revocationPath,
                        tags,
                    }
                }
            };
            const { reply: revocationReply } = remote
                ? yield this.agent.sendDwnRequest(revocationParams)
                : yield this.agent.processDwnRequest(revocationParams);
            if (revocationReply.status.code !== 200) {
                throw new Error(`PermissionsApi: Failed to fetch revocations: ${revocationReply.status.detail}`);
            }
            const revokedGrantIds = new Set();
            for (const entry of revocationReply.entries) {
                if (entry.descriptor.parentId !== undefined) {
                    revokedGrantIds.add(entry.descriptor.parentId);
                }
            }
            return revokedGrantIds;
        });
    }
    fetchRequests(_a) {
        return __awaiter(this, arguments, void 0, function* ({ author, target, protocol, remote = false }) {
            // filter by a protocol using tags if provided
            const tags = protocol ? { protocol } : undefined;
            const params = {
                author: author,
                target: target,
                messageType: DwnInterface.RecordsQuery,
                messageParams: {
                    filter: {
                        protocol: PermissionsProtocol.uri,
                        protocolPath: PermissionsProtocol.requestPath,
                        tags
                    }
                }
            };
            const { reply } = remote ? yield this.agent.sendDwnRequest(params) : yield this.agent.processDwnRequest(params);
            if (reply.status.code !== 200) {
                throw new Error(`PermissionsApi: Failed to fetch requests: ${reply.status.detail}`);
            }
            const requests = [];
            for (const entry of reply.entries) {
                const request = DwnPermissionRequest.parse(entry);
                requests.push({ request, message: entry });
            }
            return requests;
        });
    }
    isGrantRevoked(_a) {
        return __awaiter(this, arguments, void 0, function* ({ author, target, grantRecordId, remote = false }) {
            const params = {
                author,
                target,
                messageType: DwnInterface.RecordsRead,
                messageParams: {
                    filter: {
                        parentId: grantRecordId,
                        protocol: PermissionsProtocol.uri,
                        protocolPath: PermissionsProtocol.revocationPath,
                    }
                }
            };
            const { reply: revocationReply } = remote ? yield this.agent.sendDwnRequest(params) : yield this.agent.processDwnRequest(params);
            if (revocationReply.status.code === 404) {
                // no revocation found, the grant is not revoked
                return false;
            }
            else if (revocationReply.status.code === 200) {
                // a revocation was found, the grant is revoked
                return true;
            }
            throw new Error(`PermissionsApi: Failed to check if grant is revoked: ${revocationReply.status.detail}`);
        });
    }
    createGrant(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const { author, store = false, delegated = false } = params, createGrantParams = __rest(params, ["author", "store", "delegated"]);
            let tags = undefined;
            if (PermissionsProtocol.hasProtocolScope(createGrantParams.scope)) {
                tags = { protocol: createGrantParams.scope.protocol };
            }
            const permissionGrantData = {
                dateExpires: createGrantParams.dateExpires,
                requestId: createGrantParams.requestId,
                description: createGrantParams.description,
                delegated,
                scope: createGrantParams.scope,
            };
            // Attach delegate key-delivery metadata to the grant data payload.
            // This is stored in the grant's encoded data (not tags) to avoid
            // SQL column size limits on tag values.
            if (createGrantParams.delegateKeyDelivery) {
                permissionGrantData.delegateKeyDelivery = createGrantParams.delegateKeyDelivery;
            }
            const permissionsGrantBytes = Convert.object(permissionGrantData).toUint8Array();
            const messageParams = {
                recipient: createGrantParams.grantedTo,
                protocol: PermissionsProtocol.uri,
                protocolPath: PermissionsProtocol.grantPath,
                dataFormat: 'application/json',
                tags
            };
            const { reply, message } = yield this.agent.processDwnRequest({
                store,
                author,
                target: author,
                messageType: DwnInterface.RecordsWrite,
                messageParams,
                dataStream: new Blob([permissionsGrantBytes])
            });
            if (reply.status.code !== 202) {
                throw new Error(`PermissionsApi: Failed to create grant: ${reply.status.detail}`);
            }
            const dataEncodedMessage = Object.assign(Object.assign({}, message), { encodedData: Convert.uint8Array(permissionsGrantBytes).toBase64Url() });
            const grant = DwnPermissionGrant.parse(dataEncodedMessage);
            return { grant, message: dataEncodedMessage };
        });
    }
    createRequest(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const { author, store = false, delegated = false } = params, createGrantParams = __rest(params, ["author", "store", "delegated"]);
            let tags = undefined;
            if (PermissionsProtocol.hasProtocolScope(createGrantParams.scope)) {
                tags = { protocol: createGrantParams.scope.protocol };
            }
            const permissionRequestData = {
                description: createGrantParams.description,
                delegated,
                scope: createGrantParams.scope
            };
            const permissionRequestBytes = Convert.object(permissionRequestData).toUint8Array();
            const messageParams = {
                protocol: PermissionsProtocol.uri,
                protocolPath: PermissionsProtocol.requestPath,
                dataFormat: 'application/json',
                tags
            };
            const { reply, message } = yield this.agent.processDwnRequest({
                store,
                author,
                target: author,
                messageType: DwnInterface.RecordsWrite,
                messageParams,
                dataStream: new Blob([permissionRequestBytes])
            });
            if (reply.status.code !== 202) {
                throw new Error(`PermissionsApi: Failed to create request: ${reply.status.detail}`);
            }
            const dataEncodedMessage = Object.assign(Object.assign({}, message), { encodedData: Convert.uint8Array(permissionRequestBytes).toBase64Url() });
            const request = DwnPermissionRequest.parse(dataEncodedMessage);
            return { request, message: dataEncodedMessage };
        });
    }
    createRevocation(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const { author, store = false, grant, description } = params;
            const revokeData = { description };
            const permissionRevocationBytes = Convert.object(revokeData).toUint8Array();
            let tags = undefined;
            if (PermissionsProtocol.hasProtocolScope(grant.scope)) {
                tags = { protocol: grant.scope.protocol };
            }
            const messageParams = {
                parentContextId: grant.id,
                protocol: PermissionsProtocol.uri,
                protocolPath: PermissionsProtocol.revocationPath,
                dataFormat: 'application/json',
                tags
            };
            if (params.permissionGrantId) {
                messageParams.permissionGrantId = params.permissionGrantId;
            }
            const { reply, message } = yield this.agent.processDwnRequest({
                store,
                author,
                target: author,
                messageType: DwnInterface.RecordsWrite,
                messageParams,
                granteeDid: params.granteeDid,
                dataStream: new Blob([permissionRevocationBytes])
            });
            if (reply.status.code !== 202) {
                throw new Error(`PermissionsApi: Failed to create revocation: ${reply.status.detail}`);
            }
            const dataEncodedMessage = Object.assign(Object.assign({}, message), { encodedData: Convert.uint8Array(permissionRevocationBytes).toBase64Url() });
            return { message: dataEncodedMessage };
        });
    }
    clear() {
        return __awaiter(this, void 0, void 0, function* () {
            this._cachedPermissions.clear();
        });
    }
    /**
     * Matches the appropriate grant from an array of grants based on the provided parameters.
     *
     * @param delegated if true, only delegated grants are turned, if false all grants are returned including delegated ones.
     */
    static matchGrantFromArray(grantor_1, grantee_1, messageParams_1, grants_1) {
        return __awaiter(this, arguments, void 0, function* (grantor, grantee, messageParams, grants, delegated = false) {
            // Two-pass matching: prefer exact scope matches over unified Messages.Read fallback.
            // This ensures that if both a Messages.Sync grant and a Messages.Read grant exist,
            // the specific Messages.Sync grant is returned for MessagesSync lookups.
            let unifiedFallback;
            for (const entry of grants) {
                const { grant, message } = entry;
                if (delegated === true && grant.delegated !== true) {
                    continue;
                }
                const { messageType, protocol, protocolPath, contextId } = messageParams;
                if (this.matchScopeFromGrant(grantor, grantee, messageType, grant, protocol, protocolPath, contextId)) {
                    const scopeMessageType = grant.scope.interface + grant.scope.method;
                    // Exact match — return immediately
                    if (scopeMessageType === messageType) {
                        return { grant, message };
                    }
                    // Unified fallback match — hold for later in case an exact match is found
                    if (!unifiedFallback) {
                        unifiedFallback = { grant, message };
                    }
                }
            }
            return unifiedFallback;
        });
    }
    static matchScopeFromGrant(grantor, grantee, messageType, grant, protocol, protocolPath, contextId) {
        // Check if the grant matches the provided parameters
        if (grant.grantee !== grantee || grant.grantor !== grantor) {
            return false;
        }
        const scope = grant.scope;
        const scopeMessageType = scope.interface + scope.method;
        // Messages.Read is the only valid Messages scope and covers Read, Sync, and Subscribe operations.
        // Defensively require method === Read so malformed/legacy grants with method Sync/Subscribe
        // are rejected rather than treated as valid scopes.
        const isMessagesScopeMatch = scope.interface === 'Messages'
            ? scope.method === 'Read'
                && (messageType === DwnInterface.MessagesRead
                    || messageType === DwnInterface.MessagesSync
                    || messageType === DwnInterface.MessagesSubscribe)
            : scopeMessageType === messageType;
        if (isMessagesScopeMatch) {
            if (isRecordsType(messageType)) {
                const recordScope = scope;
                return PermissionScopeMatcher.matches(recordScope, { protocol, protocolPath, contextId });
            }
            else {
                const messagesScope = scope;
                return PermissionScopeMatcher.matches(messagesScope, { protocol, protocolPath, contextId });
            }
        }
        return false;
    }
}
//# sourceMappingURL=permissions-api.js.map