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 { TtlCache } from '@enbox/common';
import { Cid, ContentEncryptionAlgorithm, DataStoreLevel, DataStream, Dwn, DwnMethodName, EventEmitterEventLog, Jws, KeyDerivationScheme, Message, MessageStoreLevel, Protocols, Records, ResumableTaskStoreLevel, StateIndexLevel, } from '@enbox/dwn-sdk-js';
import { CryptoUtils, X25519 } from '@enbox/crypto';
import { DidDht, DidJwk, DidResolverCacheLevel, UniversalResolver } from '@enbox/dids';
import { KeyDeliveryProtocolDefinition } from './store-data-protocols.js';
import { DwnInterface, dwnMessageConstructors } from './types/dwn.js';
import { getDwnServiceEndpointUrls, isRecordsWrite } from './utils.js';
// Re-export type guards for backward compatibility
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 extracted encryption functions
import { buildEncryptionInput as buildEncryptionInputFn, deriveContextEncryptionInput as deriveContextEncryptionInputFn, encryptAndComputeCid as encryptAndComputeCidFn, getEncryptionKeyDeriver as getEncryptionKeyDeriverFn, getEncryptionKeyInfo as getEncryptionKeyInfoFn, getKeyDecrypter as getKeyDecrypterFn, ivLength as ivLengthFn, maybeDecryptReply as maybeDecryptReplyFn, } from './dwn-encryption.js';
// Import extracted protocol utilities
import { detectNewParticipants as detectNewParticipantsFn, isMultiPartyContext as isMultiPartyContextFn, } from './protocol-utils.js';
// Import extracted key delivery functions
import { eagerSendContextKeyRecord as eagerSendContextKeyRecordFn, ensureKeyDeliveryProtocol as ensureKeyDeliveryProtocolFn, fetchContextKeyRecord as fetchContextKeyRecordFn, writeContextKeyRecord as writeContextKeyRecordFn, } from './dwn-key-delivery.js';
// Import extracted record upgrade function
import { upgradeExternalRootRecord as upgradeExternalRootRecordFn } from './dwn-record-upgrade.js';
// Import extracted protocol definition fetching functions
import { extractDerivedPublicKey as extractDerivedPublicKeyFn, fetchRemoteProtocolDefinition as fetchRemoteProtocolDefinitionFn, getProtocolDefinition as getProtocolDefinitionFn, } from './dwn-protocol-cache.js';
export class AgentDwnApi {
    constructor({ agent, dwn }) {
        /**
         * Protocol definition cache — TTL 30 minutes. Protocols rarely change.
         * Keyed by `${tenantDid}~${protocolUri}`.
         */
        this._protocolDefinitionCache = new TtlCache({
            ttl: 30 * 60 * 1000
        });
        /**
         * Context key cache — stores resolved context encryption key info for
         * multi-party protocols. Keyed by rootContextId. TTL 30 minutes.
         */
        this._contextKeyCache = new TtlCache({ ttl: 30 * 60 * 1000 });
        /**
         * Context-derived private key cache — stores DerivedPrivateJwk for contexts
         * where the current user is a participant (not the creator).
         * Keyed by `ctx~${authorDid}~${rootContextId}`. TTL 30 minutes.
         */
        this._contextDerivedKeyCache = new TtlCache({
            ttl: 30 * 60 * 1000
        });
        // ---------------------------------------------------------------------------
        // Key Delivery Protocol
        // ---------------------------------------------------------------------------
        /**
         * Cache for key delivery protocol installation status per tenant.
         * Once confirmed installed, we skip re-checking for 21 days.
         */
        this._keyDeliveryProtocolInstalledCache = new TtlCache({
            ttl: 21 * 24 * 60 * 60 * 1000,
            max: 1000,
        });
        // If an agent is provided, set it as the execution context for this API.
        this._agent = agent;
        // Set the DWN instance for this API.
        this._dwn = dwn;
    }
    /**
     * Retrieves the `Web5PlatformAgent` execution context.
     *
     * @returns The `Web5PlatformAgent` 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;
    }
    /**
     * 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
     *   `Web5PlatformAgent`. In other words, so that a developer can call `agent.dwn.node` to access
     *   the DWN instance and not `agent.dwn.dwn`.
     */
    get node() {
        return this._dwn;
    }
    static createDwn(_a) {
        return __awaiter(this, arguments, void 0, function* ({ dataPath, dataStore, didResolver, stateIndex, eventLog, messageStore, tenantGate, resumableTaskStore }) {
            dataStore !== null && dataStore !== void 0 ? dataStore : (dataStore = new DataStoreLevel({ blockstoreLocation: `${dataPath}/DWN_DATASTORE` }));
            didResolver !== null && didResolver !== void 0 ? didResolver : (didResolver = new UniversalResolver({
                didResolvers: [DidDht, DidJwk],
                cache: new DidResolverCacheLevel({ location: `${dataPath}/DID_RESOLVERCACHE` }),
            }));
            stateIndex !== null && stateIndex !== void 0 ? stateIndex : (stateIndex = new StateIndexLevel({ location: `${dataPath}/DWN_STATEINDEX` }));
            messageStore !== null && messageStore !== void 0 ? messageStore : (messageStore = new MessageStoreLevel(({
                blockstoreLocation: `${dataPath}/DWN_MESSAGESTORE`,
                indexLocation: `${dataPath}/DWN_MESSAGEINDEX`
            })));
            resumableTaskStore !== null && resumableTaskStore !== void 0 ? resumableTaskStore : (resumableTaskStore = new ResumableTaskStoreLevel({ location: `${dataPath}/DWN_RESUMABLETASKSTORE` }));
            eventLog !== null && eventLog !== void 0 ? eventLog : (eventLog = new EventEmitterEventLog());
            return yield Dwn.create({ dataStore, didResolver, stateIndex, eventLog, messageStore, tenantGate, resumableTaskStore });
        });
    }
    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.
            const reply = (request.store !== false)
                ? yield this._dwn.processMessage(request.target, message, { dataStream: dataStream, subscriptionHandler })
                : { status: { code: 202, detail: 'Accepted' } };
            // Post-write key delivery: detect new participants and write contextKey records.
            yield this.postWriteKeyDelivery(request, message, reply);
            // Auto-decrypt reply data if encryption is enabled (Component 7)
            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),
            };
        });
    }
    sendRequest(request) {
        return __awaiter(this, void 0, void 0, function* () {
            // First, confirm the target DID can be dereferenced and extract the DWN service endpoint URLs.
            const dwnEndpointUrls = yield getDwnServiceEndpointUrls(request.target, this.agent.did);
            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 } = yield this.constructDwnMessage({ request }));
                if (request.dataStream && !(request.dataStream instanceof Blob)) {
                    throw new Error('AgentDwnApi: DataStream must be provided as a Blob');
                }
                data = request.dataStream;
                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
                        ? Object.assign(Object.assign({}, request.messageParams), { cursor })
                        : request.messageParams;
                    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 };
        });
    }
    /**
     * Post-write key delivery: after a successful encrypted `RecordsWrite`,
     * detect new participants and write `contextKey` records so they can
     * decrypt records in the context.
     *
     * This is a non-fatal operation — if participant detection or key delivery
     * fails, the record is still stored and a warning is logged.
     */
    postWriteKeyDelivery(request, message, reply) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            if (!isDwnRequest(request, DwnInterface.RecordsWrite) ||
                !request.encryption ||
                reply.status.code !== 202) {
                return;
            }
            const writeParams = request.messageParams;
            // Skip key-delivery protocol writes to avoid infinite recursion (contextKey records are themselves encrypted)
            if (writeParams.protocol === KeyDeliveryProtocolDefinition.protocol) {
                return;
            }
            try {
                const protocolDefinition = yield this.getProtocolDefinition(request.target, writeParams.protocol);
                if (!protocolDefinition) {
                    return;
                }
                const recordsWriteMessage = message;
                // Reactive root-record upgrade (PR E): if this is an externally-authored
                // root record with only ProtocolPath encryption, the owner upgrades it by
                // appending a ProtocolContext recipient entry so that context key
                // holders (including the external author) can also decrypt.
                const authorDid = Jws.getSignerDid(recordsWriteMessage.authorization.signature.signatures[0]);
                const isExternallyAuthored = authorDid !== request.target;
                const isRootRecord = !writeParams.parentContextId;
                const rootPathSegment = writeParams.protocolPath.split('/')[0];
                const isMultiParty = isMultiPartyContextFn(protocolDefinition, rootPathSegment);
                if (isExternallyAuthored && isRootRecord && isMultiParty) {
                    try {
                        yield upgradeExternalRootRecordFn(this.agent, request.target, recordsWriteMessage, this._dwn, this.getSigner.bind(this), this._contextKeyCache);
                    }
                    catch (upgradeError) {
                        console.warn(`AgentDwnApi: Reactive root-record upgrade failed for ` +
                            `'${recordsWriteMessage.recordId}': ${upgradeError.message}`);
                    }
                }
                const newParticipants = detectNewParticipantsFn({
                    protocolDefinition,
                    protocolPath: writeParams.protocolPath,
                    recipient: writeParams.recipient,
                    tenantDid: request.target,
                    authorDid: isExternallyAuthored ? authorDid : undefined,
                });
                if (newParticipants.size > 0) {
                    // Derive the context key to deliver to participants
                    const rootContextId = ((_a = recordsWriteMessage.contextId) === null || _a === void 0 ? void 0 : _a.split('/')[0])
                        || recordsWriteMessage.contextId
                        || recordsWriteMessage.recordId;
                    const { keyId, keyUri } = yield getEncryptionKeyInfoFn(this.agent, request.target);
                    const contextDerivationPath = [
                        KeyDerivationScheme.ProtocolContext,
                        rootContextId,
                    ];
                    const contextDerivedPrivateKeyBytes = yield this.agent.keyManager.derivePrivateKeyBytes({
                        keyUri,
                        derivationPath: contextDerivationPath,
                    });
                    const contextDerivedPrivateJwk = yield X25519.bytesToPrivateKey({ privateKeyBytes: contextDerivedPrivateKeyBytes });
                    const contextKeyPayload = {
                        rootKeyId: keyId,
                        derivationScheme: KeyDerivationScheme.ProtocolContext,
                        derivationPath: contextDerivationPath,
                        derivedPrivateKey: contextDerivedPrivateJwk,
                    };
                    // Extract the author's key delivery public key from the record
                    // so we can encrypt the contextKey directly to the external author.
                    const authorKeyDeliveryPubKey = (_b = recordsWriteMessage.authorization) === null || _b === void 0 ? void 0 : _b.authorKeyDeliveryPublicKey;
                    for (const participantDid of newParticipants) {
                        try {
                            // Use the author's key delivery public key when delivering
                            // to the external author; for other participants (e.g.
                            // recipient, role holders) fall back to owner-key encryption.
                            const recipientKey = (participantDid === authorDid && authorKeyDeliveryPubKey)
                                ? authorKeyDeliveryPubKey
                                : undefined;
                            yield this.writeContextKeyRecord({
                                tenantDid: request.target,
                                recipientDid: participantDid,
                                contextKeyData: contextKeyPayload,
                                sourceProtocol: writeParams.protocol,
                                sourceContextId: rootContextId,
                                recipientKeyDeliveryPublicKey: recipientKey,
                            });
                        }
                        catch (keyDeliveryError) {
                            console.warn(`AgentDwnApi: Key delivery to '${participantDid}' for context ` +
                                `'${rootContextId}' failed: ${keyDeliveryError.message}. ` +
                                `The participant may not be able to decrypt records in this context.`);
                        }
                    }
                }
            }
            catch (detectionError) {
                // Participant detection failure is non-fatal — the record is still stored.
                console.warn(`AgentDwnApi: Post-write participant detection failed: ` +
                    `${detectionError.message}`);
            }
        });
    }
    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)}`);
        });
    }
    constructDwnMessage(_a) {
        return __awaiter(this, arguments, void 0, function* ({ request }) {
            // 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;
            // TODO: Consider refactoring to move data transformations imposed by fetch() limitations to the HTTP transport-related methods.
            // 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}`);
            }
            // Auto-encrypt data on RecordsWrite.
            //
            // Encryption scheme decision (unified key delivery):
            //   | Condition                                    | Scheme          |
            //   |----------------------------------------------|-----------------|
            //   | Local root record, multi-party               | ProtocolContext  | deferred (needs recordId)
            //   | Local non-root record, multi-party           | ProtocolContext  |
            //   | Local single-party                           | ProtocolPath    |
            //   | Cross-DWN root record, multi-party           | ProtocolPath    | target's key; owner upgrades later
            //   | Cross-DWN non-root record, multi-party       | ProtocolContext  | uses derivedPublicKey from existing records
            //   | Cross-DWN single-party                       | ProtocolPath    | target's key
            //
            // Key delivery happens as a post-write step in processRequest() via
            // detectNewParticipants() + writeContextKeyRecord(). Role records no
            // longer carry encrypted key payloads — they preserve user data.
            //
            // For local root multi-party records, encryption is deferred until after
            // message creation because contextId = recordId, which is only known after
            // create(). Follows the SDK two-pass pattern: create -> encryptSymmetricEncryptionKey -> sign.
            //
            // For cross-DWN writes (target !== author), the external author cannot
            // derive the target's context key. Root records use the target's ProtocolPath
            // public key. The target's agent reactively upgrades the record to include a
            // ProtocolContext recipient entry. Non-root records extract the context
            // public key (derivedPublicKey) from existing ProtocolContext-encrypted records
            // in the same context on the target's DWN.
            // Tracks deferred context encryption info for root multi-party records.
            let deferredContextEncryption;
            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);
                    }
                    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 $encryption 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.$encryption)) {
                        throw new Error(`AgentDwnApi: Protocol '${messageParams.protocol}' at path ` +
                            `'${messageParams.protocolPath}' does not have encryption configured. ` +
                            `Configure the protocol with encryption: true.`);
                    }
                    // 3. Classify the record
                    const rootPathSegment = messageParams.protocolPath.split('/')[0];
                    // The key-delivery protocol must always use ProtocolPath encryption so
                    // that recipients can decrypt contextKey records with their own derived
                    // key.  Treating it as multi-party would trigger ProtocolContext
                    // encryption, but no one delivers context keys FOR the key-delivery
                    // protocol itself (excluded at line 246 to prevent infinite recursion),
                    // making the records undecryptable by recipients.
                    const isKeyDeliveryProtocol = messageParams.protocol === KeyDeliveryProtocolDefinition.protocol;
                    const isMultiPartyContext = isKeyDeliveryProtocol
                        ? false
                        : isMultiPartyContextFn(protocolDefinition, rootPathSegment);
                    const isRootRecord = !messageParams.parentContextId;
                    // 4. 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.');
                    }
                    // 5. Generate random DEK and IV (IV size depends on content encryption algorithm)
                    const contentEncryptionAlgorithm = ContentEncryptionAlgorithm.A256GCM;
                    const dataEncryptionKey = crypto.getRandomValues(new Uint8Array(32));
                    const dataEncryptionIV = crypto.getRandomValues(new Uint8Array(ivLengthFn(contentEncryptionAlgorithm)));
                    // 6. Build partial EncryptionInput (authenticationTag added after AEAD encryption)
                    let encryptionInput;
                    const buildProtocolPathInput = () => buildEncryptionInputFn(dataEncryptionKey, dataEncryptionIV, ruleSet.$encryption.rootKeyId, ruleSet.$encryption.publicKeyJwk, KeyDerivationScheme.ProtocolPath);
                    if (isCrossDwn && isMultiPartyContext && isRootRecord) {
                        // --- Cross-DWN root record in multi-party context -> Target's ProtocolPath key ---
                        encryptionInput = buildProtocolPathInput();
                    }
                    else if (isCrossDwn && isMultiPartyContext && !isRootRecord) {
                        // --- Cross-DWN non-root record in multi-party context -> derivedPublicKey ---
                        const rootContextId = messageParams.parentContextId.split('/')[0]
                            || messageParams.parentContextId;
                        const derivedPublicKeyInfo = yield this.extractDerivedPublicKey(request.target, messageParams.protocol, rootContextId, request.author);
                        if (derivedPublicKeyInfo) {
                            encryptionInput = buildEncryptionInputFn(dataEncryptionKey, dataEncryptionIV, derivedPublicKeyInfo.rootKeyId, derivedPublicKeyInfo.derivedPublicKey, KeyDerivationScheme.ProtocolContext);
                        }
                        else {
                            // Fallback: no ProtocolContext-encrypted record exists yet (owner hasn't
                            // upgraded the root record). Use ProtocolPath encryption instead.
                            encryptionInput = buildProtocolPathInput();
                        }
                    }
                    else if (isCrossDwn) {
                        // --- Cross-DWN single-party -> Target's ProtocolPath key ---
                        encryptionInput = buildProtocolPathInput();
                    }
                    else if (isMultiPartyContext && !isRootRecord) {
                        // --- Local non-root record in a multi-party context -> Context key ---
                        const rootContextId = messageParams.parentContextId.split('/')[0]
                            || messageParams.parentContextId;
                        let contextKeyInfo = this._contextKeyCache.get(rootContextId);
                        if (!contextKeyInfo) {
                            const { keyId, keyUri } = yield getEncryptionKeyInfoFn(this.agent, request.author);
                            const contextDerivationPath = Records.constructKeyDerivationPathUsingProtocolContextScheme(rootContextId);
                            contextKeyInfo = { keyId, keyUri, contextDerivationPath };
                            this._contextKeyCache.set(rootContextId, contextKeyInfo);
                        }
                        const contextPublicKey = yield this.agent.keyManager.derivePublicKey({
                            keyUri: contextKeyInfo.keyUri,
                            derivationPath: contextKeyInfo.contextDerivationPath,
                        });
                        encryptionInput = buildEncryptionInputFn(dataEncryptionKey, dataEncryptionIV, contextKeyInfo.keyId, contextPublicKey, KeyDerivationScheme.ProtocolContext);
                    }
                    else if (isMultiPartyContext && isRootRecord) {
                        // --- Local root record in multi-party context -> Deferred context encryption ---
                        // contextId = recordId, which is only known after message creation.
                        // Skip encryptionInput here; apply it after create() below.
                        encryptionInput = undefined;
                    }
                    else {
                        // --- Local single-party -> ProtocolPath key (existing logic) ---
                        encryptionInput = buildProtocolPathInput();
                    }
                    // 7. Encrypt data with AEAD and compute CID
                    const { encryptedBytes, dataCid, dataSize, authenticationTag } = yield encryptAndComputeCidFn(plaintextBytes, dataEncryptionKey, dataEncryptionIV, contentEncryptionAlgorithm);
                    // 8. Replace plaintext with encrypted data
                    messageParams.dataCid = dataCid;
                    messageParams.dataSize = dataSize;
                    delete messageParams.data;
                    readableStream = DataStream.fromBytes(encryptedBytes);
                    request.dataStream = undefined;
                    if (encryptionInput) {
                        encryptionInput.authenticationTag = authenticationTag;
                        messageParams.encryptionInput = encryptionInput;
                    }
                    else {
                        // Deferred — store info for post-creation encryption
                        deferredContextEncryption = { dataEncryptionKey, dataEncryptionIV, encryptedBytes, authenticationTag };
                    }
                    // 9. For cross-DWN writes in multi-party contexts, attach the author's
                    //    key-delivery ProtocolPath public key so the DWN owner can encrypt
                    //    context keys back to the author without querying the author's DWN.
                    if (isCrossDwn && isMultiPartyContext) {
                        const { keyId: authorKeyId, keyUri: authorKeyUri } = yield getEncryptionKeyInfoFn(this.agent, request.author);
                        const keyDeliveryDerivationPath = [
                            KeyDerivationScheme.ProtocolPath,
                            KeyDeliveryProtocolDefinition.protocol,
                            'contextKey',
                        ];
                        const authorKeyDeliveryPubKey = yield this.agent.keyManager.derivePublicKey({
                            keyUri: authorKeyUri,
                            derivationPath: keyDeliveryDerivationPath,
                        });
                        messageParams.authorKeyDeliveryPublicKey = {
                            rootKeyId: authorKeyId,
                            publicKeyJwk: authorKeyDeliveryPubKey,
                        };
                    }
                }
            }
            let dwnMessage;
            const dwnMessageConstructor = dwnMessageConstructors[request.messageType];
            // if there is no raw message provided, we need to create the dwn message
            if (!rawMessage) {
                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 permissionGrantId, or a delegatedGrant message
                const signer = request.granteeDid ?
                    yield this.getSigner(request.granteeDid) :
                    yield this.getSigner(request.author);
                dwnMessage = yield dwnMessageConstructor.create(Object.assign(Object.assign({}, request.messageParams), { signer }));
                // Deferred context encryption for root multi-party records (Component 9).
                // Now that the message exists, we know recordId = contextId.
                // Following the SDK two-pass pattern: encryptSymmetricEncryptionKey -> sign.
                if (deferredContextEncryption && isDwnRequest(request, DwnInterface.RecordsWrite)) {
                    const recordsWriteInstance = dwnMessage;
                    const contextId = recordsWriteInstance.message.recordId;
                    const { encryptionInput: contextEncryptionInput, keyId, keyUri, contextDerivationPath } = yield deriveContextEncryptionInputFn(this.agent, request.author, contextId, deferredContextEncryption.dataEncryptionKey, deferredContextEncryption.dataEncryptionIV);
                    const fullContextInput = Object.assign(Object.assign({}, contextEncryptionInput), { authenticationTag: deferredContextEncryption.authenticationTag });
                    yield recordsWriteInstance.encryptSymmetricEncryptionKey(fullContextInput);
                    yield recordsWriteInstance.sign({ signer });
                    // Cache context key info for subsequent writes in this context
                    this._contextKeyCache.set(contextId, { keyId, keyUri, contextDerivationPath });
                }
            }
            else {
                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);
                }
            }
            return {
                message: dwnMessage.message,
                dataStream: readableStream,
            };
        });
    }
    hasGrantParams(params) {
        return params !== undefined &&
            (('permissionGrantId' in params && params.permissionGrantId !== 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 {
                // Otherwise, use the author's DID to determine the signing method.
                try {
                    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 need context key delivery.
     *
     * @param params - Parameters for participant detection
     * @returns Set of DIDs that need context key delivery
     */
    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) {
        return __awaiter(this, void 0, void 0, function* () {
            return getProtocolDefinitionFn(tenantDid, protocolUri, this._dwn, this.getSigner.bind(this), this._protocolDefinitionCache);
        });
    }
    /**
     * 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.agent.did, this.sendDwnRpcRequest.bind(this), this._protocolDefinitionCache);
        });
    }
    /**
     * Extracts the `derivedPublicKey` from an existing ProtocolContext-encrypted
     * record in a context on a remote DWN. This key allows an external author to
     * encrypt new records in the same context without knowing the context private key.
     *
     * @param targetDid      - The DWN owner's DID
     * @param protocolUri    - The protocol URI to search
     * @param rootContextId  - The root context ID
     * @param requesterDid   - The DID of the requester (used for signing the query)
     * @returns The rootKeyId and derivedPublicKey, or undefined if no ProtocolContext
     *          record exists yet
     */
    extractDerivedPublicKey(targetDid, protocolUri, rootContextId, requesterDid) {
        return __awaiter(this, void 0, void 0, function* () {
            return extractDerivedPublicKeyFn(targetDid, protocolUri, rootContextId, requesterDid, this.agent.did, this.getSigner.bind(this), this.sendDwnRpcRequest.bind(this));
        });
    }
    /**
     * 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._contextDerivedKeyCache, this.fetchContextKeyRecord.bind(this));
        });
    }
    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
            });
            const result = yield this._dwn.processMessage(author, 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 dwnMessageWithBlob = { message };
            // If the message is a RecordsWrite, data will be present in the form of a stream
            if (isRecordsWrite(messageEntry) && messageEntry.data) {
                const dataBytes = yield DataStream.toBytes(messageEntry.data);
                dwnMessageWithBlob.data = new Blob([dataBytes], { type: messageEntry.message.descriptor.dataFormat });
            }
            return dwnMessageWithBlob;
        });
    }
    /**
     * Ensures the key delivery protocol is installed on the given tenant's DWN,
     * with `$encryption` keys injected.
     *
     * @param tenantDid - The DID of the DWN owner
     */
    ensureKeyDeliveryProtocol(tenantDid) {
        return __awaiter(this, void 0, void 0, function* () {
            return ensureKeyDeliveryProtocolFn(this.agent, tenantDid, this.processRequest.bind(this), this.getProtocolDefinition.bind(this), this._keyDeliveryProtocolInstalledCache, this._protocolDefinitionCache);
        });
    }
    /**
     * Writes a `contextKey` record to the owner's DWN, delivering an encrypted
     * context key to a participant.
     *
     * @param params - The write parameters
     * @returns The recordId of the written contextKey record
     */
    writeContextKeyRecord(params) {
        return __awaiter(this, void 0, void 0, function* () {
            return writeContextKeyRecordFn(this.agent, params, this.processRequest.bind(this), this.ensureKeyDeliveryProtocol.bind(this), this.eagerSendContextKeyRecord.bind(this));
        });
    }
    /**
     * Eagerly sends a contextKey record to the tenant's remote DWN.
     * This is best-effort — sync guarantees eventual consistency regardless.
     */
    eagerSendContextKeyRecord(tenantDid, contextKeyMessage) {
        return __awaiter(this, void 0, void 0, function* () {
            return eagerSendContextKeyRecordFn(this.agent, tenantDid, contextKeyMessage, this.getDwnMessage.bind(this), this.sendDwnRpcRequest.bind(this));
        });
    }
    /**
     * Fetches and decrypts a `contextKey` record from a DWN, returning the
     * `DerivedPrivateJwk` payload.
     *
     * @param params - The fetch parameters
     * @returns The decrypted `DerivedPrivateJwk`, or `undefined` if no matching record found
     */
    fetchContextKeyRecord(params) {
        return __awaiter(this, void 0, void 0, function* () {
            return fetchContextKeyRecordFn(this.agent, params, this.processRequest.bind(this), this.getSigner.bind(this), this.sendDwnRpcRequest.bind(this));
        });
    }
}
//# sourceMappingURL=dwn-api.js.map