"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getConditionalFormattingColor = exports.getConditionalFormattingColorWithSingleColor = exports.getConditionalFormattingColorWithColorRange = exports.getConditionalFormattingDescription = exports.getConditionalFormattingConfig = exports.hasMatchingConditionalRules = exports.ConditionalFormattingError = exports.getMinMaxFromMinMaxMap = exports.convertFormattedValue = exports.hasPercentageFormat = exports.createConditionalFormattingConfigWithColorRange = exports.createConditionalFormattingConfigWithSingleColor = exports.createConditionalFormattingRuleWithCompareTargetValues = exports.createConditionalFormattingRuleWithCompareTarget = exports.createConditionalFormattingRuleWithValues = void 0;
const tslib_1 = require("tslib");
const findLast_1 = tslib_1.__importDefault(require("lodash/findLast"));
const uuid_1 = require("uuid");
const conditionalFormatting_1 = require("../types/conditionalFormatting");
const errors_1 = require("../types/errors");
const field_1 = require("../types/field");
const filter_1 = require("../types/filter");
const assertUnreachable_1 = tslib_1.__importDefault(require("./assertUnreachable"));
const item_1 = require("./item");
const createConditionalFormattingRuleWithValues = () => ({
    id: (0, uuid_1.v4)(),
    operator: filter_1.FilterOperator.EQUALS,
    values: [],
});
exports.createConditionalFormattingRuleWithValues = createConditionalFormattingRuleWithValues;
const createConditionalFormattingRuleWithCompareTarget = () => ({
    id: (0, uuid_1.v4)(),
    operator: filter_1.FilterOperator.EQUALS,
    compareTarget: null,
});
exports.createConditionalFormattingRuleWithCompareTarget = createConditionalFormattingRuleWithCompareTarget;
const createConditionalFormattingRuleWithCompareTargetValues = () => ({
    id: (0, uuid_1.v4)(),
    operator: filter_1.FilterOperator.EQUALS,
    compareTarget: null,
    values: [],
});
exports.createConditionalFormattingRuleWithCompareTargetValues = createConditionalFormattingRuleWithCompareTargetValues;
const createConditionalFormattingConfigWithSingleColor = (defaultColor, target = null) => ({
    target,
    color: defaultColor,
    rules: [(0, exports.createConditionalFormattingRuleWithValues)()],
});
exports.createConditionalFormattingConfigWithSingleColor = createConditionalFormattingConfigWithSingleColor;
const createConditionalFormattingConfigWithColorRange = (defaultColor, target = null) => ({
    target,
    color: {
        start: '#ffffff',
        end: defaultColor,
    },
    rule: {
        min: 0,
        max: 100,
    },
});
exports.createConditionalFormattingConfigWithColorRange = createConditionalFormattingConfigWithColorRange;
const hasPercentageFormat = (field) => {
    if (!field)
        return false;
    return (((0, field_1.isField)(field) && field?.format === field_1.Format.PERCENT) ||
        ((0, field_1.isTableCalculation)(field) &&
            field.format?.type === field_1.CustomFormatType.PERCENT));
};
exports.hasPercentageFormat = hasPercentageFormat;
const convertFormattedValue = (value, field) => {
    if (!field)
        return value;
    if ((0, exports.hasPercentageFormat)(field)) {
        return typeof value === 'number' ? value * 100 : value;
    }
    return value;
};
exports.convertFormattedValue = convertFormattedValue;
const getMinMaxFromMinMaxMap = (minMaxMap) => ({
    min: Math.min(...Object.values(minMaxMap).map((m) => m.min)),
    max: Math.max(...Object.values(minMaxMap).map((m) => m.max)),
});
exports.getMinMaxFromMinMaxMap = getMinMaxFromMinMaxMap;
class ConditionalFormattingError extends errors_1.LightdashError {
    constructor(message) {
        super({
            message,
            name: 'ConditionalFormattingError',
            statusCode: 400,
            data: {},
        });
    }
}
exports.ConditionalFormattingError = ConditionalFormattingError;
const hasMatchingConditionalRules = (field, value, minMaxMap, config, rowFields) => {
    if (!config)
        return false;
    const parsedValue = (0, item_1.isNumericItem)(field) ? Number(value) : value;
    const convertedValue = (0, exports.convertFormattedValue)(parsedValue, field);
    const currentFieldId = (0, item_1.getItemId)(field);
    const targetFieldId = config.target?.fieldId;
    if (targetFieldId !== undefined && targetFieldId !== currentFieldId) {
        return false;
    }
    if ((0, conditionalFormatting_1.isConditionalFormattingConfigWithSingleColor)(config)) {
        return config.rules.every((rule) => {
            let convertedCompareValue;
            let compareField;
            if ((0, conditionalFormatting_1.isConditionalFormattingWithCompareTarget)(rule) &&
                rule.compareTarget?.fieldId &&
                rowFields) {
                const rowField = rowFields[rule.compareTarget.fieldId];
                if (!rowField)
                    return false;
                compareField = rowField.field;
                const parsedCompareValue = (0, item_1.isNumericItem)(compareField)
                    ? Number(rowField.value)
                    : rowField.value;
                convertedCompareValue = (0, exports.convertFormattedValue)(parsedCompareValue, compareField);
            }
            // Compares field value to values when there is no compare target
            const shouldCompareFieldToValue = (0, conditionalFormatting_1.isConditionalFormattingWithValues)(rule) &&
                !(0, conditionalFormatting_1.isConditionalFormattingWithCompareTarget)(rule);
            // Compares field value to compare target when there is a compare target and there are no values
            const shouldCompareFieldToTarget = (0, conditionalFormatting_1.isConditionalFormattingWithCompareTarget)(rule) &&
                !(0, conditionalFormatting_1.isConditionalFormattingWithValues)(rule);
            // Compares compare target value to values when there is a compare target and there are values
            const shouldCompareTargetToValue = (0, conditionalFormatting_1.isConditionalFormattingWithCompareTarget)(rule) &&
                (0, conditionalFormatting_1.isConditionalFormattingWithValues)(rule);
            switch (rule.operator) {
                case filter_1.FilterOperator.NULL:
                    return value === null;
                case filter_1.FilterOperator.NOT_NULL:
                    return value !== null;
                case filter_1.FilterOperator.EQUALS:
                    if (shouldCompareFieldToValue) {
                        return rule.values.some((v) => convertedValue === v);
                    }
                    if (shouldCompareFieldToTarget) {
                        return convertedValue === convertedCompareValue;
                    }
                    if (shouldCompareTargetToValue) {
                        return rule.values.some((v) => convertedCompareValue === v);
                    }
                    throw new errors_1.NotImplementedError();
                case filter_1.FilterOperator.NOT_EQUALS:
                    if (shouldCompareFieldToValue) {
                        return rule.values.some((v) => convertedValue !== v);
                    }
                    if (shouldCompareFieldToTarget) {
                        return convertedValue !== convertedCompareValue;
                    }
                    if (shouldCompareTargetToValue) {
                        return rule.values.some((v) => convertedCompareValue !== v);
                    }
                    throw new errors_1.NotImplementedError();
                case filter_1.FilterOperator.LESS_THAN:
                    if (shouldCompareFieldToValue) {
                        return rule.values.some((v) => (0, item_1.isNumericItem)(field) &&
                            typeof v === 'number' &&
                            typeof convertedValue === 'number' &&
                            convertedValue < v);
                    }
                    if (shouldCompareFieldToTarget) {
                        return ((0, item_1.isNumericItem)(field) &&
                            (0, item_1.isNumericItem)(compareField) &&
                            typeof convertedCompareValue === 'number' &&
                            typeof convertedValue === 'number' &&
                            convertedValue < convertedCompareValue);
                    }
                    if (shouldCompareTargetToValue) {
                        return rule.values.some((v) => (0, item_1.isNumericItem)(compareField) &&
                            typeof v === 'number' &&
                            typeof convertedCompareValue === 'number' &&
                            convertedCompareValue < v);
                    }
                    throw new errors_1.NotImplementedError();
                case filter_1.FilterOperator.GREATER_THAN:
                    if (shouldCompareFieldToValue) {
                        return rule.values.some((v) => (0, item_1.isNumericItem)(field) &&
                            typeof v === 'number' &&
                            typeof convertedValue === 'number' &&
                            convertedValue > v);
                    }
                    if (shouldCompareFieldToTarget) {
                        return ((0, item_1.isNumericItem)(field) &&
                            (0, item_1.isNumericItem)(compareField) &&
                            typeof convertedCompareValue === 'number' &&
                            typeof convertedValue === 'number' &&
                            convertedValue > convertedCompareValue);
                    }
                    if (shouldCompareTargetToValue) {
                        return rule.values.some((v) => (0, item_1.isNumericItem)(compareField) &&
                            typeof v === 'number' &&
                            typeof convertedCompareValue === 'number' &&
                            convertedCompareValue > v);
                    }
                    throw new errors_1.NotImplementedError();
                case filter_1.FilterOperator.STARTS_WITH:
                case filter_1.FilterOperator.ENDS_WITH:
                case filter_1.FilterOperator.INCLUDE:
                    if (shouldCompareFieldToValue) {
                        return rule.values.some((v) => (0, item_1.isStringDimension)(field) &&
                            typeof v === 'string' &&
                            typeof convertedValue === 'string' &&
                            convertedValue.includes(v));
                    }
                    if (shouldCompareFieldToTarget) {
                        return ((0, item_1.isStringDimension)(field) &&
                            (0, item_1.isStringDimension)(compareField) &&
                            typeof convertedValue === 'string' &&
                            typeof convertedCompareValue === 'string' &&
                            convertedValue.includes(convertedCompareValue));
                    }
                    if (shouldCompareTargetToValue) {
                        return rule.values.some((v) => (0, item_1.isStringDimension)(compareField) &&
                            typeof v === 'string' &&
                            typeof convertedCompareValue === 'string' &&
                            convertedCompareValue.includes(v));
                    }
                    throw new errors_1.NotImplementedError();
                case filter_1.FilterOperator.IN_BETWEEN:
                    if ((0, item_1.isStringDimension)(field)) {
                        throw new ConditionalFormattingError(`String dimensions are not supported for conditional formatting with ${rule.operator}`);
                    }
                    if (shouldCompareFieldToValue) {
                        if (typeof convertedValue !== 'number') {
                            throw new ConditionalFormattingError(`Conditional formatting with ${rule.operator} requires a numeric value`);
                        }
                        const ruleValues = rule.values;
                        if (ruleValues.length !== 2 ||
                            typeof ruleValues[0] !== 'number' ||
                            typeof ruleValues[1] !== 'number') {
                            return false;
                        }
                        return (convertedValue >= ruleValues[0] &&
                            convertedValue <= ruleValues[1]);
                    }
                    if (shouldCompareFieldToTarget) {
                        throw new ConditionalFormattingError(`Conditional formatting with ${rule.operator} does not support compare targets`);
                    }
                    if (shouldCompareTargetToValue) {
                        throw new ConditionalFormattingError(`Conditional formatting with ${rule.operator} does not support target values`);
                    }
                    // should never happen
                    return false;
                case filter_1.FilterOperator.NOT_IN_BETWEEN:
                    if ((0, item_1.isStringDimension)(field)) {
                        throw new ConditionalFormattingError(`String dimensions are not supported for conditional formatting with ${rule.operator}`);
                    }
                    if (shouldCompareFieldToValue) {
                        if (typeof convertedValue !== 'number') {
                            throw new ConditionalFormattingError(`Conditional formatting with ${rule.operator} requires a numeric value`);
                        }
                        const ruleValues = rule.values;
                        if (ruleValues.length !== 2 ||
                            typeof ruleValues[0] !== 'number' ||
                            typeof ruleValues[1] !== 'number') {
                            return false;
                        }
                        return (convertedValue < ruleValues[0] ||
                            convertedValue > ruleValues[1]);
                    }
                    if (shouldCompareTargetToValue) {
                        throw new ConditionalFormattingError(`Conditional formatting with ${rule.operator} does not support target values`);
                    }
                    if (shouldCompareFieldToTarget) {
                        throw new ConditionalFormattingError(`Conditional formatting with ${rule.operator} does not support compare targets`);
                    }
                    // should never happen
                    return false;
                case filter_1.FilterOperator.LESS_THAN_OR_EQUAL:
                    if (shouldCompareFieldToValue) {
                        return rule.values.some((v) => (0, item_1.isNumericItem)(field) &&
                            typeof v === 'number' &&
                            typeof convertedValue === 'number' &&
                            convertedValue <= v);
                    }
                    if (shouldCompareFieldToTarget) {
                        return ((0, item_1.isNumericItem)(field) &&
                            (0, item_1.isNumericItem)(compareField) &&
                            typeof convertedCompareValue === 'number' &&
                            typeof convertedValue === 'number' &&
                            convertedValue <= convertedCompareValue);
                    }
                    if (shouldCompareTargetToValue) {
                        return rule.values.some((v) => (0, item_1.isNumericItem)(compareField) &&
                            typeof v === 'number' &&
                            typeof convertedCompareValue === 'number' &&
                            convertedCompareValue <= v);
                    }
                    throw new errors_1.NotImplementedError();
                case filter_1.FilterOperator.GREATER_THAN_OR_EQUAL:
                    if (shouldCompareFieldToValue) {
                        return rule.values.some((v) => (0, item_1.isNumericItem)(field) &&
                            typeof v === 'number' &&
                            typeof convertedValue === 'number' &&
                            convertedValue >= v);
                    }
                    if (shouldCompareFieldToTarget) {
                        return ((0, item_1.isNumericItem)(field) &&
                            (0, item_1.isNumericItem)(compareField) &&
                            typeof convertedCompareValue === 'number' &&
                            typeof convertedValue === 'number' &&
                            convertedValue >= convertedCompareValue);
                    }
                    if (shouldCompareTargetToValue) {
                        return rule.values.some((v) => (0, item_1.isNumericItem)(compareField) &&
                            typeof v === 'number' &&
                            typeof convertedCompareValue === 'number' &&
                            convertedCompareValue >= v);
                    }
                    throw new errors_1.NotImplementedError();
                case filter_1.FilterOperator.NOT_INCLUDE:
                case filter_1.FilterOperator.IN_THE_PAST:
                case filter_1.FilterOperator.NOT_IN_THE_PAST:
                case filter_1.FilterOperator.IN_THE_NEXT:
                case filter_1.FilterOperator.IN_THE_CURRENT:
                case filter_1.FilterOperator.NOT_IN_THE_CURRENT:
                case filter_1.FilterOperator.IN_PERIOD_TO_DATE:
                    throw new errors_1.NotImplementedError(`Conditional formatting with ${rule.operator} is not implemented`);
                default:
                    return (0, assertUnreachable_1.default)(rule.operator, 'Unknown operator for conditional formatting');
            }
        });
    }
    if ((0, conditionalFormatting_1.isConditionalFormattingConfigWithColorRange)(config)) {
        if (typeof convertedValue !== 'number')
            return false;
        let min;
        let max;
        if (config.rule.min === 'auto') {
            min =
                targetFieldId && targetFieldId in minMaxMap
                    ? minMaxMap[targetFieldId].min
                    : (0, exports.getMinMaxFromMinMaxMap)(minMaxMap).min;
        }
        else {
            min = config.rule.min;
        }
        if (config.rule.max === 'auto') {
            max =
                targetFieldId && targetFieldId in minMaxMap
                    ? minMaxMap[targetFieldId].max
                    : (0, exports.getMinMaxFromMinMaxMap)(minMaxMap).max;
        }
        else {
            max = config.rule.max;
        }
        return convertedValue >= min && convertedValue <= max;
    }
    return (0, assertUnreachable_1.default)(config, 'Unknown conditional formatting config');
};
exports.hasMatchingConditionalRules = hasMatchingConditionalRules;
const getConditionalFormattingConfig = ({ field, value, minMaxMap = {}, conditionalFormattings, rowFields, }) => {
    // For backwards compatibility with old table calculations without type
    const isCalculationTypeUndefined = field && (0, field_1.isTableCalculation)(field) && field.type === undefined;
    if (!conditionalFormattings ||
        !field ||
        (!(0, item_1.isNumericItem)(field) &&
            !(0, item_1.isStringDimension)(field) &&
            !isCalculationTypeUndefined))
        return undefined;
    return (0, findLast_1.default)(conditionalFormattings, (config) => (0, exports.hasMatchingConditionalRules)(field, value, minMaxMap, config, rowFields));
};
exports.getConditionalFormattingConfig = getConditionalFormattingConfig;
const getConditionalFormattingDescription = (field, conditionalFormattingConfig, rowFields, getConditionalRuleLabel) => {
    if (!field || !(0, field_1.isFilterableItem)(field) || !conditionalFormattingConfig) {
        return undefined;
    }
    if ((0, conditionalFormatting_1.isConditionalFormattingConfigWithColorRange)(conditionalFormattingConfig)) {
        return [
            `is greater than or equal to ${conditionalFormattingConfig.rule.min === 'auto'
                ? 'min value in table'
                : conditionalFormattingConfig.rule.min}`,
            `is less than or equal to ${conditionalFormattingConfig.rule.max === 'auto'
                ? 'max value in table'
                : conditionalFormattingConfig.rule.max}`,
        ].join(' and ');
    }
    if ((0, conditionalFormatting_1.isConditionalFormattingConfigWithSingleColor)(conditionalFormattingConfig)) {
        return conditionalFormattingConfig.rules
            .map((r) => {
            const fieldLabel = getConditionalRuleLabel(r, field);
            if ((0, conditionalFormatting_1.isConditionalFormattingWithCompareTarget)(r)) {
                const compareRowField = r.compareTarget?.fieldId
                    ? rowFields[r.compareTarget?.fieldId]
                    : undefined;
                if (!compareRowField ||
                    !(0, field_1.isFilterableItem)(compareRowField.field)) {
                    return fieldLabel;
                }
                // If there are no values, then the field is being compared to the compare target
                if (!(0, conditionalFormatting_1.isConditionalFormattingWithValues)(r)) {
                    return {
                        ...fieldLabel,
                        value: String(compareRowField.value),
                    };
                }
                // If there are values, then the target is being compared to the values
                return {
                    ...getConditionalRuleLabel(r, compareRowField.field),
                    isComparingTargetToValues: true,
                };
            }
            return fieldLabel;
        })
            .map((l) => l.isComparingTargetToValues
            ? `${l.field} ${l.operator} ${l.value}`
            : `${l.operator} ${l.value}`)
            .join(' and ');
    }
    return (0, assertUnreachable_1.default)(conditionalFormattingConfig, 'Unknown conditional formatting config');
};
exports.getConditionalFormattingDescription = getConditionalFormattingDescription;
const getConditionalFormattingColorWithColorRange = ({ field, value, config, minMaxMap = {}, getColorFromRange, }) => {
    if (!field)
        return undefined;
    const numericValue = typeof value === 'string' ? parseFloat(value) : value;
    const convertedValue = (0, exports.convertFormattedValue)(numericValue, field);
    const currentFieldId = (0, item_1.getItemId)(field);
    const targetFieldId = config.target?.fieldId;
    if (targetFieldId !== undefined && targetFieldId !== currentFieldId) {
        return undefined;
    }
    if (typeof convertedValue !== 'number')
        return undefined;
    let min;
    let max;
    if (config.rule.min === 'auto') {
        min =
            targetFieldId && targetFieldId in minMaxMap
                ? minMaxMap[targetFieldId].min
                : (0, exports.getMinMaxFromMinMaxMap)(minMaxMap).min;
    }
    else {
        min = config.rule.min;
    }
    if (config.rule.max === 'auto') {
        max =
            targetFieldId && targetFieldId in minMaxMap
                ? minMaxMap[targetFieldId].max
                : (0, exports.getMinMaxFromMinMaxMap)(minMaxMap).max;
    }
    else {
        max = config.rule.max;
    }
    const color = getColorFromRange(convertedValue, config.color, { min, max });
    if (!color)
        return undefined;
    return {
        color,
        applyTo: config.applyTo ?? conditionalFormatting_1.ConditionalFormattingColorApplyTo.CELL,
    };
};
exports.getConditionalFormattingColorWithColorRange = getConditionalFormattingColorWithColorRange;
const getConditionalFormattingColorWithSingleColor = ({ config, }) => ({
    color: config.color,
    applyTo: config.applyTo ?? conditionalFormatting_1.ConditionalFormattingColorApplyTo.CELL,
});
exports.getConditionalFormattingColorWithSingleColor = getConditionalFormattingColorWithSingleColor;
const getConditionalFormattingColor = ({ field, value, config, minMaxMap, getColorFromRange, }) => {
    if (!config)
        return undefined;
    if ((0, conditionalFormatting_1.isConditionalFormattingConfigWithSingleColor)(config)) {
        return (0, exports.getConditionalFormattingColorWithSingleColor)({ config });
    }
    return (0, exports.getConditionalFormattingColorWithColorRange)({
        field,
        value,
        config,
        minMaxMap,
        getColorFromRange,
    });
};
exports.getConditionalFormattingColor = getConditionalFormattingColor;
//# sourceMappingURL=conditionalFormatting.js.map