"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FunctionalTranslator = void 0;
const ir_js_1 = require("./ir.cjs");
const base_js_1 = require("./base.cjs");
const utils_js_1 = require("./utils.cjs");
/**
 * A class that extends `BaseTranslator` to translate structured queries
 * into functional filters.
 * @example
 * ```typescript
 * const functionalTranslator = new FunctionalTranslator();
 * const relevantDocuments = await functionalTranslator.getRelevantDocuments(
 *   "Which movies are rated higher than 8.5?",
 * );
 * ```
 */
class FunctionalTranslator extends base_js_1.BaseTranslator {
    constructor() {
        super(...arguments);
        Object.defineProperty(this, "allowedOperators", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: [ir_js_1.Operators.and, ir_js_1.Operators.or]
        });
        Object.defineProperty(this, "allowedComparators", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: [
                ir_js_1.Comparators.eq,
                ir_js_1.Comparators.ne,
                ir_js_1.Comparators.gt,
                ir_js_1.Comparators.gte,
                ir_js_1.Comparators.lt,
                ir_js_1.Comparators.lte,
            ]
        });
    }
    formatFunction() {
        throw new Error("Not implemented");
    }
    /**
     * Returns a function that performs a comparison based on the provided
     * comparator.
     * @param comparator The comparator to base the comparison function on.
     * @returns A function that takes two arguments and returns a boolean based on the comparison.
     */
    getComparatorFunction(comparator) {
        switch (comparator) {
            case ir_js_1.Comparators.eq: {
                return (a, b) => a === b;
            }
            case ir_js_1.Comparators.ne: {
                return (a, b) => a !== b;
            }
            case ir_js_1.Comparators.gt: {
                return (a, b) => a > b;
            }
            case ir_js_1.Comparators.gte: {
                return (a, b) => a >= b;
            }
            case ir_js_1.Comparators.lt: {
                return (a, b) => a < b;
            }
            case ir_js_1.Comparators.lte: {
                return (a, b) => a <= b;
            }
            default: {
                throw new Error("Unknown comparator");
            }
        }
    }
    /**
     * Returns a function that performs an operation based on the provided
     * operator.
     * @param operator The operator to base the operation function on.
     * @returns A function that takes two boolean arguments and returns a boolean based on the operation.
     */
    getOperatorFunction(operator) {
        switch (operator) {
            case ir_js_1.Operators.and: {
                return (a, b) => a && b;
            }
            case ir_js_1.Operators.or: {
                return (a, b) => a || b;
            }
            default: {
                throw new Error("Unknown operator");
            }
        }
    }
    /**
     * Visits the operation part of a structured query and translates it into
     * a functional filter.
     * @param operation The operation part of a structured query.
     * @returns A function that takes a `Document` as an argument and returns a boolean based on the operation.
     */
    visitOperation(operation) {
        const { operator, args } = operation;
        if (this.allowedOperators.includes(operator)) {
            const operatorFunction = this.getOperatorFunction(operator);
            return (document) => {
                if (!args) {
                    return true;
                }
                return args.reduce((acc, arg) => {
                    const result = arg.accept(this);
                    if (typeof result === "function") {
                        return operatorFunction(acc, result(document));
                    }
                    else {
                        throw new Error("Filter is not a function");
                    }
                }, true);
            };
        }
        else {
            throw new Error("Operator not allowed");
        }
    }
    /**
     * Visits the comparison part of a structured query and translates it into
     * a functional filter.
     * @param comparison The comparison part of a structured query.
     * @returns A function that takes a `Document` as an argument and returns a boolean based on the comparison.
     */
    visitComparison(comparison) {
        const { comparator, attribute, value } = comparison;
        const undefinedTrue = [ir_js_1.Comparators.ne];
        if (this.allowedComparators.includes(comparator)) {
            const comparatorFunction = this.getComparatorFunction(comparator);
            return (document) => {
                const documentValue = document.metadata[attribute];
                if (documentValue === undefined) {
                    if (undefinedTrue.includes(comparator)) {
                        return true;
                    }
                    return false;
                }
                return comparatorFunction(documentValue, (0, utils_js_1.castValue)(value));
            };
        }
        else {
            throw new Error("Comparator not allowed");
        }
    }
    /**
     * Visits a structured query and translates it into a functional filter.
     * @param query The structured query to translate.
     * @returns An object containing a `filter` property, which is a function that takes a `Document` as an argument and returns a boolean based on the structured query.
     */
    visitStructuredQuery(query) {
        if (!query.filter) {
            return {};
        }
        const filterFunction = query.filter?.accept(this);
        if (typeof filterFunction !== "function") {
            throw new Error("Structured query filter is not a function");
        }
        return { filter: filterFunction };
    }
    /**
     * Merges two filters into one, based on the specified merge type.
     * @param defaultFilter The default filter function.
     * @param generatedFilter The generated filter function.
     * @param mergeType The type of merge to perform. Can be 'and', 'or', or 'replace'. Default is 'and'.
     * @returns A function that takes a `Document` as an argument and returns a boolean based on the merged filters, or `undefined` if both filters are empty.
     */
    mergeFilters(defaultFilter, generatedFilter, mergeType = "and") {
        if ((0, utils_js_1.isFilterEmpty)(defaultFilter) && (0, utils_js_1.isFilterEmpty)(generatedFilter)) {
            return undefined;
        }
        if ((0, utils_js_1.isFilterEmpty)(defaultFilter) || mergeType === "replace") {
            if ((0, utils_js_1.isFilterEmpty)(generatedFilter)) {
                return undefined;
            }
            return generatedFilter;
        }
        if ((0, utils_js_1.isFilterEmpty)(generatedFilter)) {
            if (mergeType === "and") {
                return undefined;
            }
            return defaultFilter;
        }
        if (mergeType === "and") {
            return (document) => defaultFilter(document) && generatedFilter(document);
        }
        else if (mergeType === "or") {
            return (document) => defaultFilter(document) || generatedFilter(document);
        }
        else {
            throw new Error("Unknown merge type");
        }
    }
}
exports.FunctionalTranslator = FunctionalTranslator;
