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 { ContentEncryptionAlgorithm, DataStream, KeyDerivationScheme, Message, Protocols, Records, } from '@enbox/dwn-sdk-js';
import { KeyDeliveryProtocolDefinition } from './store-data-protocols.js';
import { buildEncryptionInput, encryptAndComputeCid, getEncryptionKeyDeriver, getKeyDecrypter, ivLength } from './dwn-encryption.js';
import { DwnInterface, dwnMessageConstructors } from './types/dwn.js';
/**
 * Ensures the key delivery protocol is installed on the given tenant's DWN,
 * with `$encryption` keys injected. Uses the same lazy initialization pattern
 * as `DwnDataStore.initialize()`.
 *
 * @param agent - The platform agent
 * @param tenantDid - The DID of the DWN owner
 * @param processRequest - The agent's processRequest method (bound)
 * @param getProtocolDefinition - Function to get a protocol definition
 * @param installedCache - Cache for installation status
 */
export function ensureKeyDeliveryProtocol(agent, tenantDid, processRequest, getProtocolDefinition, installedCache, protocolDefinitionCache) {
    return __awaiter(this, void 0, void 0, function* () {
        if (installedCache.get(tenantDid)) {
            return;
        }
        const protocolUri = KeyDeliveryProtocolDefinition.protocol;
        const existing = yield getProtocolDefinition(tenantDid, protocolUri);
        if (!existing) {
            // Derive and inject $encryption keys for each type path
            const keyDeriver = yield getEncryptionKeyDeriver(agent, tenantDid);
            const definitionWithKeys = yield Protocols.deriveAndInjectPublicEncryptionKeys(KeyDeliveryProtocolDefinition, keyDeriver);
            const { reply: { status } } = yield processRequest({
                author: tenantDid,
                target: tenantDid,
                messageType: DwnInterface.ProtocolsConfigure,
                messageParams: { definition: definitionWithKeys },
            });
            if (status.code !== 202) {
                throw new Error(`AgentDwnApi: Failed to install key delivery protocol: ${status.code} - ${status.detail}`);
            }
            // Invalidate protocol definition cache so subsequent reads pick up the new definition
            protocolDefinitionCache.delete(`${tenantDid}~${protocolUri}`);
        }
        installedCache.set(tenantDid, true);
    });
}
/**
 * Writes a `contextKey` record to the owner's DWN, delivering an encrypted
 * context key to a participant.
 *
 * The payload is encrypted to the **recipient's** ProtocolPath-derived public
 * key on the key-delivery protocol, so only the recipient can decrypt it.
 *
 * @param agent - The platform agent
 * @param params - The write parameters
 * @param processRequest - The agent's processRequest method (bound)
 * @param ensureProtocol - Function to ensure key delivery protocol is installed
 * @param eagerSend - Function to eagerly send the record to the remote DWN
 * @returns The recordId of the written contextKey record
 */
