import { Validator, Jexl, ValidationSeverity } from '@pawel-up/jexl';
import * as stringFunctions from '@pawel-up/jexl/string.js';
import * as mathFunctions from '@pawel-up/jexl/math.js';
import * as dateFunctions from '@pawel-up/jexl/date.js';
import * as arrayFunctions from '@pawel-up/jexl/array.js';
import * as operatorFunctions from '@pawel-up/jexl/operators.js';
function addModule(jexl, module, prefix = '') {
    Object.keys(module).forEach((key) => {
        const functionName = prefix ? `${prefix}.${key}` : key;
        jexl.addFunction(functionName, module[key]);
    });
}
/**
 * A class to parse and validate formulas from the calculated fields.
 */
export class FormulaParser {
    formula;
    property;
    /**
     * A map of properties available in the entity.
     * The keys are property keys, and the values are DomainProperty objects.
     * This allows for quick lookup of properties by their keys.
     */
    properties;
    constructor(formula, property, properties = []) {
        this.formula = formula;
        this.property = property;
        this.properties = new Map(properties.map((p) => [p.key, p]));
    }
    /**
     * Parses the formula and returns the validation result.
     */
    validate() {
        const defaultValue = { valid: true, errors: [], info: [], issues: [], warnings: [] };
        if (!this.formula.trim()) {
            return defaultValue;
        }
        const expression = this.getJexlExpression();
        const jexl = new Jexl();
        // Register appropriate functions for the data type
        this.registerFunctions(jexl);
        const validator = new Validator(jexl.grammar);
        try {
            // TODO: we need to extract variables from the text field and (possibly) add a context.
            return validator.validate(expression, undefined, { allowUndefinedContext: true });
        }
        catch (e) {
            defaultValue.valid = false;
            defaultValue.errors.push({
                message: e.message,
                severity: ValidationSeverity.ERROR,
            });
            return defaultValue;
        }
    }
    async test(dependencies) {
        if (!this.formula.trim()) {
            return {
                error: true,
                value: undefined,
                message: 'Formula is empty.',
            };
        }
        const expression = this.getJexlExpression();
        const context = this.buildTestContext(dependencies);
        const jexl = new Jexl();
        // Register appropriate functions for the data type
        this.registerFunctions(jexl);
        const result = { error: false, value: undefined };
        // TODO: evaluate array and enum values.
        // For arrays, we return the results as it would be evaluated normally,
        // but we ensure the result is an array.
        // For enums, we render value if the resulting value is one of the defined values,
        // or otherwise we render error.
        const dataType = this.property.type;
        try {
            if (dataType === 'string') {
                result.value = await jexl.evalAsString(expression, context);
            }
            else if (dataType === 'number') {
                result.value = await jexl.evalAsNumber(expression, context);
            }
            else if (dataType === 'boolean') {
                result.value = await jexl.evalAsBoolean(expression, context);
            }
            else {
                result.value = await jexl.eval(expression, context);
            }
        }
        catch (e) {
            result.error = true;
            result.message = e.message;
        }
        return result;
    }
    getJexlExpression() {
        // For all types, we treat the input as a direct Jexl expression.
        // The user is responsible for providing a valid Jexl expression, including quoting string literals.
        // We just need to replace the mention syntax `{{field_name}}` with the Jexl identifier `property_key`.
        return this.formula.replace(/\{\{([^}]+)\}\}/g, (match, id) => {
            const prop = this.properties.get(id);
            if (prop) {
                // we use column names as identifiers in Jexl expressions
                // the key is for fallback if the name is not set
                return prop.info.name || prop.key;
            }
            return match;
        });
    }
    /**
     * Builds a test context for evaluating the formula.
     * This context includes the values of the properties that are dependencies of the formula.
     * @param dependencies The list of property keys that are dependencies in the formula.
     * @returns An object containing the property values for testing.
     */
    buildTestContext(dependencies) {
        const context = {};
        if (!dependencies || !dependencies.length) {
            return context;
        }
        for (const dep of dependencies || []) {
            const prop = this.properties.get(dep);
            if (prop) {
                context[prop.info.name || prop.key] = prop.toExample('application/json', {
                    renderExamples: true,
                    renderOptional: true,
                    renderMocked: true,
                });
            }
        }
        return context;
    }
    /**
     * Registers appropriate Jexl functions based on the data type.
     * @param jexl The Jexl instance to register functions on.
     */
    registerFunctions(jexl) {
        addModule(jexl, stringFunctions);
        addModule(jexl, mathFunctions);
        addModule(jexl, dateFunctions);
        addModule(jexl, arrayFunctions);
        addModule(jexl, operatorFunctions);
    }
    /**
     * Extracts property dependencies from the formula.
     * This method looks for {{field_name}} patterns in the original formula string.
     * @returns The list of unique dependency keys.
     */
    extractDependencies() {
        const dependencies = new Set();
        const mentionPattern = /\{\{([^}]+)\}\}/g;
        let match;
        while ((match = mentionPattern.exec(this.formula)) !== null) {
            const fieldKey = match[1];
            if (this.properties.has(fieldKey)) {
                dependencies.add(fieldKey);
            }
        }
        return Array.from(dependencies);
    }
}
//# sourceMappingURL=formula_parser.js.map