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 { authenticate } from '../core/auth.js';
import { messageReplyFromError } from '../core/message-reply.js';
import { ProtocolAuthorization } from '../core/protocol-authorization.js';
import { Records } from '../utils/records.js';
import { SortOrder } from '../types/message-types.js';
import { DateSort, RecordsQuery } from '../interfaces/records-query.js';
import { DwnInterfaceName, DwnMethodName } from '../core/message.js';
export class RecordsQueryHandler {
    constructor(didResolver, messageStore, dataStore) {
        this.didResolver = didResolver;
        this.messageStore = messageStore;
        this.dataStore = dataStore;
    }
    handle({ tenant, message }) {
        return __awaiter(this, void 0, void 0, function* () {
            let recordsQuery;
            try {
                recordsQuery = yield RecordsQuery.parse(message);
            }
            catch (e) {
                return messageReplyFromError(e, 400);
            }
            let recordsWrites;
            let paginationMessageCid;
            // if this is an anonymous query and the filter supports published records, query only published records
            if (RecordsQueryHandler.filterIncludesPublishedRecords(recordsQuery) && recordsQuery.author === undefined) {
                const results = yield this.fetchPublishedRecords(tenant, recordsQuery);
                recordsWrites = results.messages;
                paginationMessageCid = results.paginationMessageCid;
            }
            else {
                // authentication and authorization
                try {
                    yield authenticate(message.authorization, this.didResolver);
                    // Only run protocol authz if message deliberately invokes it
                    if (RecordsQueryHandler.shouldProtocolAuthorizeQuery(recordsQuery)) {
                        yield ProtocolAuthorization.authorizeQuery(tenant, recordsQuery, this.messageStore);
                    }
                }
                catch (e) {
                    return messageReplyFromError(e, 401);
                }
                if (recordsQuery.author === tenant) {
                    const results = yield this.fetchRecordsAsOwner(tenant, recordsQuery);
                    recordsWrites = results.messages;
                    paginationMessageCid = results.paginationMessageCid;
                }
                else {
                    const results = yield this.fetchRecordsAsNonOwner(tenant, recordsQuery);
                    recordsWrites = results.messages;
                    paginationMessageCid = results.paginationMessageCid;
                }
            }
            return {
                status: { code: 200, detail: 'OK' },
                entries: recordsWrites,
                paginationMessageCid
            };
        });
    }
    /**
     * Convert an incoming DateSort to a sort type accepted by MessageStore
     * Defaults to 'dateCreated' in Descending order if no sort is supplied.
     *
     * @param dateSort the optional DateSort from the RecordsQuery message descriptor.
     * @returns {MessageSort} for MessageStore sorting.
     */
    convertDateSort(dateSort) {
        switch (dateSort) {
            case DateSort.CreatedAscending:
                return { dateCreated: SortOrder.Ascending };
            case DateSort.CreatedDescending:
                return { dateCreated: SortOrder.Descending };
            case DateSort.PublishedAscending:
                return { datePublished: SortOrder.Ascending };
            case DateSort.PublishedDescending:
                return { datePublished: SortOrder.Descending };
            default:
                return { dateCreated: SortOrder.Ascending };
        }
    }
    /**
     * Fetches the records as the owner of the DWN with no additional filtering.
     */
    fetchRecordsAsOwner(tenant, recordsQuery) {
        return __awaiter(this, void 0, void 0, function* () {
            const { dateSort, filter, pagination } = recordsQuery.message.descriptor;
            // fetch all published records matching the query
            const queryFilter = Object.assign(Object.assign({}, Records.convertFilter(filter)), { interface: DwnInterfaceName.Records, method: DwnMethodName.Write, isLatestBaseState: true });
            const messageSort = this.convertDateSort(dateSort);
            return this.messageStore.query(tenant, [queryFilter], messageSort, pagination);
        });
    }
    /**
     * Fetches the records as a non-owner.
     *
     * Filters can support returning both published and unpublished records,
     * as well as explicitly only published or only unpublished records.
     *
     * A) BOTH published and unpublished:
     *    1. published records; and
     *    2. unpublished records intended for the query author (where `recipient` is the query author); and
     *    3. unpublished records authorized by a protocol rule.
     *
     * B) PUBLISHED:
     *    1. only published records;
     *
     * C) UNPUBLISHED:
     *    1. unpublished records intended for the query author (where `recipient` is the query author); and
     *    2. unpublished records authorized by a protocol rule.
     *
     */
    fetchRecordsAsNonOwner(tenant, recordsQuery) {
        return __awaiter(this, void 0, void 0, function* () {
            const { dateSort, pagination } = recordsQuery.message.descriptor;
            const filters = [];
            if (RecordsQueryHandler.filterIncludesPublishedRecords(recordsQuery)) {
                filters.push(RecordsQueryHandler.buildPublishedRecordsFilter(recordsQuery));
            }
            if (RecordsQueryHandler.filterIncludesUnpublishedRecords(recordsQuery)) {
                filters.push(RecordsQueryHandler.buildUnpublishedRecordsByQueryAuthorFilter(recordsQuery));
                const recipientFilter = recordsQuery.message.descriptor.filter.recipient;
                if (recipientFilter === undefined || recipientFilter === recordsQuery.author) {
                    filters.push(RecordsQueryHandler.buildUnpublishedRecordsForQueryAuthorFilter(recordsQuery));
                }
                if (RecordsQueryHandler.shouldProtocolAuthorizeQuery(recordsQuery)) {
                    filters.push(RecordsQueryHandler.buildUnpublishedProtocolAuthorizedRecordsFilter(recordsQuery));
                }
            }
            const messageSort = this.convertDateSort(dateSort);
            return this.messageStore.query(tenant, filters, messageSort, pagination);
        });
    }
    /**
     * Fetches only published records.
     */
    fetchPublishedRecords(tenant, recordsQuery) {
        return __awaiter(this, void 0, void 0, function* () {
            const { dateSort, pagination } = recordsQuery.message.descriptor;
            const filter = RecordsQueryHandler.buildPublishedRecordsFilter(recordsQuery);
            const messageSort = this.convertDateSort(dateSort);
            return this.messageStore.query(tenant, [filter], messageSort, pagination);
        });
    }
    static buildPublishedRecordsFilter(recordsQuery) {
        // fetch all published records matching the query
        return Object.assign(Object.assign({}, Records.convertFilter(recordsQuery.message.descriptor.filter)), { interface: DwnInterfaceName.Records, method: DwnMethodName.Write, published: true, isLatestBaseState: true });
    }
    /**
     * Creates a filter for unpublished records that are intended for the query author (where `recipient` is the author).
     */
    static buildUnpublishedRecordsForQueryAuthorFilter(recordsQuery) {
        // include records where recipient is query author
        return Object.assign(Object.assign({}, Records.convertFilter(recordsQuery.message.descriptor.filter)), { interface: DwnInterfaceName.Records, method: DwnMethodName.Write, recipient: recordsQuery.author, isLatestBaseState: true, published: false });
    }
    /**
     * Creates a filter for unpublished records that are within the specified protocol.
     * Validation that `protocol` and other required protocol-related fields occurs before this method.
     */
    static buildUnpublishedProtocolAuthorizedRecordsFilter(recordsQuery) {
        return Object.assign(Object.assign({}, Records.convertFilter(recordsQuery.message.descriptor.filter)), { interface: DwnInterfaceName.Records, method: DwnMethodName.Write, isLatestBaseState: true, published: false });
    }
    /**
     * Creates a filter for only unpublished records where the author is the same as the query author.
     */
    static buildUnpublishedRecordsByQueryAuthorFilter(recordsQuery) {
        // include records where author is the same as the query author
        return Object.assign(Object.assign({}, Records.convertFilter(recordsQuery.message.descriptor.filter)), { author: recordsQuery.author, interface: DwnInterfaceName.Records, method: DwnMethodName.Write, isLatestBaseState: true, published: false });
    }
    /**
     * Determines if ProtocolAuthorization.authorizeQuery should be run and if the corresponding filter should be used.
     */
    static shouldProtocolAuthorizeQuery(recordsQuery) {
        return recordsQuery.signaturePayload.protocolRole !== undefined;
    }
    /**
     * Checks if the recordQuery filter supports returning published records.
     */
    static filterIncludesPublishedRecords(recordsQuery) {
        const { filter } = recordsQuery.message.descriptor;
        // When `published` and `datePublished` range are both undefined, published records can be returned.
        return filter.datePublished !== undefined || filter.published !== false;
    }
    /**
     * Checks if the recordQuery filter supports returning unpublished records.
     */
    static filterIncludesUnpublishedRecords(recordsQuery) {
        const { filter } = recordsQuery.message.descriptor;
        // When `published` and `datePublished` range are both undefined, unpublished records can be returned.
        if (filter.datePublished === undefined && filter.published === undefined) {
            return true;
        }
        return filter.published === false;
    }
}
//# sourceMappingURL=records-query.js.map