export function writeContextKeyRecord(agent, params, processRequest, ensureProtocol, eagerSend) {
    return __awaiter(this, void 0, void 0, function* () {
        const { tenantDid, recipientDid, contextKeyData, sourceProtocol, sourceContextId, recipientKeyDeliveryPublicKey } = params;
        // Ensure the key delivery protocol is installed on the owner's DWN
        yield ensureProtocol(tenantDid);
        const protocolUri = KeyDeliveryProtocolDefinition.protocol;
        // Serialize the payload to JSON bytes
        const plaintextBytes = new TextEncoder().encode(JSON.stringify(contextKeyData));
        // Common contextKey record parameters
        const contextKeyParams = {
            protocol: protocolUri,
            protocolPath: 'contextKey',
            dataFormat: 'application/json',
            recipient: recipientDid,
            tags: { protocol: sourceProtocol, contextId: sourceContextId },
        };
        let message;
        let status;
        if (recipientKeyDeliveryPublicKey) {
            // --- Encrypt to the recipient's ProtocolPath key (cross-DWN delivery) ---
            // Manually build encryption input targeting the recipient's key so the
            // record is decryptable only by the recipient.
            const algorithm = ContentEncryptionAlgorithm.A256GCM;
            const dataEncryptionKey = crypto.getRandomValues(new Uint8Array(32));
            const dataEncryptionIV = crypto.getRandomValues(new Uint8Array(ivLength(algorithm)));
            const { encryptedBytes, dataCid, dataSize, authenticationTag } = yield encryptAndComputeCid(plaintextBytes, dataEncryptionKey, dataEncryptionIV, algorithm);
            const encryptionInput = Object.assign(Object.assign({}, buildEncryptionInput(dataEncryptionKey, dataEncryptionIV, recipientKeyDeliveryPublicKey.rootKeyId, recipientKeyDeliveryPublicKey.publicKeyJwk, KeyDerivationScheme.ProtocolPath)), { authenticationTag });
            ({ message, reply: { status } } = yield processRequest({
                author: tenantDid,
                target: tenantDid,
                messageType: DwnInterface.RecordsWrite,
                messageParams: Object.assign(Object.assign({}, contextKeyParams), { dataCid, dataSize, encryptionInput }),
                dataStream: new Blob([encryptedBytes]),
            }));
        }
        else {
            // --- Fallback: encrypt to the owner's key (local self-delivery) ---
            // When no recipient key is provided, use the generic processRequest
            // encryption path which encrypts to the DWN owner's ProtocolPath key.
            ({ message, reply: { status } } = yield processRequest({
                author: tenantDid,
                target: tenantDid,
                messageType: DwnInterface.RecordsWrite,
                messageParams: contextKeyParams,
                dataStream: new Blob([plaintextBytes], { type: 'application/json' }),
                encryption: true,
            }));
        }
        if (!(message && status.code === 202)) {
            throw new Error(`AgentDwnApi: Failed to write contextKey record for ${recipientDid}: ${status.code} - ${status.detail}`);
        }
        // Eagerly send the contextKey record to the tenant's remote DWN so that
        // participants can fetch it immediately without waiting for sync.
        // This is fire-and-forget — sync will guarantee eventual consistency.
        eagerSend(tenantDid, message).catch((err) => {
            console.warn(`AgentDwnApi: Eager send of contextKey record '${message.recordId}' ` +
                `to remote DWN failed: ${err.message}. Sync will deliver it later.`);
        });
        return message.recordId;
    });
}
/**
 * Eagerly sends a contextKey record to the tenant's remote DWN.
 * This is best-effort — sync guarantees eventual consistency regardless.
 *
 * @param agent - The platform agent
 * @param tenantDid - The DWN owner's DID
 * @param contextKeyMessage - The context key message to send
 * @param getDwnMessage - Function to read a full message from local DWN
 * @param sendDwnRpcRequest - Function to send a DWN RPC request
 * @param getDwnEndpointUrlsForTarget - Function to resolve DWN endpoint URLs (with local discovery)
 */
export function eagerSendContextKeyRecord(agent, tenantDid, contextKeyMessage, getDwnMessage, sendDwnRpcRequest, getDwnEndpointUrlsForTarget) {
    return __awaiter(this, void 0, void 0, function* () {
        let dwnEndpointUrls;
        try {
            dwnEndpointUrls = yield getDwnEndpointUrlsForTarget(tenantDid);
        }
        catch (_a) {
            // DID resolution or endpoint lookup failed — not fatal, sync will handle it.
            return;
        }
        if (dwnEndpointUrls.length === 0) {
            return;
        }
        // Read the full message (including data blob) from the local DWN
        const { data } = yield getDwnMessage({
            author: tenantDid,
            messageType: DwnInterface.RecordsWrite,
            messageCid: yield Message.getCid(contextKeyMessage),
        });
        yield sendDwnRpcRequest({
            targetDid: tenantDid,
            dwnEndpointUrls,
            message: contextKeyMessage,
            data,
        });
    });
}
/**
 * Fetches and decrypts a `contextKey` record from a DWN, returning the
 * `DerivedPrivateJwk` payload.
 *
 * Supports both local reads (tenant queries own DWN) and remote reads
 * (participant queries the context owner's DWN).
 *
 * @param agent - The platform agent
 * @param params - The fetch parameters
 * @param processRequest - The agent's processRequest method (bound)
 * @param getSigner - Function to get a signer for a DID
 * @param sendDwnRpcRequest - Function to send a DWN RPC request
 * @param getDwnEndpointUrlsForTarget - Function to resolve DWN endpoint URLs (with local discovery)
 * @returns The decrypted `DerivedPrivateJwk`, or `undefined` if no matching record found
 */
