import { AllowAllTenantGate } from './core/tenant-gate.js';
import { Cid } from './utils/cid.js';
import { CoreProtocolRegistry } from './core/core-protocol.js';
import { DataStream } from './utils/data-stream.js';
import { DwnConstant } from './core/dwn-constant.js';
import { EncryptionProtocol } from './protocols/encryption.js';
import { Message } from './core/message.js';
import { messageReplyFromError } from './core/message-reply.js';
import { MessagesQueryHandler } from './handlers/messages-query.js';
import { MessagesReadHandler } from './handlers/messages-read.js';
import { MessagesSubscribeHandler } from './handlers/messages-subscribe.js';
import { PermissionsProtocol } from './protocols/permissions.js';
import { ProtocolsConfigureHandler } from './handlers/protocols-configure.js';
import { ProtocolsQueryHandler } from './handlers/protocols-query.js';
import { Records } from './utils/records.js';
import { RecordsCountHandler } from './handlers/records-count.js';
import { RecordsDelete } from './interfaces/records-delete.js';
import { RecordsDeleteHandler } from './handlers/records-delete.js';
import { RecordsQueryHandler } from './handlers/records-query.js';
import { RecordsReadHandler } from './handlers/records-read.js';
import { RecordsSubscribeHandler } from './handlers/records-subscribe.js';
import { RecordsWrite } from './interfaces/records-write.js';
import { RecordsWriteHandler } from './handlers/records-write.js';
import { ResumableTaskManager } from './core/resumable-task-manager.js';
import { StorageController } from './store/storage-controller.js';
import { StoreValidationStateReader } from './core/validation-state-reader.js';
import { DidDht, DidJwk, DidKey, DidResolverCacheMemory, DidWeb, UniversalResolver } from '@enbox/dids';
import { DwnError, DwnErrorCode } from './core/dwn-error.js';
import { DwnInterfaceName, DwnMethodName } from './enums/dwn-interface-method.js';
import { missingAncestorRecordIdsFromReply, replicationApplyResultFromReply } from './core/replication-apply.js';
export class Dwn {
    methodHandlers;
    didResolver;
    messageStore;
    dataStore;
    resumableTaskStore;
    tenantGate;
    eventLog;
    storageController;
    resumableTaskManager;
    _coreProtocols;
    validationStateReader;
    /** Whether the DWN owns the resolver's lifecycle (i.e., created it via defaults). */
    ownsResolver;
    constructor(config) {
        this.didResolver = config.didResolver;
        this.ownsResolver = config.ownsResolver ?? false;
        this.tenantGate = config.tenantGate;
        this.messageStore = config.messageStore;
        this.dataStore = config.dataStore;
        this.resumableTaskStore = config.resumableTaskStore;
        // Initialize the core protocol registry with built-in system protocols.
        this._coreProtocols = new CoreProtocolRegistry();
        this._coreProtocols.register(new EncryptionProtocol());
        this._coreProtocols.register(new PermissionsProtocol());
        // The single narrow surface through which validation logic reads state (replay-basis closure).
        const validationStateReader = new StoreValidationStateReader({
            messageStore: this.messageStore,
            dataStore: this.dataStore,
            coreProtocols: this._coreProtocols,
        });
        this.validationStateReader = config.instrumentValidationStateReader?.(validationStateReader) ?? validationStateReader;
        this.eventLog = config.eventLog;
        this.storageController = new StorageController({
            messageStore: this.messageStore,
            dataStore: this.dataStore,
        });
        this.resumableTaskManager = new ResumableTaskManager(config.resumableTaskStore, this.storageController);
        // Build the shared dependency bag once; every handler receives the same object
        // and accesses only the dependencies it needs.
        const deps = {
            didResolver: this.didResolver,
            messageStore: this.messageStore,
            validationStateReader: this.validationStateReader,
            dataStore: this.dataStore,
            resumableTaskManager: this.resumableTaskManager,
            coreProtocols: this._coreProtocols,
            eventLog: this.eventLog,
        };
        this.methodHandlers = {
            [DwnInterfaceName.Messages + DwnMethodName.Read]: new MessagesReadHandler(deps),
            [DwnInterfaceName.Messages + DwnMethodName.Query]: new MessagesQueryHandler(deps),
            [DwnInterfaceName.Messages + DwnMethodName.Subscribe]: new MessagesSubscribeHandler(deps),
            [DwnInterfaceName.Protocols + DwnMethodName.Configure]: new ProtocolsConfigureHandler(deps),
            [DwnInterfaceName.Protocols + DwnMethodName.Query]: new ProtocolsQueryHandler(deps),
            [DwnInterfaceName.Records + DwnMethodName.Count]: new RecordsCountHandler(deps),
            [DwnInterfaceName.Records + DwnMethodName.Delete]: new RecordsDeleteHandler(deps),
            [DwnInterfaceName.Records + DwnMethodName.Query]: new RecordsQueryHandler(deps),
            [DwnInterfaceName.Records + DwnMethodName.Read]: new RecordsReadHandler(deps),
            [DwnInterfaceName.Records + DwnMethodName.Subscribe]: new RecordsSubscribeHandler(deps),
            [DwnInterfaceName.Records + DwnMethodName.Write]: new RecordsWriteHandler(deps),
        };
    }
    /**
     * Creates an instance of the DWN.
     */
    static async create(config) {
        if (!config.didResolver) {
            config.didResolver = new UniversalResolver({
                didResolvers: [DidDht, DidJwk, DidKey, DidWeb],
                cache: new DidResolverCacheMemory(),
            });
            config.ownsResolver = true;
        }
        config.tenantGate ??= new AllowAllTenantGate();
        const dwn = new Dwn(config);
        await dwn.open();
        return dwn;
    }
    /**
     * Initializes the DWN instance and opens the connection to it.
     */
    async open() {
        // Open the resolver's cache if the DWN owns it (created via defaults).
        // The base `DidResolver` interface in `@enbox/dids` doesn't declare
        // optional lifecycle hooks; cache-backed implementations expose them
        // structurally. Narrowly-typed probe (no `any`).
        const lifecycleResolver = this.didResolver;
        if (this.ownsResolver && typeof lifecycleResolver.open === 'function') {
            await lifecycleResolver.open();
        }
        await this.messageStore.open();
        await this.dataStore.open();
        await this.resumableTaskStore.open();
        await this.eventLog?.open();
        await this.resumableTaskManager.resumeTasksAndWaitForCompletion();
    }
    async close() {
        await this.eventLog?.close();
        await this.messageStore.close();
        await this.dataStore.close();
        await this.resumableTaskStore.close();
        // Close the resolver's cache if the DWN owns it.
        const lifecycleResolver = this.didResolver;
        if (this.ownsResolver && typeof lifecycleResolver.close === 'function') {
            await lifecycleResolver.close();
        }
    }
    /**
     * The registry of core protocols (hardcoded, immutable, always-installed).
     * Used by handlers and utilities that need to check whether a protocol URI
     * belongs to a core protocol or to dispatch lifecycle hooks.
     */
    get coreProtocols() {
        return this._coreProtocols;
    }
    /**
     * Returns the internal storage components for advanced operations that
     * cannot be expressed through the standard `processMessage()` pipeline.
     *
     * Callers are responsible for maintaining consistency across stores.
     */
    get storage() {
        return {
            dataStore: this.dataStore,
            messageStore: this.messageStore,
            eventLog: this.eventLog,
        };
    }
    async processMessage(tenant, rawMessage, options = {}) {
        const errorMessageReply = await this.validateTenant(tenant) ?? await this.validateMessageIntegrity(rawMessage);
        if (errorMessageReply !== undefined) {
            return errorMessageReply;
        }
        const { dataStream, subscriptionHandler } = options;
        const handlerKey = rawMessage.descriptor.interface + rawMessage.descriptor.method;
        const methodHandlerReply = await this.methodHandlers[handlerKey].handle({
            tenant,
            message: rawMessage,
            dataStream,
            subscriptionHandler,
        });
        return methodHandlerReply;
    }
    /**
     * Applies a message obtained through replication and returns a structured
     * outcome instead of an HTTP-like handler status. Normal authoring still
     * uses `processMessage`; sync uses this entry point so missing local
     * dependencies can be fetched and retried without treating the replicated
     * message as permanently invalid.
     */
    async applyReplicatedMessage(tenant, rawMessage, options = {}) {
        const tenantError = await this.validateTenant(tenant);
        if (tenantError !== undefined) {
            return { kind: 'Deferred', reason: 'tenant-inactive' };
        }
        const integrityError = await this.validateMessageIntegrity(rawMessage);
        if (integrityError !== undefined) {
            return { kind: 'Invalid', reason: integrityError.status.detail };
        }
        if (await this.replicatedMessageAlreadyStored(tenant, rawMessage)) {
            return { kind: 'Duplicate' };
        }
        const reply = await this.processMessage(tenant, rawMessage, options);
        const replicatedWriteBeatenByDeleteResult = await this.storeReplicatedWriteBeatenByDelete(tenant, rawMessage, reply, options);
        if (replicatedWriteBeatenByDeleteResult !== undefined) {
            return replicatedWriteBeatenByDeleteResult;
        }
        const protocolDefinition = await this.getReplicationApplyProtocolDefinition(tenant, rawMessage, reply);
        const missingAncestorRecordIds = await this.getReplicationApplyMissingAncestors(tenant, rawMessage, reply);
        return replicationApplyResultFromReply(rawMessage, reply, { protocolDefinition, missingAncestorRecordIds });
    }
    /**
     * Computes the layer-batched missing-ancestor set for a replicated message that failed on a
     * missing ancestor (immediate parent or record-chain construction), so the resulting
     * `Incomplete` names every locally-absent ancestor at once. Returns `undefined`
     * (single-ancestor emission) when the set cannot be computed.
     */
    async getReplicationApplyMissingAncestors(tenant, message, reply) {
        try {
            return await missingAncestorRecordIdsFromReply(tenant, message, reply, this.validationStateReader);
        }
        catch {
            return undefined;
        }
    }
    async storeReplicatedWriteBeatenByDelete(tenant, message, reply, options) {
        const detail = reply.status.detail ?? '';
        if (!detail.startsWith(`${DwnErrorCode.RecordsWriteNotAllowedAfterDelete}:`) || !Records.isRecordsWrite(message)) {
            return undefined;
        }
        const query = {
            interface: DwnInterfaceName.Records,
            recordId: message.recordId,
        };
        const { messages: existingMessages } = await this.messageStore.query(tenant, [query]);
        const initialWrite = await RecordsWrite.getInitialWrite(existingMessages);
        const existingDelete = await Records.getNewestRecordsDelete(existingMessages);
        if (existingDelete === undefined) {
            return undefined;
        }
        const validationReply = await this.validateReplicatedWriteBeatenByDelete(tenant, message, existingMessages, options);
        if (validationReply !== undefined) {
            return replicationApplyResultFromReply(message, validationReply);
        }
        const recordsWrite = await RecordsWrite.parse(message);
        const storedWriteMessage = { ...message };
        delete storedWriteMessage.encodedData;
        const recordsWriteIndexes = await recordsWrite.constructIndexes(false);
        await this.messageStore.put(tenant, storedWriteMessage, recordsWriteIndexes);
        const recordsDelete = await RecordsDelete.parse(existingDelete);
        const visibilitySourceWrite = await Records.getNewestRecordsWrite([...existingMessages, storedWriteMessage]) ?? initialWrite;
        const recordsDeleteIndexes = recordsDelete.constructIndexes(initialWrite, visibilitySourceWrite);
        const recordsDeleteCid = await Message.getCid(existingDelete);
        await this.messageStore.updateIndexes(tenant, recordsDeleteCid, recordsDeleteIndexes);
        return { kind: 'Superseded' };
    }
    async validateReplicatedWriteBeatenByDelete(tenant, message, existingMessages, options) {
        try {
            await this.validateReplicatedWriteBeatenByDeleteOrThrow(tenant, message, existingMessages, options);
            return undefined;
        }
        catch (error) {
            const statusCode = error instanceof DwnError
                ? this._coreProtocols.mapErrorToStatusCode(error.code) ?? 400
                : 400;
            return messageReplyFromError(error, statusCode);
        }
    }
    async validateReplicatedWriteBeatenByDeleteOrThrow(tenant, message, existingMessages, options) {
        const coreProtocol = message.descriptor.protocol === undefined
            ? undefined
            : this._coreProtocols.get(message.descriptor.protocol);
        if (coreProtocol?.preProcessWrite !== undefined) {
            await coreProtocol.preProcessWrite(tenant, message, this.validationStateReader);
        }
        if (options.dataStream !== undefined) {
            await Dwn.validateReplicatedWriteBeatenByDeleteDataStream(message, options.dataStream, coreProtocol);
            return;
        }
        if (await RecordsWrite.isInitialWrite(message)) {
            return;
        }
        await this.validateReplicatedWriteBeatenByDeleteExistingData(tenant, message, existingMessages);
    }
    static async validateReplicatedWriteBeatenByDeleteDataStream(message, dataStream, coreProtocol) {
        if (message.descriptor.dataSize <= DwnConstant.maxDataSizeAllowedToBeEncoded) {
            const dataBytes = await DataStream.toBytes(dataStream);
            const dataCid = await Cid.computeDagPbCidFromBytes(dataBytes);
            RecordsWrite.validateDataIntegrity(message.descriptor.dataCid, message.descriptor.dataSize, dataCid, dataBytes.length);
            if (coreProtocol?.validateRecord !== undefined) {
                await coreProtocol.validateRecord(message, dataBytes);
            }
            return;
        }
        const [dataCidStream, dataSizeStream] = DataStream.duplicateDataStream(dataStream, 2);
        const [dataCid, dataSize] = await Promise.all([
            Cid.computeDagPbCidFromStream(dataCidStream),
            Dwn.getDataStreamByteLength(dataSizeStream),
        ]);
        RecordsWrite.validateDataIntegrity(message.descriptor.dataCid, message.descriptor.dataSize, dataCid, dataSize);
    }
    async validateReplicatedWriteBeatenByDeleteExistingData(tenant, message, existingMessages) {
        const newestExistingWrite = await Records.getNewestRecordsWrite(existingMessages);
        if (newestExistingWrite === undefined) {
            throw new DwnError(DwnErrorCode.RecordsWriteGetInitialWriteNotFound, `initial write is missing for record ${message.recordId}`);
        }
        const { dataCid, dataSize } = message.descriptor;
        RecordsWrite.validateDataIntegrity(dataCid, dataSize, newestExistingWrite.descriptor.dataCid, newestExistingWrite.descriptor.dataSize);
        if (dataSize <= DwnConstant.maxDataSizeAllowedToBeEncoded) {
            const newestExistingWriteWithData = newestExistingWrite;
            if (newestExistingWriteWithData.encodedData === undefined) {
                throw new DwnError(DwnErrorCode.RecordsWriteMissingEncodedDataInPrevious, `No dataStream was provided and unable to get data from previous message`);
            }
            return;
        }
        const priorDataExists = await this.validationStateReader.hasStoredData(tenant, newestExistingWrite.recordId, dataCid);
        if (!priorDataExists) {
            throw new DwnError(DwnErrorCode.RecordsWriteMissingDataInPrevious, `No dataStream was provided and unable to get data from previous message`);
        }
    }
    static async getDataStreamByteLength(dataStream) {
        let byteLength = 0;
        for await (const chunk of DataStream.asAsyncIterable(dataStream)) {
            byteLength += chunk.length;
        }
        return byteLength;
    }
    async getReplicationApplyProtocolDefinition(tenant, message, reply) {
        const detail = reply.status.detail ?? '';
        if (!detail.startsWith(`${DwnErrorCode.ProtocolAuthorizationMatchingRoleRecordNotFound}:`) &&
            !detail.startsWith(`${DwnErrorCode.EncryptionProtocolValidateAudienceWriterUnauthorized}:`)) {
            return undefined;
        }
        const lookup = Dwn.getReplicationApplyProtocolDefinitionLookup(message);
        if (lookup === undefined) {
            return undefined;
        }
        try {
            return await this.validationStateReader.fetchProtocolDefinition(tenant, lookup.protocol, lookup.messageTimestamp);
        }
        catch {
            return undefined;
        }
    }
    static getReplicationApplyProtocolDefinitionLookup(message) {
        if (Dwn.isRecordsWriteMessage(message)) {
            const taggedProtocol = Dwn.getEncryptionRecordTaggedProtocol(message);
            if (taggedProtocol !== undefined) {
                return {
                    protocol: taggedProtocol,
                    messageTimestamp: message.descriptor.messageTimestamp,
                };
            }
            return {
                protocol: message.descriptor.protocol,
                messageTimestamp: message.descriptor.messageTimestamp,
            };
        }
        const protocol = Dwn.getMessageProtocolForReplicationApply(message);
        return protocol === undefined ? undefined : { protocol };
    }
    static getEncryptionRecordTaggedProtocol(message) {
        if (message.descriptor.protocol !== EncryptionProtocol.uri) {
            return undefined;
        }
        const protocol = message.descriptor.tags?.protocol;
        return typeof protocol === 'string' ? protocol : undefined;
    }
    static getMessageProtocolForReplicationApply(message) {
        const descriptor = message.descriptor;
        if (typeof descriptor.protocol === 'string') {
            return descriptor.protocol;
        }
        if (typeof descriptor.filter?.protocol === 'string') {
            return descriptor.filter.protocol;
        }
    }
    static isRecordsWriteMessage(message) {
        return message.descriptor.interface === DwnInterfaceName.Records &&
            message.descriptor.method === DwnMethodName.Write;
    }
    async replicatedMessageAlreadyStored(tenant, message) {
        const existingMessages = await this.getExistingMessagesForReplicationDedup(tenant, message);
        if (existingMessages.length === 0) {
            return false;
        }
        const incomingCid = await Message.getCid(message);
        for (const existing of existingMessages) {
            if (await Message.getCid(existing) !== incomingCid) {
                continue;
            }
            return true;
        }
        return false;
    }
    async getExistingMessagesForReplicationDedup(tenant, message) {
        const { descriptor } = message;
        if (descriptor.interface === DwnInterfaceName.Records && descriptor.method === DwnMethodName.Write) {
            const recordId = message.recordId;
            if (typeof recordId !== 'string') {
                return [];
            }
            const { messages } = await this.messageStore.query(tenant, [{
                    interface: DwnInterfaceName.Records,
                    recordId,
                }]);
            return messages;
        }
        if (descriptor.interface === DwnInterfaceName.Records && descriptor.method === DwnMethodName.Delete) {
            const recordId = descriptor.recordId;
            if (typeof recordId !== 'string') {
                return [];
            }
            const { messages } = await this.messageStore.query(tenant, [{
                    interface: DwnInterfaceName.Records,
                    recordId,
                }]);
            return messages;
        }
        if (descriptor.interface === DwnInterfaceName.Protocols && descriptor.method === DwnMethodName.Configure) {
            const protocol = descriptor.definition?.protocol;
            if (typeof protocol !== 'string') {
                return [];
            }
            const { messages } = await this.messageStore.query(tenant, [{
                    interface: DwnInterfaceName.Protocols,
                    method: DwnMethodName.Configure,
                    protocol,
                }]);
            return messages;
        }
        return [];
    }
    /**
     * Checks tenant gate to see if tenant is allowed.
     * @param tenant The tenant DID to route the given message to.
     * @returns GenericMessageReply if the message has an integrity error, otherwise undefined.
     */
    async validateTenant(tenant) {
        const result = await this.tenantGate.isActiveTenant(tenant);
        if (!result.isActiveTenant) {
            const detail = result.detail ?? `DID ${tenant} is not an active tenant.`;
            return {
                status: { code: 401, detail }
            };
        }
    }
    /**
     * Validates structure of DWN message
     * @param tenant The tenant DID to route the given message to.
     * @param dwnMessageInterface The interface of DWN message.
     * @param dwnMessageMethod The interface of DWN message.
  
     * @returns GenericMessageReply if the message has an integrity error, otherwise undefined.
     */
    async validateMessageIntegrity(rawMessage) {
        // Verify interface and method
        const dwnInterface = rawMessage?.descriptor?.interface;
        const dwnMethod = rawMessage?.descriptor?.method;
        if (dwnInterface === undefined || dwnMethod === undefined) {
            return {
                status: { code: 400, detail: `Both interface and method must be present, interface: ${dwnInterface}, method: ${dwnMethod}` }
            };
        }
        // validate message structure
        try {
            // consider to push this down to individual handlers
            Message.validateJsonSchema(rawMessage);
        }
        catch (error) {
            return messageReplyFromError(error, 400);
        }
    }
}
;
;
//# sourceMappingURL=dwn.js.map