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 { CryptoUtils } from '@enbox/crypto';
import { Cid, ContentEncryptionAlgorithm, DataStream, DurableEventLog, Dwn, DwnMethodName, Encoder, Encryption, ENCRYPTION_CONTROL_AUDIENCE_PATH, EncryptionControl, EncryptionControlDeliveryRecipientAuthority, EventEmitterWakePublisher, getRoleAudienceContextId, getRuleSetAtPath, isCrossProtocolRef, KeyAgreementAlgorithm, KeyDerivationScheme, Message, Protocols, RecordsWrite, ROLE_AUDIENCE_DERIVATION_SCHEME, Time, } from '@enbox/dwn-sdk-js';
import { Convert, logger, TtlCache } from '@enbox/common';
import { DidDht, DidJwk, UniversalResolver } from '@enbox/dids';
import { AgentPermissionsApi } from './permissions-api.js';
import { DwnDiscoveryFile } from './dwn-discovery-file.js';
import { LocalDwnDiscovery } from './local-dwn.js';
import { DwnInterface, dwnMessageConstructors } from './types/dwn.js';
import { getDwnServiceEndpointUrls, isRecordsWrite } from './utils.js';
// Re-export DWN type guards from the agent API surface.
export { isDwnMessage, isDwnRequest, isMessagesPermissionScope, isRecordPermissionScope, isRecordsType } from './dwn-type-guards.js';
// Import type guards for internal use
import { isDwnRequest } from './dwn-type-guards.js';
import { processDwnRequestWithRemoteFallback as processDwnReadThrough } from './dwn-read-through.js';
// Import extracted encryption functions
import { buildEncryptionInput as buildEncryptionInputFn, createAudienceDeliveryRecord as createAudienceDeliveryRecordFn, createAudienceRecord as createAudienceRecordFn, encryptAndComputeCid as encryptAndComputeCidFn, generateAudienceKey as generateAudienceKeyFn, getAudienceDecryptionKeyCacheKey, getEncryptionKeyDeriver as getEncryptionKeyDeriverFn, getEncryptionKeyInfo as getEncryptionKeyInfoFn, getKeyDecrypter as getKeyDecrypterFn, hasAudienceSealCoverage as hasAudienceSealCoverageFn, ivLength as ivLengthFn, maybeDecryptReply as maybeDecryptReplyFn, resolveAudienceDecryptionKey as resolveAudienceDecryptionKeyFn, } from './dwn-encryption.js';
// Import extracted protocol utilities
import { detectNewParticipants as detectNewParticipantsFn, } from './protocol-utils.js';
// Import extracted protocol definition fetching functions
import { fetchRemoteProtocolDefinition as fetchRemoteProtocolDefinitionFn, getProtocolDefinition as getProtocolDefinitionFn, } from './dwn-protocol-cache.js';
export class AgentDwnApi {
    constructor(params) {
        /**
         * Protocol definition cache — TTL 30 minutes. Protocols rarely change.
         * Keyed by `${tenantDid}~${protocolUri}`.
         */
        this._protocolDefinitionCache = new TtlCache({
            ttl: 30 * 60 * 1000
        });
        /**
         * Delegate decryption key cache — stores scope-aware decryption keys
         * delivered to delegates during the connect flow. These keys enable
         * delegates to decrypt encrypted records without possessing the owner's
         * root X25519 private key.
         *
         * Keyed by `ddk~${delegateDid}`. Each entry is an array covering all
         * granted read scopes for that delegate session.
         * TTL 24 hours (keys are re-populated on session restore).
         */
        this._delegateDecryptionKeyCache = new TtlCache({
            ttl: 24 * 60 * 60 * 1000
        });
        /**
         * Role-audience private keys hydrated from durable audience seals or delivery records.
         * This is a memory-only session cache; the records remain the durable source of truth.
         */
        this._audienceDecryptionKeyCache = new TtlCache({
            ttl: 24 * 60 * 60 * 1000
        });
        /**
         * Cache of locally-managed DIDs (agent DID + identities). Used to decide
         * whether a target DID should be routed through the local DWN server.
         */
        this._localManagedDidCache = new TtlCache({
            ttl: 30 * 60 * 1000
        });
        const { agent, localDwnStrategy = 'prefer' } = params;
        // If an agent is provided, set it as the execution context for this API.
        this._agent = agent;
        // Set the DWN instance (undefined in remote mode).
        this._dwn = 'dwn' in params ? params.dwn : undefined;
        // Set the remote endpoint (undefined in local mode).
        this._localDwnEndpoint = 'localDwnEndpoint' in params ? params.localDwnEndpoint : undefined;
        // Set the local DWN discovery strategy.
        this._localDwnStrategy = localDwnStrategy;
        // If agent is already available, eagerly initialize the discovery instance.
        if (agent) {
            this._localDwnDiscovery = new LocalDwnDiscovery(agent.rpc, 10000, AgentDwnApi._tryCreateDiscoveryFile());
        }
    }
    /**
     * Whether the API is operating in remote mode (no in-process DWN).
     * In remote mode, all DWN operations are routed through RPC.
     */
    get isRemoteMode() {
        return this._dwn === undefined;
    }
    /**
     * Retrieves the `EnboxPlatformAgent` execution context.
     *
     * @returns The `EnboxPlatformAgent` instance that represents the current execution context.
     * @throws Will throw an error if the `agent` instance property is undefined.
     */
    get agent() {
        if (this._agent === undefined) {
            throw new Error('AgentDwnApi: Unable to determine agent execution context.');
        }
        return this._agent;
    }
    set agent(agent) {
        this._agent = agent;
        // Re-initialize local DWN discovery with the new agent's RPC client.
        this._localDwnDiscovery = new LocalDwnDiscovery(agent.rpc, 10000, AgentDwnApi._tryCreateDiscoveryFile());
        this._localManagedDidCache.clear();
    }
    get localDwnStrategy() {
        return this._localDwnStrategy;
    }
    setLocalDwnStrategy(strategy) {
        this._localDwnStrategy = strategy;
    }
    /**
     * Inject a cached local DWN endpoint (e.g. from a `dwn://connect`
     * browser redirect or from persisted storage). The endpoint is validated
     * via `GET /info` before being accepted.
     *
     * @param endpoint - The local DWN server base URL.
     * @returns `true` if the endpoint was validated and cached, `false` otherwise.
     * @see https://github.com/enboxorg/enbox/issues/589
     */
    setCachedLocalDwnEndpoint(endpoint) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            (_a = this._localDwnDiscovery) !== null && _a !== void 0 ? _a : (this._localDwnDiscovery = new LocalDwnDiscovery(this.agent.rpc, 10000, AgentDwnApi._tryCreateDiscoveryFile()));
            return this._localDwnDiscovery.setCachedEndpoint(endpoint);
        });
    }
    /**
     * Resolves the DWN service endpoint URLs for the given target DID, optionally
     * prepending a local DWN server endpoint when local discovery is enabled and
     * the target is a locally-managed DID.
     *
     * @param targetDid - The DID whose DWN endpoints should be resolved.
     * @returns An array of endpoint URLs.
     * @throws When strategy is `'only'` and no local server is available.
     */
    getDwnEndpointUrlsForTarget(targetDid) {
        return __awaiter(this, void 0, void 0, function* () {
            const shouldUseLocalDwn = yield this.shouldUseLocalDwnForTarget(targetDid);
            if (!shouldUseLocalDwn) {
                return getDwnServiceEndpointUrls(targetDid, this.agent.did);
            }
            const localDwnEndpoint = yield this.getLocalDwnEndpoint();
            if (this._localDwnStrategy === 'only') {
                if (!localDwnEndpoint) {
                    throw new Error(`AgentDwnApi: Local DWN strategy is 'only' but no local DWN endpoint was discovered. ` +
                        `Ensure the local DWN server is running and discoverable via the discovery file (~/.enbox/dwn.json) or dwn://connect.`);
                }
                return [localDwnEndpoint];
            }
            let dwnEndpointUrls = [];
            try {
                dwnEndpointUrls = yield getDwnServiceEndpointUrls(targetDid, this.agent.did);
            }
            catch (error) {
                if (!localDwnEndpoint) {
                    throw error;
                }
            }
            if (!localDwnEndpoint) {
                return dwnEndpointUrls;
            }
            const uniqueEndpoints = new Set([
                localDwnEndpoint,
                ...dwnEndpointUrls,
            ]);
            return [...uniqueEndpoints];
        });
    }
    /**
     * Returns only the DWN service endpoints from the DID document (no local
     * discovery endpoint). Use this when you need to confirm that a message
     * reached the owner's actual remote DWN, not just the delegate's local server.
     */
    getRemoteDwnEndpointUrls(targetDid) {
        return __awaiter(this, void 0, void 0, function* () {
            return getDwnServiceEndpointUrls(targetDid, this.agent.did);
        });
    }
    /** Lazily retrieves the local DWN server endpoint via discovery. */
    getLocalDwnEndpoint() {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            // In remote mode, the endpoint is always known.
            if (this._localDwnEndpoint) {
                return this._localDwnEndpoint;
            }
            (_a = this._localDwnDiscovery) !== null && _a !== void 0 ? _a : (this._localDwnDiscovery = new LocalDwnDiscovery(this.agent.rpc, 10000, AgentDwnApi._tryCreateDiscoveryFile()));
            return this._localDwnDiscovery.getEndpoint();
        });
    }
    /**
     * Attempt to create a {@link DwnDiscoveryFile} for file-based local DWN
     * discovery. Returns `undefined` in environments where the filesystem
     * is not available (e.g. browsers).
     */
    static _tryCreateDiscoveryFile() {
        try {
            return new DwnDiscoveryFile();
        }
        catch (_a) {
            // Browser environment — node:fs/promises not available.
            return undefined;
        }
    }
    /**
     * Determines whether the given target DID should be routed through the
     * local DWN server. Returns `true` if the DID is the agent DID or one
     * of the locally-managed identity DIDs.
     */
    shouldUseLocalDwnForTarget(targetDid) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this._localDwnStrategy === 'off') {
                return false;
            }
            const cached = this._localManagedDidCache.get(targetDid);
            if (cached !== undefined) {
                return cached;
            }
            if (targetDid === this.agent.agentDid.uri) {
                this._localManagedDidCache.set(targetDid, true);
                return true;
            }
            const identities = yield this.agent.identity.list();
            const localManagedDids = new Set();
            for (const identity of identities) {
                localManagedDids.add(identity.did.uri);
                if (identity.metadata.connectedDid) {
                    localManagedDids.add(identity.metadata.connectedDid);
                }
            }
            for (const localDid of localManagedDids) {
                this._localManagedDidCache.set(localDid, true);
            }
            const isLocalManaged = localManagedDids.has(targetDid);
            if (!isLocalManaged) {
                this._localManagedDidCache.set(targetDid, false);
            }
            return isLocalManaged;
        });
    }
    /**
     * Public getter for the DWN instance used by this API.
     *
     * Notes:
     * - This getter is public to allow advanced developers to access the DWN instance directly.
     *   However, it is recommended to use the `processRequest` method to interact with the DWN
     *   instance to ensure that the DWN message is constructed correctly.
     * - The getter is named `node` to avoid confusion with the `dwn` property of the
     *   `EnboxPlatformAgent`. In other words, so that a developer can call `agent.dwn.node` to access
     *   the DWN instance and not `agent.dwn.dwn`.
     */
    get node() {
        if (!this._dwn) {
            throw new Error('AgentDwnApi: The in-process DWN instance is not available. ' +
                'The agent is operating in remote mode (local DWN server at ' +
                `'${this._localDwnEndpoint}'). Use processRequest() instead ` +
                'of accessing the DWN node directly.');
        }
        return this._dwn;
    }
    static createDwn(_a) {
        return __awaiter(this, arguments, void 0, function* ({ dataPath, dataStore, didResolver, messageLog, tenantGate, resumableTaskStore }) {
            if (dataStore === undefined) {
                const { DataStoreLevel } = yield import('@enbox/dwn-sdk-js/stores/level');
                dataStore = new DataStoreLevel({ blockstoreLocation: `${dataPath}/DWN_DATASTORE` });
            }
            if (didResolver === undefined) {
                const { DidResolverCacheLevel } = yield import('@enbox/dids/resolver-cache-level');
                didResolver = new UniversalResolver({
                    didResolvers: [DidDht, DidJwk],
                    cache: new DidResolverCacheLevel({ location: `${dataPath}/DID_RESOLVERCACHE` }),
                });
            }
            if (resumableTaskStore === undefined) {
                const { ResumableTaskStoreLevel } = yield import('@enbox/dwn-sdk-js/stores/level');
                resumableTaskStore = new ResumableTaskStoreLevel({ location: `${dataPath}/DWN_RESUMABLETASKSTORE` });
            }
            const { eventLog, messageStore } = messageLog !== null && messageLog !== void 0 ? messageLog : yield AgentDwnApi.createDefaultMessageLog(dataPath);
            return yield Dwn.create({ dataStore, didResolver, eventLog, messageStore, tenantGate, resumableTaskStore });
        });
    }
    static createDefaultMessageLog(dataPath) {
        return __awaiter(this, void 0, void 0, function* () {
            const { MessageStoreLevel } = yield import('@enbox/dwn-sdk-js/stores/level');
            const wakePublisher = new EventEmitterWakePublisher();
            const messageStore = new MessageStoreLevel({
                location: `${dataPath}/DWN_MESSAGESTORE`,
                wakePublisher,
            });
            return { eventLog: new DurableEventLog(messageStore, wakePublisher), messageStore };
        });
    }
    processRequest(request) {
        return __awaiter(this, void 0, void 0, function* () {
            // Constructs a DWN message. and if there is a data payload, prepares the data as a
            // Web ReadableStream.
            const { message, dataStream } = yield this.constructDwnMessage({ request });
            // Extracts the optional subscription handler from the request to pass into `processMessage.
            const { subscriptionHandler } = request;
            // Conditionally processes the message with the DWN instance:
            // - If `store` is not explicitly set to false, it sends the message to the DWN node for
            //   processing, passing along the target DID, the message, and any associated data stream.
            // - If `store` is set to false, it immediately returns a simulated 'accepted' status without
            //   storing the message/data in the DWN node.
            let reply;
            if (request.store === false) {
                reply = { status: { code: 202, detail: 'Accepted' } };
            }
            else if (this._dwn) {
                // Local mode: process directly with the in-process DWN.
                reply = yield this._dwn.processMessage(request.target, message, { dataStream: dataStream, subscriptionHandler });
            }
            else {
                // Remote mode: route through RPC to the local DWN server.
                reply = yield this.sendDwnRpcRequest({
                    targetDid: request.target,
                    dwnEndpointUrls: [this._localDwnEndpoint],
                    message,
                    data: dataStream,
                    subscriptionHandler,
                });
            }
            if (reply.status.code === 202 &&
                isDwnRequest(request, DwnInterface.RecordsWrite) &&
                !request.rawMessage) {
                try {
                    yield this.provisionAudienceKeyForAcceptedRoleRecord(request, message);
                }
                catch (error) {
                    logger.log(`AgentDwnApi: audience delivery provisioning failed after accepting role record '${message.recordId}': ` +
                        `${error instanceof Error ? error.message : String(error)}`);
                }
            }
            yield this.maybeDecryptReply(request, reply);
            // Returns an object containing the reply from processing the message, the original message,
            // and the content identifier (CID) of the message.
            return {
                reply,
                message,
                messageCid: yield Message.getCid(message),
            };
        });
    }
    /**
     * Process a pre-constructed DWN message against the local DWN (in-process
     * or remote server). Used by the sync engine to store messages that were
     * already fetched from a remote DWN.
     *
     * Unlike {@link processRequest}, this method does NOT construct a new
     * message — it takes an already-signed `GenericMessage` and routes it
     * to the appropriate backend.
     *
     * @param tenant - The DID of the DWN tenant (target).
     * @param message - The pre-constructed DWN message.
     * @param options - Optional data stream and subscription handler.
     * @returns The reply from processing the message.
     */
    processRawMessage(tenant, message, options) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this._dwn) {
                return this._dwn.processMessage(tenant, message, { dataStream: options === null || options === void 0 ? void 0 : options.dataStream });
            }
            return this.sendDwnRpcRequest({
                targetDid: tenant,
                dwnEndpointUrls: [this._localDwnEndpoint],
                message: message,
                data: options === null || options === void 0 ? void 0 : options.dataStream,
            });
        });
    }
    /**
     * Applies a replicated message to the local DWN and returns a structured sync
     * outcome. In-process DWNs call the native entry point directly. Local DWN
     * server mode calls the server's matching replication RPC so duplicate replay
     * and replication-index repair stay server-side instead of falling back to the
     * normal authoring path.
     */
    applyReplicatedMessage(tenant, message, options) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this._dwn) {
                return this._dwn.applyReplicatedMessage(tenant, message, options);
            }
            return this.agent.rpc.applyReplicatedMessage({
                targetDid: tenant,
                dwnUrl: this._localDwnEndpoint,
                message,
                data: options === null || options === void 0 ? void 0 : options.dataStream,
            });
        });
    }
    sendRequest(request) {
        return __awaiter(this, void 0, void 0, function* () {
            // Resolve DWN service endpoint URLs, with local DWN discovery if enabled.
            const dwnEndpointUrls = yield this.getDwnEndpointUrlsForTarget(request.target);
            if (dwnEndpointUrls.length === 0) {
                throw new Error(`AgentDwnApi: DID Service is missing or malformed: ${request.target}#dwn`);
            }
            let messageCid;
            let message;
            let data;
            let subscriptionHandler;
            // If `messageCid` is given, retrieve message and data, if any.
            if ('messageCid' in request) {
                ({ message, data } = yield this.getDwnMessage({
                    author: request.author,
                    messageCid: request.messageCid,
                    messageType: request.messageType
                }));
                messageCid = request.messageCid;
            }
            else {
                // Otherwise, construct a new message.
                ({ message, dataStream: data } = yield this.constructDwnMessage({ request }));
                subscriptionHandler = request.subscriptionHandler;
            }
            // Build a resubscribe factory for subscribe requests. This closure
            // captures the original request so it can reconstruct and re-sign a new
            // subscribe message with a cursor on reconnection.
            let resubscribeFactory;
            if (subscriptionHandler !== undefined && !('messageCid' in request)) {
                resubscribeFactory = (cursor) => __awaiter(this, void 0, void 0, function* () {
                    const resumeParams = cursor === undefined
                        ? request.messageParams
                        : Object.assign(Object.assign({}, request.messageParams), { cursor });
                    const resumeRequest = Object.assign(Object.assign({}, request), { messageParams: resumeParams });
                    const { message: resumeMessage } = yield this.constructDwnMessage({ request: resumeRequest });
                    return resumeMessage;
                });
            }
            // Send the RPC request to the target DID's DWN service endpoint using the Agent's RPC client.
            const reply = yield this.sendDwnRpcRequest({
                targetDid: request.target,
                dwnEndpointUrls,
                message,
                data,
                subscriptionHandler,
                resubscribeFactory,
            });
            // Auto-decrypt reply data if encryption is enabled (Component 7)
            yield this.maybeDecryptReply(request, reply);
            // If the message CID was not given in the `request`, compute it.
            messageCid !== null && messageCid !== void 0 ? messageCid : (messageCid = yield Message.getCid(message));
            // Returns an object containing the reply from processing the message, the original message,
            // and the content identifier (CID) of the message.
            return { reply, message, messageCid };
        });
    }
    sendDwnRpcRequest(_a) {
        return __awaiter(this, arguments, void 0, function* ({ targetDid, dwnEndpointUrls, message, data, subscriptionHandler, resubscribeFactory }) {
            const errorMessages = [];
            if (message.descriptor.method === DwnMethodName.Subscribe && subscriptionHandler === undefined) {
                throw new Error('AgentDwnApi: Subscription handler is required for subscription requests.');
            }
            // Try sending to author's publicly addressable DWNs until the first request succeeds.
            for (let dwnUrl of dwnEndpointUrls) {
                try {
                    if (subscriptionHandler !== undefined) {
                        // we get the server info to check if the server supports WebSocket for subscription requests
                        const serverInfo = yield this.agent.rpc.getServerInfo(dwnUrl);
                        if (!serverInfo.webSocketSupport) {
                            // If the server does not support WebSocket, add an error message and continue to the next URL.
                            errorMessages.push({
                                url: dwnUrl,
                                message: 'WebSocket support is not enabled on the server.'
                            });
                            continue;
                        }
                        // If the server supports WebSocket, replace the subscription URL with a socket transport.
                        // For `http` we use the unsecured `ws` protocol, and for `https` we use the secured `wss` protocol.
                        const parsedUrl = new URL(dwnUrl);
                        parsedUrl.protocol = parsedUrl.protocol === 'http:' ? 'ws:' : 'wss:';
                        dwnUrl = parsedUrl.toString();
                    }
                    const dwnReply = yield this.agent.rpc.sendDwnRequest({
                        dwnUrl,
                        targetDid,
                        message,
                        data,
                        subscription: subscriptionHandler ? {
                            handler: subscriptionHandler,
                            resubscribeFactory,
                        } : undefined,
                    });
                    return dwnReply;
                }
                catch (error) {
                    errorMessages.push({
                        url: dwnUrl,
                        message: (error instanceof Error) ? error.message : 'Unknown error',
                    });
                }
            }
            throw new Error(`Failed to send DWN RPC request: ${JSON.stringify(errorMessages)}`);
        });
    }
    provisionAudienceKeyForAcceptedRoleRecord(request, recordsWrite) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b, _c, _d, _e, _f, _g;
            const descriptor = recordsWrite.descriptor;
            if (descriptor.protocol === undefined ||
                descriptor.protocolPath === undefined ||
                descriptor.recipient === undefined) {
                return;
            }
            const protocolDefinition = yield this.getProtocolDefinition(request.target, descriptor.protocol, request.granteeDid);
            if (protocolDefinition === undefined) {
                return;
            }
            const ruleSet = getRuleSetAtPath(descriptor.protocolPath, protocolDefinition.structure);
            if ((ruleSet === null || ruleSet === void 0 ? void 0 : ruleSet.$role) !== true) {
                return;
            }
            if (((_a = ruleSet.$keyAgreement) === null || _a === void 0 ? void 0 : _a.publicKeyJwk) === undefined) {
                return;
            }
            const contextId = getRoleAudienceContextId(descriptor.protocolPath, recordsWrite.contextId);
            if (contextId === undefined) {
                throw new Error(`AgentDwnApi: Unable to determine role audience context for '${descriptor.protocolPath}'.`);
            }
            const audienceKey = yield this.getOrCreateAudienceKey({
                authorDid: request.author,
                contextId,
                delegatedGrant: (_b = request.messageParams) === null || _b === void 0 ? void 0 : _b.delegatedGrant,
                granteeDid: request.granteeDid,
                permissionGrantId: (_c = request.messageParams) === null || _c === void 0 ? void 0 : _c.permissionGrantId,
                protocol: descriptor.protocol,
                protocolRole: (_d = request.messageParams) === null || _d === void 0 ? void 0 : _d.protocolRole,
                rolePath: descriptor.protocolPath,
                sourceDid: request.target,
            });
            const recipientRolePublicKey = yield this.getRecipientRolePublicKey({
                protocol: descriptor.protocol,
                recipientDid: descriptor.recipient,
                rolePath: descriptor.protocolPath,
            });
            yield createAudienceDeliveryRecordFn({
                agent: this.agent,
                audienceKey,
                authorDid: request.author,
                delegatedGrant: (_e = request.messageParams) === null || _e === void 0 ? void 0 : _e.delegatedGrant,
                granteeDid: request.granteeDid,
                permissionGrantId: (_f = request.messageParams) === null || _f === void 0 ? void 0 : _f.permissionGrantId,
                protocolRole: (_g = request.messageParams) === null || _g === void 0 ? void 0 : _g.protocolRole,
                recipientAuthority: EncryptionControlDeliveryRecipientAuthority.RoleHolder,
                recipientDid: descriptor.recipient,
                recipientRolePublicKey,
                sourceDid: request.target,
            });
        });
    }
    getRoleAudienceKeyEncryptionInputs(params) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            const inputs = [];
            const pendingAudienceRecords = [];
            const readRules = (_b = (_a = params.sourceRuleSet.$actions) === null || _a === void 0 ? void 0 : _a.filter((rule) => { var _a; return rule.role !== undefined && ((_a = rule.can) === null || _a === void 0 ? void 0 : _a.includes('read')); })) !== null && _b !== void 0 ? _b : [];
            for (const rule of readRules) {
                const roleAudience = this.resolveRoleAudienceRule(rule.role, params.protocolDefinition, params.parentContextId);
                const pendingRoleAudience = roleAudience === undefined &&
                    this.canPreparePendingAudienceRecord(rule.role, params.parentContextId)
                    ? yield this.preparePendingAudienceRecord(rule.role, params.protocolDefinition)
                    : undefined;
                if (roleAudience === undefined && pendingRoleAudience === undefined) {
                    throw new Error(`AgentDwnApi: Unable to resolve encrypted role audience '${rule.role}' ` +
                        `for '${params.protocol}/${params.sourceProtocolPath}'.`);
                }
                const audienceKey = roleAudience === undefined
                    ? pendingRoleAudience.audienceKey
                    : yield this.getOrCreateAudiencePublicKey({
                        authorDid: params.authorDid,
                        contextId: roleAudience.contextId,
                        delegatedGrant: params.delegatedGrant,
                        granteeDid: params.granteeDid,
                        permissionGrantId: params.permissionGrantId,
                        protocol: roleAudience.protocol,
                        protocolRole: params.protocolRole,
                        rolePath: roleAudience.rolePath,
                        sourceDid: params.sourceDid,
                    });
                if (pendingRoleAudience !== undefined) {
                    pendingAudienceRecords.push(pendingRoleAudience);
                }
                const audiencePublicKey = 'publicKeyJwk' in audienceKey
                    ? audienceKey.publicKeyJwk
                    : audienceKey.keyMaterial.publicKeyJwk;
                inputs.push({
                    algorithm: KeyAgreementAlgorithm.X25519HkdfSha256A256Kw,
                    derivationScheme: ROLE_AUDIENCE_DERIVATION_SCHEME,
                    keyId: audienceKey.keyId,
                    protocol: audienceKey.protocol,
                    publicKey: audiencePublicKey,
                    rolePath: audienceKey.rolePath,
                });
            }
            return { inputs, pendingAudienceRecords };
        });
    }
    canPreparePendingAudienceRecord(roleRef, parentContextId) {
        if (isCrossProtocolRef(roleRef)) {
            return false;
        }
        const pendingContextId = parentContextId === undefined ? 'pending-record' : `${parentContextId}/pending-record`;
        return getRoleAudienceContextId(roleRef, pendingContextId) !== undefined;
    }
    preparePendingAudienceRecord(roleRef, protocolDefinition) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            if (isCrossProtocolRef(roleRef)) {
                return undefined;
            }
            const ruleSet = getRuleSetAtPath(roleRef, protocolDefinition.structure);
            const sealingPublicKey = (_a = ruleSet === null || ruleSet === void 0 ? void 0 : ruleSet.$keyAgreement) === null || _a === void 0 ? void 0 : _a.publicKeyJwk;
            if ((ruleSet === null || ruleSet === void 0 ? void 0 : ruleSet.$role) !== true || sealingPublicKey === undefined) {
                return undefined;
            }
            return {
                audienceKey: yield generateAudienceKeyFn({
                    contextId: '',
                    protocol: protocolDefinition.protocol,
                    rolePath: roleRef,
                }),
                rolePath: roleRef,
                sealingPublicKey,
            };
        });
    }
    resolveRoleAudienceRule(roleRef, protocolDefinition, parentContextId) {
        if (isCrossProtocolRef(roleRef)) {
            return undefined;
        }
        const contextId = getRoleAudienceContextId(roleRef, parentContextId);
        return contextId === undefined ? undefined : { protocol: protocolDefinition.protocol, rolePath: roleRef, contextId };
    }
    getRoleAudienceSourceContextId(params) {
        if (params.parentContextId !== undefined) {
            return params.parentContextId;
        }
        return params.protocolPath.includes('/') ? undefined : params.recordId;
    }
    getOrCreateAudiencePublicKey(params) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            const existingAudience = yield this.resolveCurrentAudienceRecord({
                authorDid: (_a = params.granteeDid) !== null && _a !== void 0 ? _a : params.authorDid,
                contextId: params.contextId,
                protocol: params.protocol,
                rolePath: params.rolePath,
                sourceDid: params.sourceDid,
            });
            if (existingAudience !== undefined) {
                return {
                    contextId: existingAudience.payload.contextId,
                    keyId: existingAudience.payload.keyId,
                    protocol: existingAudience.payload.protocol,
                    publicKeyJwk: existingAudience.payload.publicKeyJwk,
                    rolePath: existingAudience.payload.rolePath,
                };
            }
            const audienceKey = yield this.getOrCreateAudienceKey(params);
            return {
                contextId: audienceKey.contextId,
                keyId: audienceKey.keyId,
                protocol: audienceKey.protocol,
                publicKeyJwk: audienceKey.keyMaterial.publicKeyJwk,
                rolePath: audienceKey.rolePath,
            };
        });
    }
    getOrCreateAudienceKey(params) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            const existingAudience = yield this.resolveCurrentAudienceRecord({
                authorDid: (_a = params.granteeDid) !== null && _a !== void 0 ? _a : params.authorDid,
                contextId: params.contextId,
                protocol: params.protocol,
                rolePath: params.rolePath,
                sourceDid: params.sourceDid,
            });
            if (existingAudience !== undefined) {
                const resolved = yield resolveAudienceDecryptionKeyFn({
                    agent: this.agent,
                    audienceDecryptionKeyCache: this._audienceDecryptionKeyCache,
                    delegateDecryptionKeyCache: this._delegateDecryptionKeyCache,
                    contextId: existingAudience.payload.contextId,
                    granteeDid: params.granteeDid,
                    keyId: existingAudience.payload.keyId,
                    protocol: existingAudience.payload.protocol,
                    recipientDid: params.authorDid,
                    rolePath: existingAudience.payload.rolePath,
                    sourceDid: params.sourceDid,
                });
                if (resolved === undefined) {
                    throw new Error(`AgentDwnApi: Audience key '${existingAudience.payload.keyId}' exists, but no seal or delivery can open it.`);
                }
                return {
                    contextId: existingAudience.payload.contextId,
                    keyId: existingAudience.payload.keyId,
                    keyMaterial: resolved.keyMaterial,
                    protocol: existingAudience.payload.protocol,
                    rolePath: existingAudience.payload.rolePath,
                };
            }
            const protocolDefinition = yield this.getProtocolDefinition(params.sourceDid, params.protocol, params.granteeDid);
            if (protocolDefinition === undefined) {
                throw new Error(`AgentDwnApi: protocol '${params.protocol}' is not installed for '${params.sourceDid}'.`);
            }
            const ruleSet = getRuleSetAtPath(params.rolePath, protocolDefinition.structure);
            const sealingPublicKey = (_b = ruleSet === null || ruleSet === void 0 ? void 0 : ruleSet.$keyAgreement) === null || _b === void 0 ? void 0 : _b.publicKeyJwk;
            if ((ruleSet === null || ruleSet === void 0 ? void 0 : ruleSet.$role) !== true || sealingPublicKey === undefined) {
                throw new Error(`AgentDwnApi: role '${params.rolePath}' is not an encrypted audience ` +
                    `(requires $role with $keyAgreement) in protocol '${params.protocol}'.`);
            }
            yield this.assertAudienceSealCoverage({
                contextId: params.contextId,
                granteeDid: params.granteeDid,
                protocol: params.protocol,
                rolePath: params.rolePath,
                sourceDid: params.sourceDid,
            });
            const created = yield createAudienceRecordFn({
                agent: this.agent,
                authorDid: params.authorDid,
                contextId: params.contextId,
                delegatedGrant: params.delegatedGrant,
                granteeDid: params.granteeDid,
                permissionGrantId: params.permissionGrantId,
                protocol: params.protocol,
                protocolRole: params.protocolRole,
                rolePath: params.rolePath,
                sealingPublicKey,
                sourceDid: params.sourceDid,
            });
            this._audienceDecryptionKeyCache.set(getAudienceDecryptionKeyCacheKey({
                contextId: params.contextId,
                keyId: created.audienceKey.keyId,
                protocol: params.protocol,
                recipientDid: params.authorDid,
                rolePath: params.rolePath,
                sourceDid: params.sourceDid,
            }), {
                contextId: params.contextId,
                keyMaterial: created.audienceKey.keyMaterial,
                protocol: params.protocol,
                recipientDid: params.authorDid,
                rolePath: params.rolePath,
                sourceDid: params.sourceDid,
            });
            return created.audienceKey;
        });
    }
    assertAudienceSealCoverage(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const hasCoverage = yield hasAudienceSealCoverageFn({
                agent: this.agent,
                delegateDecryptionKeyCache: this._delegateDecryptionKeyCache,
                granteeDid: params.granteeDid,
                protocol: params.protocol,
                rolePath: params.rolePath,
                sourceDid: params.sourceDid,
            });
            if (!hasCoverage) {
                throw new Error(`AgentDwnApi: Cannot mint role audience (${params.protocol}, ${params.rolePath}, ${params.contextId}) without seal coverage. ` +
                    'Use the owner identity or a delegate with grantKey material covering the role path, or provision the audience with a seal-covered actor first.');
            }
        });
    }
    resolveCurrentAudienceRecord(params) {
        return __awaiter(this, void 0, void 0, function* () {
            if (this._dwn !== undefined) {
                const record = yield EncryptionControl.resolveCurrentAudienceRecord({
                    contextId: params.contextId,
                    messageStore: this._dwn.storage.messageStore,
                    protocol: params.protocol,
                    rolePath: params.rolePath,
                    tenant: params.sourceDid,
                });
                if (record !== undefined) {
                    const payload = yield this.readAudiencePayload(params.authorDid, params.sourceDid, record);
                    return payload === undefined ? undefined : { message: record, payload };
                }
            }
            const { reply } = yield this.processRequestWithRemoteFallback({
                author: params.authorDid,
                target: params.sourceDid,
                messageType: DwnInterface.RecordsQuery,
                messageParams: {
                    filter: {
                        protocol: params.protocol,
                        protocolPath: ENCRYPTION_CONTROL_AUDIENCE_PATH,
                        tags: {
                            protocol: params.protocol,
                            rolePath: params.rolePath,
                            contextId: params.contextId,
                        },
                    },
                },
            }, (reply) => reply.status.code === 200 && reply.entries !== undefined && reply.entries.length > 0);
            if (reply.status.code !== 200 || reply.entries === undefined || reply.entries.length === 0) {
                return undefined;
            }
            const records = reply.entries;
            records.sort((left, right) => EncryptionControl.compareAudienceProjectionCandidates(params.sourceDid, left, right));
            for (const record of records) {
                const payload = yield this.readAudiencePayload(params.authorDid, params.sourceDid, record);
                if (payload !== undefined) {
                    return { message: record, payload };
                }
            }
            return undefined;
        });
    }
    readAudiencePayload(authorDid, sourceDid, record) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            if ('encodedData' in record && typeof record.encodedData === 'string') {
                return Encoder.base64UrlToObject(record.encodedData);
            }
            const { reply } = yield this.processRequestWithRemoteFallback({
                author: authorDid,
                target: sourceDid,
                messageType: DwnInterface.RecordsRead,
                messageParams: { filter: { recordId: record.recordId } },
            }, (reply) => { var _a; return reply.status.code === 200 && ((_a = reply.entry) === null || _a === void 0 ? void 0 : _a.data) !== undefined; });
            if (reply.status.code !== 200 || ((_a = reply.entry) === null || _a === void 0 ? void 0 : _a.data) === undefined) {
                return undefined;
            }
            return Encoder.bytesToObject(yield DataStream.toBytes(reply.entry.data));
        });
    }
    processRequestWithRemoteFallback(request, hasUsableReply) {
        return __awaiter(this, void 0, void 0, function* () {
            return processDwnReadThrough({
                process: this.processRequest.bind(this),
                send: this.sendRequest.bind(this),
            }, request, hasUsableReply);
        });
    }
    getRecipientRolePublicKey(params) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            let protocolDefinition;
            try {
                protocolDefinition = yield this.getProtocolDefinition(params.recipientDid, params.protocol);
            }
            catch (_b) {
                protocolDefinition = undefined;
            }
            protocolDefinition !== null && protocolDefinition !== void 0 ? protocolDefinition : (protocolDefinition = yield this.fetchRemoteProtocolDefinition(params.recipientDid, params.protocol));
            const roleRuleSet = getRuleSetAtPath(params.rolePath, protocolDefinition.structure);
            const publicKeyJwk = (_a = roleRuleSet === null || roleRuleSet === void 0 ? void 0 : roleRuleSet.$keyAgreement) === null || _a === void 0 ? void 0 : _a.publicKeyJwk;
            if (publicKeyJwk === undefined) {
                throw new Error(`AgentDwnApi: Recipient '${params.recipientDid}' has no encryption key for role path ` +
                    `'${params.protocol}/${params.rolePath}'.`);
            }
            return publicKeyJwk;
        });
    }
    buildPendingAudienceRecordDescriptor(params) {
        return __awaiter(this, void 0, void 0, function* () {
            const descriptorOptions = Object.assign(Object.assign({}, params.messageParams), { dataCid: params.dataCid, dataSize: params.dataSize });
            delete descriptorOptions.data;
            return RecordsWrite.createDescriptor(descriptorOptions);
        });
    }
    constructDwnMessage(_a) {
        return __awaiter(this, arguments, void 0, function* ({ request }) {
            var _b, _c, _d, _e, _f, _g;
            // if the request has a granteeDid, ensure the messageParams include the proper grant parameters
            if (request.granteeDid && !this.hasGrantParams(request.messageParams)) {
                throw new Error('AgentDwnApi: Requested to sign with a permission but no grant messageParams were provided in the request');
            }
            const rawMessage = request.rawMessage;
            let readableStream;
            // if the request is a RecordsWrite message, we need to handle the data stream and update the messageParams accordingly
            if (isDwnRequest(request, DwnInterface.RecordsWrite)) {
                const messageParams = request.messageParams;
                if (request.dataStream && !(messageParams === null || messageParams === void 0 ? void 0 : messageParams.data)) {
                    const { dataStream } = request;
                    let forCid;
                    if (dataStream instanceof Blob) {
                        const [cidCopy, processCopy] = dataStream.stream().tee();
                        forCid = cidCopy;
                        readableStream = processCopy;
                    }
                    else if (dataStream instanceof ReadableStream) {
                        const [cidCopy, processCopy] = dataStream.tee();
                        forCid = cidCopy;
                        readableStream = processCopy;
                    }
                    if (!rawMessage && messageParams) {
                        messageParams.dataCid = yield Cid.computeDagPbCidFromStream(forCid);
                        // Compute data size by consuming forCid (already consumed by computeDagPbCidFromStream)
                        // and using the Blob/stream size if available.
                        if (messageParams.dataSize === undefined) {
                            if (dataStream instanceof Blob) {
                                messageParams.dataSize = dataStream.size;
                            }
                            // For ReadableStream without known size, the SDK will compute it during processMessage.
                        }
                    }
                }
            }
            // Auto-inject encryption keys into protocol definition (Component 5)
            if (isDwnRequest(request, DwnInterface.ProtocolsConfigure) && request.encryption && !rawMessage) {
                const messageParams = request.messageParams;
                const keyDeriver = yield getEncryptionKeyDeriverFn(this.agent, request.author);
                // SDK walks the protocol structure and calls our callback for each path.
                // The KMS performs HKDF derivation + public key computation internally.
                messageParams.definition = yield Protocols.deriveAndInjectPublicEncryptionKeys(messageParams.definition, keyDeriver);
                // Invalidate cache for this protocol
                this._protocolDefinitionCache.delete(`${request.target}~${messageParams.definition.protocol}`);
            }
            // Cache locally-imported protocol definitions so delegated encrypted
            // writes can resolve `$keyAgreement` keys without re-querying unpublished
            // owner-tenant protocols.
            if (isDwnRequest(request, DwnInterface.ProtocolsConfigure) && !request.encryption) {
                const protocolConfigureMessage = rawMessage;
                const def = (_b = protocolConfigureMessage === null || protocolConfigureMessage === void 0 ? void 0 : protocolConfigureMessage.descriptor.definition) !== null && _b !== void 0 ? _b : (_c = request.messageParams) === null || _c === void 0 ? void 0 : _c.definition;
                if (def === null || def === void 0 ? void 0 : def.protocol) {
                    this._protocolDefinitionCache.set(`${request.target}~${def.protocol}`, def);
                }
            }
            // Auto-encrypt RecordsWrite data to the exact protocol-path `$keyAgreement`
            // key published by the target's installed protocol definition.
            if (isDwnRequest(request, DwnInterface.RecordsWrite) && request.encryption && !rawMessage) {
                const messageParams = request.messageParams;
                if ((messageParams === null || messageParams === void 0 ? void 0 : messageParams.protocol) && messageParams.protocolPath) {
                    const isCrossDwn = request.target !== request.author;
                    // 1. Fetch the protocol definition — local for same-DWN, remote for cross-DWN
                    let protocolDefinition;
                    if (isCrossDwn) {
                        protocolDefinition = yield this.fetchRemoteProtocolDefinition(request.target, messageParams.protocol);
                    }
                    else {
                        protocolDefinition = yield this.getProtocolDefinition(request.target, messageParams.protocol, request.granteeDid);
                    }
                    if (!protocolDefinition) {
                        throw new Error(`AgentDwnApi: Protocol '${messageParams.protocol}' is not installed ` +
                            `for '${request.target}'. Install the protocol before writing ` +
                            `encrypted records.`);
                    }
                    // 2. Walk the protocol structure to find $keyAgreement for this protocol path
                    const protocolPathSegments = messageParams.protocolPath.split('/');
                    let ruleSet = protocolDefinition.structure;
                    for (const segment of protocolPathSegments) {
                        ruleSet = ruleSet[segment];
                    }
                    if (!(ruleSet === null || ruleSet === void 0 ? void 0 : ruleSet.$keyAgreement)) {
                        throw new Error(`AgentDwnApi: Protocol '${messageParams.protocol}' at path ` +
                            `'${messageParams.protocolPath}' does not have encryption configured. ` +
                            `Configure the protocol with encryption: true.`);
                    }
                    // 3. Get plaintext bytes (normalize from all supported input types)
                    let plaintextBytes;
                    if (messageParams.data) {
                        plaintextBytes = messageParams.data instanceof Uint8Array
                            ? messageParams.data
                            : new TextEncoder().encode(String(messageParams.data));
                    }
                    else if (request.dataStream instanceof Blob) {
                        plaintextBytes = new Uint8Array(yield request.dataStream.arrayBuffer());
                    }
                    else if (request.dataStream instanceof ReadableStream) {
                        plaintextBytes = yield DataStream.toBytes(request.dataStream);
                    }
                    else {
                        throw new Error('AgentDwnApi: Data must be provided for encrypted records.');
                    }
                    // 4. Generate random DEK and IV.
                    const contentEncryptionAlgorithm = ContentEncryptionAlgorithm.A256CTR;
                    const dataEncryptionKey = crypto.getRandomValues(new Uint8Array(32));
                    const dataEncryptionIV = crypto.getRandomValues(new Uint8Array(ivLengthFn(contentEncryptionAlgorithm)));
                    // 5. Build ProtocolPath encryption input.
                    const protocolPathPublicKey = ruleSet.$keyAgreement.publicKeyJwk;
                    const encryptionInput = buildEncryptionInputFn(dataEncryptionKey, dataEncryptionIV, yield Encryption.getKeyId(protocolPathPublicKey), protocolPathPublicKey, KeyDerivationScheme.ProtocolPath);
                    const roleAudienceInputs = yield this.getRoleAudienceKeyEncryptionInputs({
                        authorDid: request.author,
                        delegatedGrant: messageParams.delegatedGrant,
                        granteeDid: request.granteeDid,
                        parentContextId: this.getRoleAudienceSourceContextId({
                            parentContextId: messageParams.parentContextId,
                            protocolPath: messageParams.protocolPath,
                            recordId: messageParams.recordId,
                        }),
                        permissionGrantId: messageParams.permissionGrantId,
                        protocol: messageParams.protocol,
                        protocolRole: messageParams.protocolRole,
                        protocolDefinition,
                        sourceDid: request.target,
                        sourceProtocolPath: messageParams.protocolPath,
                        sourceRuleSet: ruleSet,
                    });
                    encryptionInput.keyEncryptionInputs.push(...roleAudienceInputs.inputs);
                    // 6. Encrypt data and compute CID.
                    const { encryptedBytes, dataCid, dataSize } = yield encryptAndComputeCidFn(plaintextBytes, dataEncryptionKey, dataEncryptionIV, contentEncryptionAlgorithm);
                    // 7. Write pending role-audience records before finalizing the encrypted source record.
                    if (roleAudienceInputs.pendingAudienceRecords.length > 0) {
                        (_d = messageParams.dateCreated) !== null && _d !== void 0 ? _d : (messageParams.dateCreated = Time.getCurrentTimestamp());
                        (_e = messageParams.messageTimestamp) !== null && _e !== void 0 ? _e : (messageParams.messageTimestamp = messageParams.dateCreated);
                        if (messageParams.published === true) {
                            (_f = messageParams.datePublished) !== null && _f !== void 0 ? _f : (messageParams.datePublished = messageParams.dateCreated);
                        }
                        if (request.granteeDid && messageParams.permissionGrantId && !messageParams.delegatedGrant) {
                            yield this.populateDelegatedGrantForWrite(request.author, request.granteeDid, messageParams);
                        }
                        (_g = messageParams.recordId) !== null && _g !== void 0 ? _g : (messageParams.recordId = yield RecordsWrite.getEntryId(request.author, yield this.buildPendingAudienceRecordDescriptor({
                            dataCid,
                            dataSize,
                            messageParams,
                        })));
                        for (const pending of roleAudienceInputs.pendingAudienceRecords) {
                            const sourceContextId = messageParams.parentContextId === undefined
                                ? messageParams.recordId
                                : `${messageParams.parentContextId}/${messageParams.recordId}`;
                            const contextId = getRoleAudienceContextId(pending.rolePath, sourceContextId);
                            if (contextId === undefined) {
                                throw new Error(`AgentDwnApi: Unable to determine role audience context for '${pending.rolePath}'.`);
                            }
                            yield this.assertAudienceSealCoverage({
                                contextId,
                                granteeDid: request.granteeDid,
                                protocol: pending.audienceKey.protocol,
                                rolePath: pending.audienceKey.rolePath,
                                sourceDid: request.target,
                            });
                            const audienceKey = Object.assign(Object.assign({}, pending.audienceKey), { contextId });
                            yield createAudienceRecordFn({
                                agent: this.agent,
                                audienceKey,
                                authorDid: request.author,
                                contextId,
                                delegatedGrant: messageParams.delegatedGrant,
                                granteeDid: request.granteeDid,
                                permissionGrantId: messageParams.permissionGrantId,
                                protocol: audienceKey.protocol,
                                protocolRole: messageParams.protocolRole,
                                rolePath: audienceKey.rolePath,
                                sealingPublicKey: pending.sealingPublicKey,
                                sourceDid: request.target,
                            });
                            this._audienceDecryptionKeyCache.set(getAudienceDecryptionKeyCacheKey({
                                contextId,
                                keyId: audienceKey.keyId,
                                protocol: audienceKey.protocol,
                                recipientDid: request.author,
                                rolePath: audienceKey.rolePath,
                                sourceDid: request.target,
                            }), {
                                contextId,
                                keyMaterial: audienceKey.keyMaterial,
                                protocol: audienceKey.protocol,
                                recipientDid: request.author,
                                rolePath: audienceKey.rolePath,
                                sourceDid: request.target,
                            });
                        }
                    }
                    messageParams.dataCid = dataCid;
                    messageParams.dataSize = dataSize;
                    delete messageParams.data;
                    readableStream = DataStream.fromBytes(encryptedBytes);
                    request.dataStream = undefined;
                    messageParams.encryptionInput = encryptionInput;
                }
            }
            let dwnMessage;
            const dwnMessageConstructor = dwnMessageConstructors[request.messageType];
            // if a raw message is provided, parse it; otherwise create a new dwn message
            if (rawMessage) {
                dwnMessage = yield dwnMessageConstructor.parse(rawMessage);
                if (isRecordsWrite(dwnMessage) && request.signAsOwner) {
                    // if we are signing as owner, we use the author's signer
                    const signer = yield this.getSigner(request.author);
                    yield dwnMessage.signAsOwner(signer);
                }
                else if (request.granteeDid && isRecordsWrite(dwnMessage) && request.signAsOwnerDelegate) {
                    // if we are signing as owner delegate, we use the grantee's signer and the provided delegated grant
                    const signer = yield this.getSigner(request.granteeDid);
                    //if we have reached here, the presence of the grant params has already been checked
                    const messageParams = request.messageParams;
                    yield dwnMessage.signAsOwnerDelegate(signer, messageParams.delegatedGrant);
                }
            }
            else {
                if (request.messageParams === undefined) {
                    throw new Error('AgentDwnApi: messageParams must be provided when rawMessage is not given.');
                }
                // If we need to sign as an author delegate or with permissions we need to get the grantee's signer.
                // The messageParams should include either a permission grant invocation or a delegatedGrant message.
                const signer = request.granteeDid ?
                    yield this.getSigner(request.granteeDid) :
                    yield this.getSigner(request.author);
                // When signing as a delegate with a permissionGrantId, fetch the full
                // grant message and pass it as `delegatedGrant` so the DWN SDK correctly
                // sets `authorization.authorDelegatedGrant` and resolves the logical
                // author to the grantor (owner) rather than the signer (delegate).
                const params = Object.assign({}, request.messageParams);
                if (request.granteeDid && params.permissionGrantId && !params.delegatedGrant && isDwnRequest(request, DwnInterface.RecordsWrite)) {
                    yield this.populateDelegatedGrantForWrite(request.author, request.granteeDid, params);
                }
                dwnMessage = yield dwnMessageConstructor.create(Object.assign(Object.assign({}, params), { signer }));
            }
            return {
                message: dwnMessage.message,
                dataStream: readableStream,
            };
        });
    }
    populateDelegatedGrantForWrite(authorDid, granteeDid, messageParams) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            if (messageParams.permissionGrantId === undefined || messageParams.delegatedGrant !== undefined) {
                return;
            }
            const { reply: grantReply } = yield this.processRequest({
                author: granteeDid,
                target: authorDid,
                messageType: DwnInterface.RecordsRead,
                messageParams: { filter: { recordId: messageParams.permissionGrantId } },
            });
            if (grantReply.status.code === 200 && ((_a = grantReply.entry) === null || _a === void 0 ? void 0 : _a.recordsWrite) && ((_b = grantReply.entry) === null || _b === void 0 ? void 0 : _b.data)) {
                const grantDataBytes = yield DataStream.toBytes(grantReply.entry.data);
                messageParams.delegatedGrant = Object.assign(Object.assign({}, grantReply.entry.recordsWrite), { encodedData: Convert.uint8Array(grantDataBytes).toBase64Url() });
            }
        });
    }
    hasGrantParams(params) {
        return params !== undefined &&
            (('permissionGrantId' in params && params.permissionGrantId !== undefined) ||
                ('permissionGrantIds' in params && params.permissionGrantIds !== undefined) ||
                ('delegatedGrant' in params && params.delegatedGrant !== undefined));
    }
    getSigner(author) {
        return __awaiter(this, void 0, void 0, function* () {
            // If the author is the Agent's DID, use the Agent's signer.
            if (author === this.agent.agentDid.uri) {
                const signer = yield this.agent.agentDid.getSigner();
                return {
                    algorithm: signer.algorithm,
                    keyId: signer.keyId,
                    sign: (data) => __awaiter(this, void 0, void 0, function* () {
                        return yield signer.sign({ data });
                    })
                };
            }
            else {
                // Prefer a locally-stored BearerDid when the agent controls this DID.
                // This avoids an unnecessary DID resolution round-trip during signing,
                // which can fail if the resolver path encounters malformed cached data.
                try {
                    const localDid = yield this.agent.did.get({ didUri: author });
                    if (localDid) {
                        const signer = yield localDid.getSigner();
                        return {
                            algorithm: signer.algorithm,
                            keyId: signer.keyId,
                            sign: (data) => __awaiter(this, void 0, void 0, function* () {
                                return yield signer.sign({ data });
                            })
                        };
                    }
                    // Otherwise, use the author's DID to determine the signing method.
                    const signingMethod = yield this.agent.did.getSigningMethod({ didUri: author });
                    if (!signingMethod.publicKeyJwk) {
                        throw new Error(`Verification method '${signingMethod.id}' does not contain a public key in JWK format`);
                    }
                    // Compute the key URI of the verification method's public key.
                    const keyUri = yield this.agent.keyManager.getKeyUri({ key: signingMethod.publicKeyJwk });
                    // Verify that the key is present in the key manager. If not, an error is thrown.
                    const publicKey = yield this.agent.keyManager.getPublicKey({ keyUri });
                    // Bind the Agent's Key Manager to the signer.
                    const keyManager = this.agent.keyManager;
                    return {
                        algorithm: CryptoUtils.getJoseSignatureAlgorithmFromPublicKey(publicKey),
                        keyId: signingMethod.id,
                        sign: (data) => __awaiter(this, void 0, void 0, function* () {
                            return yield keyManager.sign({ data, keyUri: keyUri });
                        })
                    };
                }
                catch (error) {
                    throw new Error(`AgentDwnApi: Unable to get signer for author '${author}': ${error.message}`);
                }
            }
        });
    }
    /**
     * Constructs an EncryptionKeyDeriver callback for the SDK.
     * Delegates to the standalone function in `dwn-encryption.ts`.
     *
     * @param didUri - The DID URI to create the key deriver for
     * @returns An EncryptionKeyDeriver callback object
     */
    getEncryptionKeyDeriver(didUri) {
        return __awaiter(this, void 0, void 0, function* () {
            return getEncryptionKeyDeriverFn(this.agent, didUri);
        });
    }
    /**
     * Resolves the keyAgreement verification method for the given DID and returns
     * the key ID, key URI, and public key JWK.
     *
     * @param didUri - The DID URI to look up
     */
    getEncryptionKeyInfo(didUri) {
        return __awaiter(this, void 0, void 0, function* () {
            return getEncryptionKeyInfoFn(this.agent, didUri);
        });
    }
    /**
     * Constructs a ProtocolPath KeyDecrypter for the given DID.
     *
     * @param didUri - The DID URI to build a decrypter for
     */
    getKeyDecrypter(didUri) {
        return __awaiter(this, void 0, void 0, function* () {
            return getKeyDecrypterFn(this.agent, didUri);
        });
    }
    /**
     * Analyses a record write to determine which DIDs join the readable audience.
     *
     * @param params - Parameters for participant detection
     * @returns Set of DIDs that join the readable audience
     */
    detectNewParticipants(params) {
        return detectNewParticipantsFn(params);
    }
    /**
     * Fetches a protocol definition from the local DWN, with caching.
     * Returns undefined if the protocol is not installed.
     *
     * @param tenantDid - The tenant DID to query
     * @param protocolUri - The protocol URI to fetch
     * @returns The protocol definition, or undefined if not found
     */
    getProtocolDefinition(tenantDid, protocolUri, granteeDid) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this._dwn) {
                // Remote mode: query via RPC (same as fetchRemoteProtocolDefinition,
                // but for locally-managed DIDs). The remote protocol definition
                // cache uses a different key prefix, so we use a dedicated call.
                try {
                    return yield this.fetchRemoteProtocolDefinition(tenantDid, protocolUri);
                }
                catch (error) {
                    // Only treat "not found" responses as missing protocols.  Transient
                    // errors (network timeouts, auth failures) are rethrown so the
                    // caller does not silently skip encryption or other protocol-
                    // required behaviours.
                    const msg = error instanceof Error ? error.message : '';
                    if (msg.includes('not found') || msg.includes('404') || msg.includes('No protocol')) {
                        return undefined;
                    }
                    throw error;
                }
            }
            // When operating as a delegate, resolve the ProtocolsQuery grant so
            // the local DWN authorises the query for unpublished protocols.
            let permissionGrantId;
            if (granteeDid) {
                try {
                    const permissionsApi = new AgentPermissionsApi({ agent: this.agent });
                    const { grant } = yield permissionsApi.getPermissionForRequest({
                        connectedDid: tenantDid,
                        delegateDid: granteeDid,
                        protocol: protocolUri,
                        cached: true,
                        messageType: DwnInterface.ProtocolsQuery,
                    });
                    permissionGrantId = grant.id;
                }
                catch (_a) {
                    // No grant found — try without (works for published protocols).
                }
            }
            return getProtocolDefinitionFn(tenantDid, protocolUri, this._dwn, this.getSigner.bind(this), this._protocolDefinitionCache, granteeDid, permissionGrantId);
        });
    }
    /**
     * Fetches a protocol definition from a remote DWN.
     * Uses an unsigned ProtocolsQuery (public protocols can be queried anonymously).
     */
    fetchRemoteProtocolDefinition(targetDid, protocolUri) {
        return __awaiter(this, void 0, void 0, function* () {
            return fetchRemoteProtocolDefinitionFn(targetDid, protocolUri, this.getDwnEndpointUrlsForTarget.bind(this), this.sendDwnRpcRequest.bind(this), this._protocolDefinitionCache);
        });
    }
    /**
     * Post-processes a DWN reply, auto-decrypting data if encryption is enabled.
     * Delegates to the standalone function in `dwn-encryption.ts`.
     */
    maybeDecryptReply(request, reply) {
        return __awaiter(this, void 0, void 0, function* () {
            return maybeDecryptReplyFn(request, reply, this.agent, this._delegateDecryptionKeyCache, this._audienceDecryptionKeyCache);
        });
    }
    getDwnMessage(_a) {
        return __awaiter(this, arguments, void 0, function* ({ author, messageCid }) {
            const signer = yield this.getSigner(author);
            // Construct a MessagesRead message to fetch the message.
            const messagesRead = yield dwnMessageConstructors[DwnInterface.MessagesRead].create({
                messageCid: messageCid,
                signer
            });
            let result;
            if (this._dwn) {
                // Local mode: process directly with the in-process DWN.
                result = yield this._dwn.processMessage(author, messagesRead.message);
            }
            else {
                // Remote mode: route through RPC to the local DWN server.
                result = yield this.sendDwnRpcRequest({
                    targetDid: author,
                    dwnEndpointUrls: [this._localDwnEndpoint],
                    message: messagesRead.message,
                });
            }
            if (result.status.code !== 200) {
                throw new Error(`AgentDwnApi: Failed to read message, response status: ${result.status.code} - ${result.status.detail}`);
            }
            const messageEntry = result.entry;
            const message = messageEntry.message;
            const dwnMessageWithData = { message };
            // If the message is a RecordsWrite, data will be present in the form of a stream
            if (isRecordsWrite(messageEntry)) {
                // The processMessage result attaches a `data` ReadableStream to
                // RecordsWrite entries, but the declared type doesn't include it.
                // Narrow structurally to the runtime shape rather than going via
                // `Record<string, unknown>` (which loses the value type entirely).
                const entryData = messageEntry.data;
                if (entryData) {
                    dwnMessageWithData.data = entryData;
                }
            }
            return dwnMessageWithData;
        });
    }
    /**
     * Clears delegate decryption keys from the in-memory cache.
     *
     * @param delegateDid - If provided, clears keys for that delegate session only.
     *                      If omitted, clears all delegate keys.
     */
    clearDelegateDecryptionKeys(delegateDid) {
        if (delegateDid) {
            this._delegateDecryptionKeyCache.delete(`ddk~${delegateDid}`);
        }
        else {
            this._delegateDecryptionKeyCache.clear();
        }
    }
}
//# sourceMappingURL=dwn-api.js.map