export function fetchContextKeyRecord(agent, params, processRequest, getSigner, sendDwnRpcRequest, getDwnEndpointUrlsForTarget) {
    return __awaiter(this, void 0, void 0, function* () {
        var _a, _b, _c, _d, _e;
        const { ownerDid, requesterDid, sourceProtocol, sourceContextId } = params;
        const protocolUri = KeyDeliveryProtocolDefinition.protocol;
        const isLocal = ownerDid === requesterDid;
        // Shared query filter for both local and remote paths
        const contextKeyFilter = {
            protocol: protocolUri,
            protocolPath: 'contextKey',
            recipient: requesterDid,
            tags: { protocol: sourceProtocol, contextId: sourceContextId },
        };
        /** Parse decrypted bytes into a DerivedPrivateJwk. */
        const parsePayload = (bytes) => JSON.parse(new TextDecoder().decode(bytes));
        if (isLocal) {
            // Local query: owner queries their own DWN
            const { reply } = yield processRequest({
                author: requesterDid,
                target: ownerDid,
                messageType: DwnInterface.RecordsQuery,
                messageParams: { filter: contextKeyFilter },
            });
            if (reply.status.code !== 200 || !((_a = reply.entries) === null || _a === void 0 ? void 0 : _a.length)) {
                return undefined;
            }
            // Read the full record to get the data (auto-decrypted by processRequest)
            const recordId = reply.entries[0].recordId;
            const { reply: readReply } = yield processRequest({
                author: requesterDid,
                target: ownerDid,
                messageType: DwnInterface.RecordsRead,
                messageParams: { filter: { recordId } },
                encryption: true,
            });
            const readResult = readReply;
            if (!((_b = readResult.entry) === null || _b === void 0 ? void 0 : _b.data)) {
                return undefined;
            }
            return parsePayload(yield DataStream.toBytes(readResult.entry.data));
        }
        else {
            // Remote query: participant queries the context owner's DWN
            const signer = yield getSigner(requesterDid);
            const dwnEndpointUrls = yield getDwnEndpointUrlsForTarget(ownerDid);
            const recordsQuery = yield dwnMessageConstructors[DwnInterface.RecordsQuery].create({
                signer,
                filter: contextKeyFilter,
            });
            const queryReply = yield sendDwnRpcRequest({
                targetDid: ownerDid,
                dwnEndpointUrls,
                message: recordsQuery.message,
            });
            if (queryReply.status.code !== 200 || !((_c = queryReply.entries) === null || _c === void 0 ? void 0 : _c.length)) {
                return undefined;
            }
            // Read the full record remotely
            const recordId = queryReply.entries[0].recordId;
            const recordsRead = yield dwnMessageConstructors[DwnInterface.RecordsRead].create({
                signer,
                filter: { recordId },
            });
            const readReply = yield sendDwnRpcRequest({
                targetDid: ownerDid,
                dwnEndpointUrls,
                message: recordsRead.message,
            });
            if (!((_d = readReply.entry) === null || _d === void 0 ? void 0 : _d.data) || !((_e = readReply.entry) === null || _e === void 0 ? void 0 : _e.recordsWrite)) {
                return undefined;
            }
            // Decrypt the contextKey payload using the requester's key-delivery protocol path key
            const keyDecrypter = yield getKeyDecrypter(agent, requesterDid);
            const decryptedStream = yield Records.decrypt(readReply.entry.recordsWrite, keyDecrypter, readReply.entry.data);
            return parsePayload(yield DataStream.toBytes(decryptedStream));
        }
    });
}
//# sourceMappingURL=dwn-key-delivery.js.map