"use strict";
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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RecordsQueryHandler = void 0;
const auth_js_1 = require("../core/auth.js");
const string_js_1 = require("../utils/string.js");
const message_reply_js_1 = require("../core/message-reply.js");
const storage_controller_js_1 = require("../store/storage-controller.js");
const records_query_js_1 = require("../interfaces/records-query.js");
const message_js_1 = require("../core/message.js");
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 records_query_js_1.RecordsQuery.parse(message);
            }
            catch (e) {
                return (0, message_reply_js_1.messageReplyFromError)(e, 400);
            }
            // if this is an anonymous query, query only published records
            if (recordsQuery.author === undefined) {
                let recordsWrites = yield this.fetchPublishedRecords(tenant, recordsQuery);
                recordsWrites = yield sortRecords(recordsWrites, recordsQuery.message.descriptor.dateSort);
                const entries = RecordsQueryHandler.removeAuthorization(recordsWrites);
                return {
                    status: { code: 200, detail: 'OK' },
                    entries
                };
            }
            // authentication
            try {
                yield (0, auth_js_1.authenticate)(message.authorization, this.didResolver);
            }
            catch (e) {
                return (0, message_reply_js_1.messageReplyFromError)(e, 401);
            }
            let recordsWrites;
            if (recordsQuery.author === tenant) {
                recordsWrites = yield this.fetchRecordsAsOwner(tenant, recordsQuery);
            }
            else {
                recordsWrites = yield this.fetchRecordsAsNonOwner(tenant, recordsQuery);
            }
            recordsWrites = yield sortRecords(recordsWrites, recordsQuery.message.descriptor.dateSort);
            const entries = RecordsQueryHandler.removeAuthorization(recordsWrites);
            return {
                status: { code: 200, detail: 'OK' },
                entries
            };
        });
    }
    /**
     * Removes `authorization` property from each and every `RecordsWrite` message given and returns the result as a different array.
     */
    static removeAuthorization(recordsWriteMessages) {
        const recordsQueryReplyEntries = [];
        for (const record of recordsWriteMessages) {
            const { authorization: _ } = record, objectWithRemainingProperties = __rest(record, ["authorization"]); // a trick to stripping away `authorization`
            recordsQueryReplyEntries.push(objectWithRemainingProperties);
        }
        return recordsQueryReplyEntries;
    }
    /**
     * Fetches the records as the owner of the DWN with no additional filtering.
     */
    fetchRecordsAsOwner(tenant, recordsQuery) {
        return __awaiter(this, void 0, void 0, function* () {
            // fetch all published records matching the query
            const filter = Object.assign(Object.assign({}, records_query_js_1.RecordsQuery.convertFilter(recordsQuery.message.descriptor.filter)), { interface: message_js_1.DwnInterfaceName.Records, method: message_js_1.DwnMethodName.Write, isLatestBaseState: true });
            const records = yield storage_controller_js_1.StorageController.query(this.messageStore, this.dataStore, tenant, filter);
            return records;
        });
    }
    /**
     * Fetches the records as a non-owner, return only:
     * 1. published records; and
     * 2. unpublished records intended for the query author (where `recipient` is the query author)
     */
    fetchRecordsAsNonOwner(tenant, recordsQuery) {
        return __awaiter(this, void 0, void 0, function* () {
            const publishedRecords = yield this.fetchPublishedRecords(tenant, recordsQuery);
            const unpublishedRecordsByAuthor = yield this.fetchUnpublishedRecordsByAuthor(tenant, recordsQuery);
            // the `RecordsQuery` author in addition is allowed to get private records that were meant for them
            let unpublishedRecordsForQueryAuthor = [];
            const recipientFilter = recordsQuery.message.descriptor.filter.recipient;
            if (recipientFilter === undefined || recipientFilter === recordsQuery.author) {
                unpublishedRecordsForQueryAuthor = yield this.fetchUnpublishedRecordsForQueryAuthor(tenant, recordsQuery);
            }
            const records = [...publishedRecords, ...unpublishedRecordsByAuthor, ...unpublishedRecordsForQueryAuthor];
            // go through the records and remove duplicates
            // this can happen between `unpublishedRecordsByAuthor` and `unpublishedRecordsForQueryAuthor` when `author` = `recipient`
            const deduplicatedRecords = new Map();
            for (const record of records) {
                if (!deduplicatedRecords.has(record.recordId)) {
                    deduplicatedRecords.set(record.recordId, record);
                }
            }
            return Array.from(deduplicatedRecords.values());
        });
    }
    /**
     * Fetches only published records.
     */
    fetchPublishedRecords(tenant, recordsQuery) {
        return __awaiter(this, void 0, void 0, function* () {
            // fetch all published records matching the query
            const filter = Object.assign(Object.assign({}, records_query_js_1.RecordsQuery.convertFilter(recordsQuery.message.descriptor.filter)), { interface: message_js_1.DwnInterfaceName.Records, method: message_js_1.DwnMethodName.Write, published: true, isLatestBaseState: true });
            const publishedRecords = yield storage_controller_js_1.StorageController.query(this.messageStore, this.dataStore, tenant, filter);
            return publishedRecords;
        });
    }
    /**
     * Fetches unpublished records that are intended for the query author (where `recipient` is the author).
     */
    fetchUnpublishedRecordsForQueryAuthor(tenant, recordsQuery) {
        return __awaiter(this, void 0, void 0, function* () {
            // include records where recipient is query author
            const filter = Object.assign(Object.assign({}, records_query_js_1.RecordsQuery.convertFilter(recordsQuery.message.descriptor.filter)), { interface: message_js_1.DwnInterfaceName.Records, method: message_js_1.DwnMethodName.Write, recipient: recordsQuery.author, isLatestBaseState: true, published: false });
            const unpublishedRecordsForQueryAuthor = yield storage_controller_js_1.StorageController.query(this.messageStore, this.dataStore, tenant, filter);
            return unpublishedRecordsForQueryAuthor;
        });
    }
    /**
     * Fetches only unpublished records where the author is the same as the query author.
     */
    fetchUnpublishedRecordsByAuthor(tenant, recordsQuery) {
        return __awaiter(this, void 0, void 0, function* () {
            // include records where author is the same as the query author
            const filter = Object.assign(Object.assign({}, records_query_js_1.RecordsQuery.convertFilter(recordsQuery.message.descriptor.filter)), { author: recordsQuery.author, interface: message_js_1.DwnInterfaceName.Records, method: message_js_1.DwnMethodName.Write, isLatestBaseState: true, published: false });
            const unpublishedRecordsForQueryAuthor = yield storage_controller_js_1.StorageController.query(this.messageStore, this.dataStore, tenant, filter);
            return unpublishedRecordsForQueryAuthor;
        });
    }
}
exports.RecordsQueryHandler = RecordsQueryHandler;
/**
 * Sorts the given records. There are 4 options for dateSort:
 * 1. createdAscending - Sort in ascending order based on when the message was created
 * 2. createdDescending - Sort in descending order based on when the message was created
 * 3. publishedAscending - If the message is published, sort in asc based on publish date
 * 4. publishedDescending - If the message is published, sort in desc based on publish date
 *
 * If sorting is based on date published, records that are not published are filtered out.
 * @param messages - Messages to be sorted if dateSort is present
 * @param dateSort - Sorting scheme
 * @returns Sorted Messages
 */
function sortRecords(messages, dateSort = records_query_js_1.DateSort.CreatedAscending) {
    return __awaiter(this, void 0, void 0, function* () {
        switch (dateSort) {
            case records_query_js_1.DateSort.CreatedAscending:
                return messages.sort((a, b) => (0, string_js_1.lexicographicalCompare)(a.descriptor.dateCreated, b.descriptor.dateCreated));
            case records_query_js_1.DateSort.CreatedDescending:
                return messages.sort((a, b) => (0, string_js_1.lexicographicalCompare)(b.descriptor.dateCreated, a.descriptor.dateCreated));
            case records_query_js_1.DateSort.PublishedAscending:
                return messages
                    .filter(m => m.descriptor.published)
                    .sort((a, b) => (0, string_js_1.lexicographicalCompare)(a.descriptor.datePublished, b.descriptor.datePublished));
            case records_query_js_1.DateSort.PublishedDescending:
                return messages
                    .filter(m => m.descriptor.published)
                    .sort((a, b) => (0, string_js_1.lexicographicalCompare)(b.descriptor.datePublished, a.descriptor.datePublished));
        }
    });
}
//# sourceMappingURL=records-query.js.map