import { authenticate } from '../core/auth.js';
import { Message } from '../core/message.js';
import { messageReplyFromError } from '../core/message-reply.js';
import { Messages } from '../utils/messages.js';
import { MessagesGrantAuthorization } from '../core/messages-grant-authorization.js';
import { MessagesQuery } from '../interfaces/messages-query.js';
import { Replication } from '../utils/replication.js';
import { DwnError, DwnErrorCode } from '../core/dwn-error.js';
export class MessagesQueryHandler {
    deps;
    constructor(deps) {
        this.deps = deps;
    }
    async handle({ tenant, message }) {
        let messagesQuery;
        try {
            messagesQuery = await MessagesQuery.parse(message);
        }
        catch (e) {
            return messageReplyFromError(e, 400);
        }
        try {
            await authenticate(message.authorization, this.deps.didResolver);
            await this.authorizeMessagesQuery(tenant, messagesQuery);
        }
        catch (e) {
            return messageReplyFromError(e, 401);
        }
        const replicationFeedReader = MessagesQueryHandler.asReplicationFeedReader(this.deps.messageStore);
        if (replicationFeedReader === undefined) {
            return {
                status: {
                    code: 501,
                    detail: `${DwnErrorCode.MessagesQueryReplicationFeedUnimplemented}: MessagesQuery requires a replication feed reader`,
                }
            };
        }
        try {
            const filters = MessagesQueryHandler.convertFilters(message.descriptor.filters, this.deps);
            const result = await replicationFeedReader.logRead(tenant, {
                cursor: message.descriptor.cursor,
                filters,
                limit: message.descriptor.limit,
            });
            const reply = {
                status: { code: 200, detail: 'OK' },
                entries: await MessagesQueryHandler.buildEntries(result.events, message.descriptor.cidsOnly ?? false),
                cursor: result.cursor,
                drained: result.drained,
            };
            const fingerprintScopes = MessagesQueryHandler.computeFingerprintScopes(message.descriptor.filters);
            if (fingerprintScopes !== undefined) {
                reply.fingerprint = await replicationFeedReader.fingerprint(tenant, fingerprintScopes);
            }
            return reply;
        }
        catch (e) {
            if (e instanceof DwnError && e.code === DwnErrorCode.EventLogProgressGap) {
                const gapInfo = MessagesQueryHandler.getProgressGapInfo(e);
                return {
                    status: { code: 410, detail: 'Progress token gap' },
                    error: gapInfo === undefined ? undefined : { code: 'ProgressGap', ...gapInfo },
                };
            }
            return messageReplyFromError(e, 500);
        }
    }
    async authorizeMessagesQuery(tenant, messagesQuery) {
        if (messagesQuery.author === tenant) {
            return;
        }
        const permissionGrantIds = Message.getPermissionGrantIds(messagesQuery.signaturePayload);
        if (messagesQuery.author !== undefined && permissionGrantIds.length > 0) {
            const permissionGrants = await MessagesGrantAuthorization.fetchPermissionGrants(tenant, this.deps.validationStateReader, permissionGrantIds);
            await MessagesGrantAuthorization.authorizeQueryOrSubscribe({
                incomingMessage: messagesQuery.message,
                expectedGrantor: tenant,
                expectedGrantee: messagesQuery.author,
                permissionGrants,
                validationStateReader: this.deps.validationStateReader
            });
            return;
        }
        throw new DwnError(DwnErrorCode.MessagesQueryAuthorizationFailed, 'message failed authorization');
    }
    static asReplicationFeedReader(candidate) {
        const partial = candidate;
        if (typeof partial.logRead === 'function' &&
            typeof partial.logBounds === 'function' &&
            typeof partial.fingerprint === 'function' &&
            typeof partial.epoch === 'function') {
            return partial;
        }
    }
    static convertFilters(filters, deps) {
        if (filters.length === 0) {
            return undefined;
        }
        return Messages.convertFilters(filters, deps.coreProtocols);
    }
    static async buildEntries(events, cidsOnly) {
        const entries = [];
        for (const event of events) {
            entries.push(await MessagesQueryHandler.buildEntry(event, cidsOnly));
        }
        return entries;
    }
    static async buildEntry(event, cidsOnly) {
        const messageCid = event.messageCid ?? await Message.getCid(event.event.message);
        const protocol = MessagesQueryHandler.getStringIndex(event.indexes, 'protocol');
        const entry = {
            seq: event.seq,
            messageCid,
            isLatestBaseState: MessagesQueryHandler.isLatestBaseState(event.indexes),
            protocol,
        };
        if (cidsOnly) {
            return entry;
        }
        const { message, encodedData } = Messages.detachEncodedData(event.event.message);
        entry.message = message;
        if (encodedData !== undefined) {
            entry.encodedData = encodedData;
        }
        return entry;
    }
    static getStringIndex(indexes, key) {
        const value = indexes[key];
        return typeof value === 'string' ? value : undefined;
    }
    static isLatestBaseState(indexes) {
        return indexes.isLatestBaseState === true || indexes.isLatestBaseState === 'true';
    }
    static computeFingerprintScopes(filters) {
        if (filters.length === 0) {
            return [Replication.globalDomain];
        }
        const protocols = new Set();
        for (const filter of filters) {
            const keys = Object.keys(filter);
            if (keys.length !== 1 || typeof filter.protocol !== 'string') {
                return undefined;
            }
            protocols.add(filter.protocol);
        }
        const scopes = [];
        for (const protocol of protocols) {
            scopes.push(Replication.protocolDomain(protocol), ...Replication.taggedCoreProtocolDomains(protocol, protocols));
        }
        return scopes;
    }
    static getProgressGapInfo(error) {
        const gapInfo = error.gapInfo;
        return gapInfo;
    }
}
//# sourceMappingURL=messages-query.js.map