"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateCollectionFunctionScore = exports.generateSearchFunctionScore = exports.generateCuratedPageCollectionQuery = exports.generateQueryCuratedPageSearch = void 0;
const firestore_1 = require("../types/firestore");
const generateQueryCuratedPageSearch = (options) => {
    var _a, _b;
    return (0, exports.generateSearchFunctionScore)({
        searchTerm: (_a = options.curation.searchTerm) !== null && _a !== void 0 ? _a : "",
        searchableFields: options.searchableFields,
        boostingRules: (_b = options.curation.boostings) !== null && _b !== void 0 ? _b : [],
        markets: options.curation.markets,
        market: options.market,
    });
};
exports.generateQueryCuratedPageSearch = generateQueryCuratedPageSearch;
const generateCuratedPageCollectionQuery = (options) => {
    var _a, _b;
    const collectionFunctionScore = (0, exports.generateCollectionFunctionScore)({
        collectionHandle: (_a = options.curation.collectionHandle) !== null && _a !== void 0 ? _a : "",
        boostingRules: (_b = options.curation.boostings) !== null && _b !== void 0 ? _b : [],
        markets: options.curation.markets,
        market: options.market,
    });
    return { function_score: collectionFunctionScore };
};
exports.generateCuratedPageCollectionQuery = generateCuratedPageCollectionQuery;
const generateMarketsClause = (markets) => {
    if (!markets.length)
        return null;
    return { terms: { markets: markets } };
};
const generateQueryField = (options) => {
    if (!options.market) {
        if (options.fieldName === "price")
            return "price_min";
        return options.fieldName;
    }
    if (options.fieldName === "price") {
        return `price_min_market_${options.market}`;
    }
    if (options.fieldName === "price_min") {
        return `price_min_market_${options.market}`;
    }
    if (options.fieldName === "price_max") {
        return `price_max_market_${options.market}`;
    }
    if (options.fieldName === "inventory_total") {
        return `inventory_quantity_market_${options.market}`;
    }
    return options.fieldName;
};
const generateBoostingRule = (options) => {
    const fieldName = generateQueryField({
        fieldName: options.boostingRule.fieldName,
        market: options.market,
    });
    if (options.boostingRule.operation === firestore_1.Operation.Contains) {
        return {
            filter: { wildcard: { [fieldName]: `*${options.boostingRule.value}*` } },
            weight: options.boostingRule.points,
        };
    }
    if (options.boostingRule.operation === firestore_1.Operation.NotContains) {
        return {
            filter: {
                bool: { must_not: { wildcard: { [fieldName]: `*${options.boostingRule.value}*` } } },
            },
            weight: options.boostingRule.points,
        };
    }
    if (options.boostingRule.operation === firestore_1.Operation.Equals) {
        return {
            filter: { term: { [fieldName]: options.boostingRule.value } },
            weight: options.boostingRule.points,
        };
    }
    if (options.boostingRule.operation === firestore_1.Operation.GreaterThan) {
        return {
            filter: { range: { [fieldName]: { gt: parseFloat(options.boostingRule.value) } } },
            weight: options.boostingRule.points,
        };
    }
    if (options.boostingRule.operation === firestore_1.Operation.LessThan) {
        return {
            filter: { range: { [fieldName]: { lt: parseFloat(options.boostingRule.value) } } },
            weight: options.boostingRule.points,
        };
    }
    throw new Error(`Unable to process rule`);
};
const generateBoostingRules = (options) => {
    return options.boostingRules
        .map((boostingRule) => generateBoostingRule({ boostingRule: boostingRule, market: options.market }))
        .filter(Boolean);
};
const generateSearchFunctionScore = (options) => {
    var _a, _b;
    const searchTerms = (_b = (_a = options.searchTerm) === null || _a === void 0 ? void 0 : _a.split(" ")) !== null && _b !== void 0 ? _b : [];
    const searchQueries = [];
    for (const searchableField of options.searchableFields) {
        for (const searchTerm of searchTerms) {
            searchQueries.push({
                match: {
                    [searchableField.field.replace(/\.[^.]*$/, "")]: {
                        query: searchTerm,
                        fuzziness: searchableField.allowedMistakes,
                        analyzer: "regular_analyzer",
                    },
                },
            });
            if (searchableField.synyonymEnabled) {
                searchQueries.push({
                    match: {
                        [searchableField.field.replace(/\.[^.]*$/, "")]: {
                            query: searchTerm,
                            fuzziness: searchableField.allowedMistakes,
                            analyzer: "synonym_analyzer",
                        },
                    },
                });
            }
        }
    }
    const searchScores = [];
    for (const searchableField of options.searchableFields) {
        const baseField = searchableField.field.replace(/\.[^.]*$/, "");
        for (let edits = 0; edits <= searchableField.allowedMistakes; edits++) {
            // The total points (e.g. 1000) minus the total mistakes (e.g. 3) multiplied by the mistakes multiplier (e.g. 10% of 1000) = 1000 - (3 * (1000 / 10)) = 700;
            const weightMaxEdits = Math.max((searchableField.points -
                searchableField.allowedMistakes *
                    (searchableField.points / (100 / searchableField.mistakePenaltyPercent))) /
                searchTerms.length, 0);
            // The total points (e.g. 1000) divided by the mistakes multiplier (e.g. 10%) = 1000 / 100 = 100;
            const weightSomeEdits = searchableField.points / (100 / searchableField.mistakePenaltyPercent) / searchTerms.length;
            // Which weight to use, weightMaxEdits should be used once and weightSomeEdits every other time
            const weightBase = edits === searchableField.allowedMistakes ? weightMaxEdits : weightSomeEdits;
            for (const searchTerm of searchTerms) {
                // Regular match
                searchScores.push({
                    filter: {
                        match: {
                            [baseField]: {
                                query: searchTerm,
                                fuzziness: edits,
                                analyzer: "regular_analyzer",
                            },
                        },
                    },
                    weight: weightBase,
                });
                // Synonym match _and not_ regular match
                if (searchableField.synyonymEnabled && searchableField.synonymPenaltyPercent > 0) {
                    // The weightBase (e.g. weightMaxEdits 700 or weightSomeEdits 100) multiplied by the synonyms multiplier as a percentage of the whole points (e.g. input 5% = output 95%) = 700 * (95%)
                    const weightSynonyms = weightBase * (1 - searchableField.synonymPenaltyPercent / 100);
                    searchScores.push({
                        filter: {
                            bool: {
                                must_not: {
                                    match: {
                                        [baseField]: {
                                            query: searchTerm,
                                            fuzziness: edits,
                                            analyzer: "regular_analyzer",
                                        },
                                    },
                                },
                                should: {
                                    match: {
                                        [baseField]: {
                                            query: searchTerm,
                                            fuzziness: edits,
                                            // analyzer: "synonym_analyzer"
                                        },
                                    },
                                },
                            },
                        },
                        weight: weightSynonyms,
                    });
                }
            }
        }
    }
    return {
        query: { bool: Object.assign(Object.assign({}, generateMarketsClause(options.markets)), { should: [...searchQueries] }) },
        functions: [
            ...searchScores,
            ...generateBoostingRules({
                boostingRules: options.boostingRules,
                market: options.market,
            }),
        ],
        score_mode: "sum",
        boost_mode: "replace",
    };
};
exports.generateSearchFunctionScore = generateSearchFunctionScore;
const generateCollectionFunctionScore = (options) => {
    const mustClauses = [
        {
            nested: {
                path: "collections",
                query: {
                    term: { ["collections.handle"]: options.collectionHandle },
                },
            },
        },
    ];
    const marketClause = generateMarketsClause(options.markets);
    if (marketClause)
        mustClauses.push(marketClause);
    return {
        query: {
            bool: { must: mustClauses },
        },
        functions: generateBoostingRules({
            boostingRules: options.boostingRules,
            market: options.market,
        }),
        score_mode: "sum",
        boost_mode: "replace",
    };
};
exports.generateCollectionFunctionScore = generateCollectionFunctionScore;
//# sourceMappingURL=queries.js.map