/**
 * NOTE: Added reference types here to avoid a `pnpm` bug during build.
 * https://github.com/enboxorg/enbox/pull/507
 */
/// <reference types="@enbox/dwn-sdk-js" />
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
import { AgentPermissionsApi, } from '@enbox/agent';
import { DwnInterface, getRecordAuthor } from '@enbox/agent';
import { dataToBlob } from './utils.js';
import { LiveQuery } from './live-query.js';
import { PermissionGrant } from './permission-grant.js';
import { PermissionRequest } from './permission-request.js';
import { Protocol } from './protocol.js';
import { Record } from './record.js';
/**
 * Interface to interact with DWN Records and Protocols
 */
export class DwnApi {
    get connectedDid() { return this._connectedDid; }
    /** @internal — used by tests to reset state between runs. */
    set connectedDid(did) { this._connectedDid = did; }
    constructor(options) {
        this.agent = options.agent;
        this._connectedDid = options.connectedDid;
        this.delegateDid = options.delegateDid;
        this.permissionsApi = new AgentPermissionsApi({ agent: this.agent });
    }
    /** Whether this DWN API instance is operating as a delegate. */
    get isDelegate() {
        return this.delegateDid !== undefined;
    }
    /**
     * API to interact with Grants
     *
     * NOTE: This is an EXPERIMENTAL API that will change behavior.
     *
     * Currently only supports issuing requests, grants, revokes and queries on behalf without permissions or impersonation.
     * If the agent is connected to a delegateDid, the delegateDid will be used to sign/author the underlying records.
     * If the agent is not connected to a delegateDid, the connectedDid will be used to sign/author the underlying records.
     *
     * @beta
     */
    get permissions() {
        return {
            /**
             * Request permission for a specific scope.
             */
            request: (request) => __awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                const { message } = yield this.permissionsApi.createRequest(Object.assign(Object.assign({}, request), { author: (_a = this.delegateDid) !== null && _a !== void 0 ? _a : this.connectedDid }));
                const requestParams = {
                    connectedDid: (_b = this.delegateDid) !== null && _b !== void 0 ? _b : this.connectedDid,
                    agent: this.agent,
                    message,
                };
                return PermissionRequest.parse(requestParams);
            }),
            /**
             * Grant permission for a specific scope to a grantee DID.
             */
            grant: (request) => __awaiter(this, void 0, void 0, function* () {
                var _a, _b;
                const { message } = yield this.permissionsApi.createGrant(Object.assign(Object.assign({}, request), { author: (_a = this.delegateDid) !== null && _a !== void 0 ? _a : this.connectedDid }));
                const grantParams = {
                    connectedDid: (_b = this.delegateDid) !== null && _b !== void 0 ? _b : this.connectedDid,
                    agent: this.agent,
                    message,
                };
                return PermissionGrant.parse(grantParams);
            }),
            /**
             * Query permission requests. You can filter by protocol and specify if you want to query a remote DWN.
             */
            queryRequests: (...args_1) => __awaiter(this, [...args_1], void 0, function* (request = {}) {
                var _a, _b, _c;
                const { from } = request, params = __rest(request, ["from"]);
                const fetchResponse = yield this.permissionsApi.fetchRequests(Object.assign(Object.assign({}, params), { author: (_a = this.delegateDid) !== null && _a !== void 0 ? _a : this.connectedDid, target: (_b = from !== null && from !== void 0 ? from : this.delegateDid) !== null && _b !== void 0 ? _b : this.connectedDid, remote: from !== undefined }));
                const requests = [];
                for (const permission of fetchResponse) {
                    const requestParams = {
                        connectedDid: (_c = this.delegateDid) !== null && _c !== void 0 ? _c : this.connectedDid,
                        agent: this.agent,
                        message: permission.message,
                    };
                    requests.push(PermissionRequest.parse(requestParams));
                }
                return requests;
            }),
            /**
             * Query permission grants. You can filter by grantee, grantor, protocol and specify if you want to query a remote DWN.
             */
            queryGrants: (...args_1) => __awaiter(this, [...args_1], void 0, function* (request = {}) {
                var _a, _b, _c;
                const { from } = request, params = __rest(request, ["from"]);
                const remote = from !== undefined;
                const author = (_a = this.delegateDid) !== null && _a !== void 0 ? _a : this.connectedDid;
                const target = (_b = from !== null && from !== void 0 ? from : this.delegateDid) !== null && _b !== void 0 ? _b : this.connectedDid;
                const fetchResponse = yield this.permissionsApi.fetchGrants(Object.assign(Object.assign({}, params), { author,
                    target,
                    remote }));
                const grants = [];
                for (const permission of fetchResponse) {
                    const grantParams = {
                        connectedDid: (_c = this.delegateDid) !== null && _c !== void 0 ? _c : this.connectedDid,
                        agent: this.agent,
                        message: permission.message,
                    };
                    grants.push(PermissionGrant.parse(grantParams));
                }
                return grants;
            })
        };
    }
    /**
     * API to interact with DWN protocols (e.g., `dwn.protocols.configure()`).
     */
    get protocols() {
        return {
            /**
             * Configure method, used to setup a new protocol (or update) with the passed definitions
             */
            configure: (request) => __awaiter(this, void 0, void 0, function* () {
                const { encryption } = request, messageParams = __rest(request, ["encryption"]);
                const agentRequest = {
                    author: this.connectedDid,
                    messageParams,
                    messageType: DwnInterface.ProtocolsConfigure,
                    target: this.connectedDid,
                    encryption,
                };
                if (this.delegateDid) {
                    const { message: delegatedGrant } = yield this.permissionsApi.getPermissionForRequest({
                        connectedDid: this.connectedDid,
                        delegateDid: this.delegateDid,
                        protocol: messageParams.definition.protocol,
                        delegate: true,
                        cached: true,
                        messageType: agentRequest.messageType
                    });
                    agentRequest.messageParams = Object.assign(Object.assign({}, agentRequest.messageParams), { delegatedGrant });
                    agentRequest.granteeDid = this.delegateDid;
                }
                const agentResponse = yield this.agent.processDwnRequest(agentRequest);
                const { message, messageCid, reply: { status } } = agentResponse;
                const response = { status };
                if (status.code < 300) {
                    const metadata = { author: this.connectedDid, messageCid };
                    response.protocol = new Protocol(this.agent, message, metadata);
                }
                return response;
            }),
            /**
             * Query the available protocols
             */
            query: (request) => __awaiter(this, void 0, void 0, function* () {
                const { from } = request, messageParams = __rest(request, ["from"]);
                const agentRequest = {
                    author: this.connectedDid,
                    messageParams,
                    messageType: DwnInterface.ProtocolsQuery,
                    target: from || this.connectedDid,
                };
                if (this.delegateDid) {
                    // We attempt to get a grant within a try catch, if there is no grant we will still sign the query with the delegate DID's key
                    // If the protocol is public, the query should be successful. This allows the app to query for public protocols without having a grant.
                    try {
                        const { grant: { id: permissionGrantId } } = yield this.permissionsApi.getPermissionForRequest({
                            connectedDid: this.connectedDid,
                            delegateDid: this.delegateDid,
                            protocol: messageParams.filter.protocol,
                            cached: true,
                            messageType: agentRequest.messageType
                        });
                        agentRequest.messageParams = Object.assign(Object.assign({}, agentRequest.messageParams), { permissionGrantId });
                        agentRequest.granteeDid = this.delegateDid;
                    }
                    catch (_a) {
                        // if a grant is not found, we should author the request as the delegated DID to get public protocols
                        agentRequest.author = this.delegateDid;
                    }
                }
                let agentResponse;
                if (from) {
                    agentResponse = yield this.agent.sendDwnRequest(agentRequest);
                }
                else {
                    agentResponse = yield this.agent.processDwnRequest(agentRequest);
                }
                const reply = agentResponse.reply;
                const { entries = [], status } = reply;
                const protocols = entries.map((entry) => {
                    const metadata = { author: this.connectedDid };
                    return new Protocol(this.agent, entry, metadata);
                });
                return { protocols, status };
            })
        };
    }
    /**
     * API to interact with DWN records (e.g., `dwn.records.write()`).
     */
    get records() {
        return {
            /**
             * Delete a record
             */
            delete: (request) => __awaiter(this, void 0, void 0, function* () {
                const { from, protocol } = request, messageParams = __rest(request, ["from", "protocol"]);
                const agentRequest = {
                    /**
                     * The `author` is the DID that will sign the message and must be the DID the Enbox app is
                     * connected with and is authorized to access the signing private key of.
                     */
                    author: this.connectedDid,
                    messageParams,
                    messageType: DwnInterface.RecordsDelete,
                    /**
                     * The `target` is the DID of the DWN tenant under which the delete will be executed.
                     * If `from` is provided, the delete operation will be executed on a remote DWN.
                     * Otherwise, the record will be deleted on the local DWN.
                     */
                    target: from || this.connectedDid,
                };
                if (this.delegateDid) {
                    const { message: delegatedGrant } = yield this.permissionsApi.getPermissionForRequest({
                        connectedDid: this.connectedDid,
                        delegateDid: this.delegateDid,
                        protocol,
                        delegate: true,
                        cached: true,
                        messageType: agentRequest.messageType
                    });
                    agentRequest.messageParams = Object.assign(Object.assign({}, agentRequest.messageParams), { delegatedGrant });
                    agentRequest.granteeDid = this.delegateDid;
                }
                let agentResponse;
                if (from) {
                    agentResponse = yield this.agent.sendDwnRequest(agentRequest);
                }
                else {
                    agentResponse = yield this.agent.processDwnRequest(agentRequest);
                }
                const { reply: { status } } = agentResponse;
                return { status };
            }),
            /**
             * Query a single or multiple records based on the given filter
             */
            query: (request) => __awaiter(this, void 0, void 0, function* () {
                var _a;
                const { from, encryption } = request, messageParams = __rest(request, ["from", "encryption"]);
                const agentRequest = {
                    /**
                     * The `author` is the DID that will sign the message and must be the DID the Enbox app is
                     * connected with and is authorized to access the signing private key of.
                     */
                    author: this.connectedDid,
                    messageParams,
                    messageType: DwnInterface.RecordsQuery,
                    /**
                     * The `target` is the DID of the DWN tenant under which the query will be executed.
                     * If `from` is provided, the query operation will be executed on a remote DWN.
                     * Otherwise, the local DWN will be queried.
                     */
                    target: from || this.connectedDid,
                    encryption,
                };
                if (this.delegateDid) {
                    // if we don't find a delegated grant, we will attempt to query signing as the delegated DID
                    // This is to allow the API caller to query public records without needing to impersonate the delegate.
                    //
                    // NOTE: For anonymous/public queries without explicit permissions, callers can use `DwnReaderApi` via `Enbox.anonymous()`.
                    // See: https://github.com/enboxorg/enbox/issues/898
                    try {
                        const { message: delegatedGrant } = yield this.permissionsApi.getPermissionForRequest({
                            connectedDid: this.connectedDid,
                            delegateDid: this.delegateDid,
                            protocol: (_a = messageParams.filter) === null || _a === void 0 ? void 0 : _a.protocol,
                            delegate: true,
                            cached: true,
                            messageType: agentRequest.messageType
                        });
                        agentRequest.messageParams = Object.assign(Object.assign({}, agentRequest.messageParams), { delegatedGrant });
                        agentRequest.granteeDid = this.delegateDid;
                    }
                    catch (_b) {
                        // if a grant is not found, we should author the request as the delegated DID to get public records
                        agentRequest.author = this.delegateDid;
                    }
                }
                let agentResponse;
                if (from) {
                    agentResponse = yield this.agent.sendDwnRequest(agentRequest);
                }
                else {
                    agentResponse = yield this.agent.processDwnRequest(agentRequest);
                }
                const reply = agentResponse.reply;
                const { entries = [], status, cursor } = reply;
                const records = entries.map((entry) => {
                    const recordOptions = Object.assign({ 
                        /**
                         * Extract the `author` DID from the record entry since records may be signed by the
                         * tenant owner or any other entity.
                         */
                        author: getRecordAuthor(entry), 
                        /**
                         * Set the `connectedDid` to currently connected DID so that subsequent calls to
                         * {@link Record} instance methods, such as `record.update()` are executed on the
                         * local DWN even if the record was returned by a query of a remote DWN.
                         */
                        connectedDid: this.connectedDid, 
                        /**
                         * If the record was returned by a query of a remote DWN, set the `remoteOrigin` to
                         * the DID of the DWN that returned the record. The `remoteOrigin` property will be used
                         * to determine which DWN to send subsequent read requests to in the event the data
                         * payload exceeds the threshold for being returned with queries.
                         */
                        remoteOrigin: from, delegateDid: this.delegateDid, protocolRole: agentRequest.messageParams.protocolRole }, entry);
                    const record = new Record(this.agent, recordOptions, this.permissionsApi);
                    return record;
                });
                return { records, status, cursor };
            }),
            /**
             * Read a single record based on the given filter
             */
            read: (request) => __awaiter(this, void 0, void 0, function* () {
                const { from, protocol, encryption } = request, messageParams = __rest(request, ["from", "protocol", "encryption"]);
                const agentRequest = {
                    /**
                     * The `author` is the DID that will sign the message and must be the DID the Enbox app is
                     * connected with and is authorized to access the signing private key of.
                     */
                    author: this.connectedDid,
                    messageParams,
                    messageType: DwnInterface.RecordsRead,
                    /**
                     * The `target` is the DID of the DWN tenant under which the read will be executed.
                     * If `from` is provided, the read operation will be executed on a remote DWN.
                     * Otherwise, the read will occur on the local DWN.
                     */
                    target: from || this.connectedDid,
                    encryption,
                };
                if (this.delegateDid) {
                    // if we don't find a delegated grant, we will attempt to read signing as the delegated DID
                    // This is to allow the API caller to read public records without needing to impersonate the delegate.
                    //
                    // NOTE: For anonymous/public reads without explicit permissions, callers can use `DwnReaderApi` via `Enbox.anonymous()`.
                    // See: https://github.com/enboxorg/enbox/issues/898
                    try {
                        const { message: delegatedGrant } = yield this.permissionsApi.getPermissionForRequest({
                            connectedDid: this.connectedDid,
                            delegateDid: this.delegateDid,
                            protocol,
                            delegate: true,
                            cached: true,
                            messageType: agentRequest.messageType
                        });
                        agentRequest.messageParams = Object.assign(Object.assign({}, agentRequest.messageParams), { delegatedGrant });
                        agentRequest.granteeDid = this.delegateDid;
                    }
                    catch (_a) {
                        // if a grant is not found, we should author the request as the delegated DID to get public records
                        agentRequest.author = this.delegateDid;
                    }
                }
                let agentResponse;
                if (from) {
                    agentResponse = yield this.agent.sendDwnRequest(agentRequest);
                }
                else {
                    agentResponse = yield this.agent.processDwnRequest(agentRequest);
                }
                const { reply: { entry, status } } = agentResponse;
                let record;
                if (200 <= status.code && status.code <= 299) {
                    const recordOptions = Object.assign({ 
                        /**
                         * Extract the `author` DID from the record since records may be signed by the
                         * tenant owner or any other entity.
                         */
                        author: getRecordAuthor(entry.recordsWrite), 
                        /**
                         * Set the `connectedDid` to currently connected DID so that subsequent calls to
                         * {@link Record} instance methods, such as `record.update()` are executed on the
                         * local DWN even if the record was read from a remote DWN.
                         */
                        connectedDid: this.connectedDid, 
                        /**
                         * If the record was returned by reading from a remote DWN, set the `remoteOrigin` to
                         * the DID of the DWN that returned the record. The `remoteOrigin` property will be used
                         * to determine which DWN to send subsequent read requests to in the event the data
                         * payload must be read again (e.g., if the data stream is consumed).
                         */
                        remoteOrigin: from, delegateDid: this.delegateDid, data: entry.data, initialWrite: entry.initialWrite }, entry.recordsWrite);
                    record = new Record(this.agent, recordOptions, this.permissionsApi);
                }
                return { record, status };
            }),
            /**
             * Subscribe to records matching the given filter.
             *
             * Returns a {@link LiveQuery} that atomically provides an initial snapshot
             * of matching records and a real-time stream of deduplicated, semantically-
             * typed change events (`create`, `update`, `delete`).
             */
            subscribe: (request) => __awaiter(this, void 0, void 0, function* () {
                var _a;
                const { from } = request, messageParams = __rest(request, ["from"]);
                // Build a DWN-level subscription handler that wraps raw RecordEvents
                // into Record objects and feeds them into the LiveQuery.
                let liveQuery;
                const remoteOrigin = from;
                const protocolRole = messageParams.protocolRole;
                const subscriptionHandler = (msg) => {
                    if (msg.type === 'eose') {
                        liveQuery === null || liveQuery === void 0 ? void 0 : liveQuery.handleLifecycleEvent('eose');
                        return;
                    }
                    if (msg.type === 'error') {
                        liveQuery === null || liveQuery === void 0 ? void 0 : liveQuery.handleError({
                            code: msg.error.code,
                            detail: msg.error.detail,
                            cursor: msg.cursor,
                        });
                        Promise.resolve(liveQuery === null || liveQuery === void 0 ? void 0 : liveQuery.close()).catch(() => { });
                        return;
                    }
                    if (msg.type === 'disconnected') {
                        liveQuery === null || liveQuery === void 0 ? void 0 : liveQuery.handleLifecycleEvent('disconnected');
                        return;
                    }
                    if (msg.type === 'reconnected') {
                        liveQuery === null || liveQuery === void 0 ? void 0 : liveQuery.handleLifecycleEvent('reconnected');
                        return;
                    }
                    if (msg.type === 'reconnecting') {
                        liveQuery === null || liveQuery === void 0 ? void 0 : liveQuery.handleLifecycleEvent('reconnecting', { attempt: msg.attempt });
                        return;
                    }
                    const { message, initialWrite } = msg.event;
                    const record = new Record(this.agent, Object.assign(Object.assign({}, message), { author: getRecordAuthor(message), connectedDid: this.connectedDid, remoteOrigin, initialWrite: initialWrite, protocolRole, delegateDid: this.delegateDid }), this.permissionsApi);
                    liveQuery === null || liveQuery === void 0 ? void 0 : liveQuery.handleEvent(record);
                };
                const agentRequest = {
                    author: this.connectedDid,
                    messageParams,
                    messageType: DwnInterface.RecordsSubscribe,
                    target: from || this.connectedDid,
                    subscriptionHandler,
                };
                if (this.delegateDid) {
                    // if we don't find a delegated grant, we will attempt to subscribe signing as the delegated DID
                    // This is to allow the API caller to subscribe to public records without needing to impersonate the delegate.
                    //
                    // NOTE: For anonymous/public subscriptions without explicit permissions, callers can use `DwnReaderApi` via `Enbox.anonymous()`.
                    // See: https://github.com/enboxorg/enbox/issues/898
                    try {
                        const { message: delegatedGrant } = yield this.permissionsApi.getPermissionForRequest({
                            connectedDid: this.connectedDid,
                            delegateDid: this.delegateDid,
                            protocol: (_a = messageParams.filter) === null || _a === void 0 ? void 0 : _a.protocol,
                            delegate: true,
                            cached: true,
                            messageType: agentRequest.messageType
                        });
                        agentRequest.messageParams = Object.assign(Object.assign({}, agentRequest.messageParams), { delegatedGrant });
                        agentRequest.granteeDid = this.delegateDid;
                    }
                    catch (_b) {
                        // if a grant is not found, we should author the request as the delegated DID to get public records
                        agentRequest.author = this.delegateDid;
                    }
                }
                let agentResponse;
                if (from) {
                    agentResponse = yield this.agent.sendDwnRequest(agentRequest);
                }
                else {
                    agentResponse = yield this.agent.processDwnRequest(agentRequest);
                }
                const reply = agentResponse.reply;
                const { status, subscription, entries = [], cursor } = reply;
                if (subscription) {
                    liveQuery = new LiveQuery({
                        agent: this.agent,
                        connectedDid: this.connectedDid,
                        delegateDid: this.delegateDid,
                        protocolRole,
                        remoteOrigin,
                        permissionsApi: this.permissionsApi,
                        initialEntries: entries,
                        cursor,
                        subscription,
                    });
                }
                return { status, liveQuery };
            }),
            /**
             * Writes a record to the DWN
             *
             * As a convenience, the Record instance returned will cache a copy of the data.  This is done
             * to maintain consistency with other DWN methods, like RecordsQuery, that include relatively
             * small data payloads when returning RecordsWrite message properties. Regardless of data
             * size, methods such as `record.data.stream()` will return the data when called even if it
             * requires fetching from the DWN datastore.
             */
            write: (request) => __awaiter(this, void 0, void 0, function* () {
                const { data, store, encryption } = request, restParams = __rest(request, ["data", "store", "encryption"]);
                const { dataBlob, dataFormat } = dataToBlob(data, restParams.dataFormat);
                const messageParams = Object.assign(Object.assign({}, restParams), { dataFormat });
                const dwnRequestParams = {
                    store,
                    messageType: DwnInterface.RecordsWrite,
                    messageParams,
                    author: this.connectedDid,
                    target: this.connectedDid,
                    dataStream: dataBlob,
                    encryption,
                };
                // if impersonation is enabled, fetch the delegated grant to use with the write operation
                if (this.delegateDid) {
                    const { message: delegatedGrant } = yield this.permissionsApi.getPermissionForRequest({
                        connectedDid: this.connectedDid,
                        delegateDid: this.delegateDid,
                        protocol: messageParams.protocol,
                        delegate: true,
                        cached: true,
                        messageType: dwnRequestParams.messageType
                    });
                    dwnRequestParams.messageParams = Object.assign(Object.assign({}, dwnRequestParams.messageParams), { delegatedGrant });
                    dwnRequestParams.granteeDid = this.delegateDid;
                }
                const agentResponse = yield this.agent.processDwnRequest(dwnRequestParams);
                const { message: responseMessage, reply: { status } } = agentResponse;
                let record;
                if (200 <= status.code && status.code <= 299) {
                    const recordOptions = Object.assign({ 
                        /**
                         * Assume the author is the connected DID since the record was just written to the
                         * local DWN.
                         */
                        author: this.connectedDid, 
                        /**
                         * Set the `connectedDid` to currently connected DID so that subsequent calls to
                         * {@link Record} instance methods, such as `record.update()` are executed on the
                         * local DWN.
                         */
                        connectedDid: this.connectedDid, encodedData: dataBlob, delegateDid: this.delegateDid }, responseMessage);
                    record = new Record(this.agent, recordOptions, this.permissionsApi);
                }
                return { record, status };
            }),
        };
    }
}
//# sourceMappingURL=dwn-api.js.map