"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.explainDatePredicate = explainDatePredicate;
exports.explainPropPredicate = explainPropPredicate;
exports.explainMatchOptionPredicate = explainMatchOptionPredicate;
exports.checkAny = checkAny;
exports.checkAll = checkAll;
exports.checkNot = checkNot;
const util_1 = require("../../util");
const arcgis_rest_portal_1 = require("@esri/arcgis-rest-portal");
const get_with_default_1 = require("../../objects/get-with-default");
const get_prop_1 = require("../../objects/get-prop");
function explainDatePredicate(_predicate, _result, _requestOptions) {
    // get the value from the predicate
    throw new Error("Not implemented");
}
function explainPropPredicate(_predicate, _result, _requestOptions) {
    // get the value from the predicate
    throw new Error("Not implemented");
}
/**
 * @internal
 * Create explanation for an IMatchOptions predicate
 * @param predicate
 * @param result
 * @param requestOptions
 * @returns
 */
async function explainMatchOptionPredicate(predicate, result, requestOptions) {
    // get the key from the predicate
    const attribute = Object.keys(predicate)[0];
    const matchOptions = predicate[attribute];
    const explanation = {
        predicate: (0, util_1.cloneObject)(predicate),
        matched: true,
        reasons: [],
    };
    // Construct meta hash that will allow us to carry additional info about the match
    const meta = {};
    // TODO Implement when needed
    // if (attribute === "orgid") {
    //   // fetch org into and attach the id + name into the meta hash
    // }
    if (attribute === "group") {
        // fetch items groups and attach as `group` prop
        const response = await (0, arcgis_rest_portal_1.getItemGroups)(result.id, requestOptions);
        // ----------------------------------------
        // TODO: We've got a bunch of useful group info in the response, but only use the ids for the explanation
        // However, to provide the user a useful explanation, we should include the group titles, and maybe even the group owners
        // ----------------------------------------
        // map out the id and titles into meta.groups
        meta.groups = [
            ...(0, get_with_default_1.getWithDefault)(response, "admin", []),
            ...(0, get_with_default_1.getWithDefault)(response, "member", []),
            ...(0, get_with_default_1.getWithDefault)(response, "other", []),
        ].map((g) => ({ id: g.id, title: g.title }));
        // and the id's into the match options attribute `group`
        result.group = meta.groups.map((g) => g.id);
    }
    // get the value of the key, from the result
    const resultValue = (0, get_prop_1.getProp)(result, attribute);
    if (resultValue) {
        // check the any, all, not, exact props if defined
        const fns = {
            any: checkAny,
            all: checkAll,
            not: checkNot,
        };
        // for each prop, if defined, call the appropriate check function
        Object.keys(fns).forEach((prop) => {
            const conditionToCheck = (0, get_prop_1.getProp)(matchOptions, prop);
            if (conditionToCheck) {
                const fn = (0, get_prop_1.getProp)(fns, prop);
                const r = fn(attribute, conditionToCheck, resultValue);
                r.meta = meta;
                explanation.reasons.push(r);
            }
        });
        // decide if all the predicates matched
        explanation.matched = explanation.reasons.every((r) => r.matched);
    }
    else {
        explanation.matched = false;
        explanation.reasons.push({
            attribute,
            matched: false,
            message: `Property ${attribute} not present on search result, cannot provide explanation`,
        });
    }
    return Promise.resolve(explanation);
}
/**
 * Returns information about the match between a result value and the .any property of an IMatchOptions predicate
 * @param attribute
 * @param option
 * @param resultValue
 * @returns
 */
function checkAny(attribute, option, resultValue) {
    const result = {
        attribute,
        values: arrayify(resultValue).join(","),
        condition: "IN",
        matched: false,
        requirement: arrayify(option).join(","),
        message: "No match",
    };
    const { matches } = getMatches(arrayify(option), arrayify(resultValue));
    result.matched = matches.length > 0;
    if (result.matched) {
        result.message = `Value(s) ${result.values} contained at least one of value from [${result.requirement}]`;
    }
    else {
        result.message = `Value(s) ${result.values} did not contain any of value from [${result.requirement}]`;
    }
    return result;
}
/**
 * * Returns information about the match between a result value and the .all property of an IMatchOptions predicate
 * @param attribute
 * @param option
 * @param resultValue
 * @returns
 */
function checkAll(attribute, option, resultValue) {
    const result = {
        attribute,
        values: arrayify(resultValue).join(","),
        condition: "ALL",
        requirement: arrayify(option).join(","),
        matched: false,
        message: "No match",
    };
    const { options, matches } = getMatches(arrayify(option), arrayify(resultValue));
    result.matched = matches.length === options.length;
    if (result.matched) {
        result.message = `Value(s) ${result.values} contained all values from [${result.requirement}]`;
    }
    else {
        result.message = `Value(s) ${result.values} did not contain all values from [${result.requirement}]`;
    }
    return result;
}
/**
 * * Returns information about the match between a result value and the .not property of an IMatchOptions predicate
 * @param attribute
 * @param option
 * @param resultValue
 * @returns
 */
function checkNot(attribute, option, resultValue) {
    const result = {
        attribute,
        values: arrayify(resultValue).join(","),
        condition: "NOT_IN",
        requirement: arrayify(option).join(","),
        matched: false,
        message: "No match",
    };
    const { matches } = getMatches(arrayify(option), arrayify(resultValue));
    result.matched = matches.length === 0;
    if (result.matched) {
        result.message = `Value(s) ${result.values} is not contained in [${result.requirement}]`;
    }
    else {
        result.message = `Value(s) ${result.values} is contained in [${result.requirement}]`;
    }
    return result;
}
/**
 * Ensure a value that could be `string | string[]` is a `string[]`
 * @param value
 * @returns
 */
function arrayify(value) {
    if (!Array.isArray(value)) {
        return [value];
    }
    return value;
}
/**
 * Return matching values from two arrays
 * @param options
 * @param resultValues
 * @returns
 */
function getMatches(options, resultValues) {
    const result = {
        options,
        values: resultValues,
        matches: [],
    };
    result.matches = options.reduce((acc, o) => {
        if (resultValues.includes(o)) {
            acc.push(o);
        }
        return acc;
    }, []);
    return result;
}
//# sourceMappingURL=explainHelpers.js.map