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 { BearerDid, Did, DidDht, UniversalResolver } from '@enbox/dids';
import { AgentDidResolverCache } from './agent-did-resolver-cache.js';
import { canonicalize } from '@enbox/crypto';
import { InMemoryDidStore } from './store-did.js';
export var DidInterface;
(function (DidInterface) {
    DidInterface["Create"] = "Create";
    // Deactivate = 'Deactivate',
    DidInterface["Resolve"] = "Resolve";
    // Update  = 'Update'
})(DidInterface || (DidInterface = {}));
export function isDidRequest(didRequest, messageType) {
    return didRequest.messageType === messageType;
}
/**
 * This API is used to manage and interact with DIDs within the Web5 Agent framework.
 *
 * If a DWN Data Store is used, the DID information is stored under DID's own tenant by default.
 * If a tenant property is passed, that tenant will be used to store the DID information.
 */
export class AgentDidApi extends UniversalResolver {
    constructor({ agent, didMethods, resolverCache, store }) {
        if (!didMethods) {
            throw new TypeError(`AgentDidApi: Required parameter missing: 'didMethods'`);
        }
        // Initialize the DID resolver with the given DID methods and resolver cache, or use a default
        // AgentDidResolverCache if none is provided.
        super({
            didResolvers: didMethods,
            cache: resolverCache !== null && resolverCache !== void 0 ? resolverCache : new AgentDidResolverCache({ agent, location: 'DATA/AGENT/DID_CACHE' })
        });
        this._didMethods = new Map();
        this._agent = agent;
        // If `store` is not given, use an in-memory store by default.
        this._store = store !== null && store !== void 0 ? store : new InMemoryDidStore();
        for (const didMethod of didMethods) {
            this._didMethods.set(didMethod.methodName, didMethod);
        }
    }
    /**
     * 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('AgentDidApi: Unable to determine agent execution context.');
        }
        return this._agent;
    }
    set agent(agent) {
        this._agent = agent;
        // AgentDidResolverCache should set the agent if it is the type of cache being used
        if ('agent' in this.cache) {
            this.cache.agent = agent;
        }
    }
    create(_a) {
        return __awaiter(this, arguments, void 0, function* ({ method, tenant, options, store }) {
            // Get the DID method implementation, which also verifies the method is supported.
            const didMethod = this.getMethod(method);
            // Create the DID and store the generated keys in the Agent's key manager.
            const bearerDid = yield didMethod.create({ keyManager: this.agent.keyManager, options });
            // pre-populate the resolution cache with the document and metadata
            yield this.cache.set(bearerDid.uri, { didDocument: bearerDid.document, didResolutionMetadata: {}, didDocumentMetadata: bearerDid.metadata });
            // Persist the DID to the store, by default, unless the `store` option is set to false.
            if (store !== null && store !== void 0 ? store : true) {
                // Data stored in the Agent's DID store must be in PortableDid format.
                const { uri, document, metadata } = bearerDid;
                const portableDid = { uri, document, metadata };
                // Unless an existing `tenant` is specified, a record that includes the DID's URI, document,
                // and metadata will be stored under a new tenant controlled by the newly created DID.
                yield this._store.set({
                    id: portableDid.uri,
                    data: portableDid,
                    agent: this.agent,
                    tenant: tenant !== null && tenant !== void 0 ? tenant : portableDid.uri,
                    preventDuplicates: false,
                    useCache: true
                });
            }
            return bearerDid;
        });
    }
    export(_a) {
        return __awaiter(this, arguments, void 0, function* ({ didUri, tenant }) {
            // Attempt to retrieve the DID from the agent's DID store.
            const bearerDid = yield this.get({ didUri, tenant });
            if (!bearerDid) {
                throw new Error(`AgentDidApi: Failed to export due to DID not found: ${didUri}`);
            }
            // If the DID was found, return the DID in a portable format, and if supported by the Agent's
            // key manager, the private key material.
            const portableDid = yield bearerDid.export();
            return portableDid;
        });
    }
    get(_a) {
        return __awaiter(this, arguments, void 0, function* ({ didUri, tenant }) {
            const portableDid = yield this._store.get({ id: didUri, agent: this.agent, tenant, useCache: true });
            if (!portableDid) {
                return undefined;
            }
            const bearerDid = yield BearerDid.import({ portableDid, keyManager: this.agent.keyManager });
            return bearerDid;
        });
    }
    getSigningMethod(_a) {
        return __awaiter(this, arguments, void 0, function* ({ didUri, methodId }) {
            // Verify the DID method is supported.
            const parsedDid = Did.parse(didUri);
            if (!parsedDid) {
                throw new Error(`Invalid DID URI: ${didUri}`);
            }
            // Get the DID method implementation, which also verifies the method is supported.
            const didMethod = this.getMethod(parsedDid.method);
            // Resolve the DID document.
            const { didDocument, didResolutionMetadata } = yield this.resolve(didUri);
            if (!didDocument) {
                throw new Error(`DID resolution failed for '${didUri}': ${JSON.stringify(didResolutionMetadata)}`);
            }
            // Retrieve the method-specific verification method to be used for signing operations.
            const verificationMethod = yield didMethod.getSigningMethod({ didDocument, methodId });
            return verificationMethod;
        });
    }
    update(_a) {
        return __awaiter(this, arguments, void 0, function* ({ tenant, portableDid, publish = true }) {
            // Check if the DID exists in the store.
            const existingDid = yield this.get({ didUri: portableDid.uri, tenant: tenant !== null && tenant !== void 0 ? tenant : portableDid.uri });
            if (!existingDid) {
                throw new Error(`AgentDidApi: Could not update, DID not found: ${portableDid.uri}`);
            }
            // If the document has not changed, abort the update.
            if (canonicalize(portableDid.document) === canonicalize(existingDid.document)) {
                throw new Error('AgentDidApi: No changes detected, update aborted');
            }
            // If private keys are present in the PortableDid, import the key material into the Agent's key
            // manager. Validate that the key material for every verification method in the DID document is
            // present in the key manager. If no keys are present, this will fail.
            // NOTE: We currently do not delete the previous keys from the document.
            // TODO: Add support for deleting the keys no longer present in the document.
            const bearerDid = yield BearerDid.import({ keyManager: this.agent.keyManager, portableDid });
            // Only the DID URI, document, and metadata are stored in the Agent's DID store.
            const { uri, document, metadata } = bearerDid;
            const portableDidWithoutKeys = { uri, document, metadata };
            // pre-populate the resolution cache with the document and metadata
            yield this.cache.set(uri, { didDocument: document, didResolutionMetadata: {}, didDocumentMetadata: metadata });
            yield this._store.set({
                id: uri,
                data: portableDidWithoutKeys,
                agent: this.agent,
                tenant: tenant !== null && tenant !== void 0 ? tenant : uri,
                updateExisting: true,
                useCache: true
            });
            if (publish) {
                const parsedDid = Did.parse(uri);
                // currently only supporting DHT as a publishable method.
                // TODO: abstract this into the didMethod class so that other publishable methods can be supported.
                if (parsedDid && parsedDid.method === 'dht') {
                    yield DidDht.publish({ did: bearerDid });
                }
            }
            return bearerDid;
        });
    }
    import(_a) {
        return __awaiter(this, arguments, void 0, function* ({ portableDid, tenant }) {
            // If private keys are present in the PortableDid, import the key material into the Agent's key
            // manager. Validate that the key material for every verification method in the DID document is
            // present in the key manager.
            const bearerDid = yield BearerDid.import({ keyManager: this.agent.keyManager, portableDid });
            // Only the DID URI, document, and metadata are stored in the Agent's DID store.
            const { uri, document, metadata } = bearerDid;
            const portableDidWithoutKeys = { uri, document, metadata };
            // pre-populate the resolution cache with the document and metadata
            yield this.cache.set(uri, { didDocument: document, didResolutionMetadata: {}, didDocumentMetadata: metadata });
            // Store the DID in the agent's DID store.
            // Unless an existing `tenant` is specified, a record that includes the DID's URI, document,
            // and metadata will be stored under a new tenant controlled by the imported DID.
            yield this._store.set({
                id: portableDidWithoutKeys.uri,
                data: portableDidWithoutKeys,
                agent: this.agent,
                tenant: tenant !== null && tenant !== void 0 ? tenant : portableDidWithoutKeys.uri,
                preventDuplicates: true,
                useCache: true
            });
            return bearerDid;
        });
    }
    delete(_a) {
        return __awaiter(this, arguments, void 0, function* ({ didUri, tenant, deleteKey = true }) {
            const portableDid = yield this._store.get({ id: didUri, agent: this.agent, tenant, useCache: false });
            if (!portableDid) {
                throw new Error('AgentDidApi: Could not delete, DID not found');
            }
            // delete from the cache
            yield this.cache.delete(didUri);
            // Delete the data before deleting the associated keys.
            yield this._store.delete({ id: didUri, agent: this.agent, tenant });
            if (deleteKey) {
                // Delete the keys associated with the DID
                // TODO: this could be made a static method on `BearerDid` class
                yield this.deleteKeys({ portableDid });
            }
        });
    }
    deleteKeys(_a) {
        return __awaiter(this, arguments, void 0, function* ({ portableDid }) {
            for (const verificationMethod of portableDid.document.verificationMethod || []) {
                if (!verificationMethod.publicKeyJwk) {
                    continue;
                }
                // Compute the key URI of the verification method's public key.
                const keyUri = yield this.agent.keyManager.getKeyUri({ key: verificationMethod.publicKeyJwk });
                yield this.agent.keyManager.deleteKey({ keyUri });
            }
        });
    }
    processRequest(request) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a;
            // Process Create DID request.
            if (isDidRequest(request, DidInterface.Create)) {
                try {
                    const bearerDid = yield this.create(Object.assign({}, request.messageParams));
                    const response = {
                        result: {
                            uri: bearerDid.uri,
                            document: bearerDid.document,
                            metadata: bearerDid.metadata,
                        },
                        ok: true,
                        status: { code: 201, message: 'Created' }
                    };
                    return response;
                }
                catch (error) {
                    return {
                        ok: false,
                        status: { code: 500, message: (_a = error.message) !== null && _a !== void 0 ? _a : 'Unknown error occurred' }
                    };
                }
            }
            // Process Resolve DID request.
            if (isDidRequest(request, DidInterface.Resolve)) {
                const { didUri, options } = request.messageParams;
                const resolutionResult = yield this.resolve(didUri, options);
                const response = {
                    result: resolutionResult,
                    ok: true,
                    status: { code: 200, message: 'OK' }
                };
                return response;
            }
            throw new Error(`AgentDidApi: Unsupported request type: ${request.messageType}`);
        });
    }
    getMethod(methodName) {
        const didMethodApi = this._didMethods.get(methodName);
        if (didMethodApi === undefined) {
            throw new Error(`DID Method not supported: ${methodName}`);
        }
        return didMethodApi;
    }
}
//# sourceMappingURL=did-api.js.map