import { debounce, dropRight, get, last, set } from 'lodash';
import { useEffect, useState } from 'react';
import { useJinjaContext } from '../context/JinjaContext.js';
import { JinjaVariable } from '../context/JinjaVariable.js';
import { runCondition, runJinja } from '../python/index.js';
import { isJinjaExpression } from '../utils/jinja.js';
import { FormContextValue } from './FormContextValue.js';
import { FormRepeatableVariable } from './FormVariable.js';
const varPathRegex = /[A-Za-z_](?:[\w_.]|(?:\['?)|(?:'?\]))*/g;
const evaluationMS = 50;
export function useFieldState(variable) {
    const [state, setState] = useState(variable.getState());
    useEffect(() => variable.onChangeState(setState), [variable]);
    return state;
}
export function useFieldValue(variable) {
    const [value, setValue] = useState(variable.get());
    useEffect(() => variable.config.controlled === false ? undefined : variable.onChange(setValue), [variable]);
    return value;
}
export function useIsRepeating(variable) {
    const [value, setValue] = useState(variable instanceof FormRepeatableVariable ? variable.isRepeating() : false);
    useEffect(() => {
        if (variable instanceof FormRepeatableVariable) {
            return variable.onChangeRepeat(setValue);
        }
    }, [variable]);
    return value;
}
async function resolveRenderCondition(variable, context) {
    if (typeof variable.config.shouldRender === 'string') {
        // todo: treat jinja conditions
    }
    else if (variable.config.shouldRender) {
        const leftVar = context.getVariable(variable.config.shouldRender.variable);
        const leftFullValue = await context.getResolvedVariable(leftVar);
        const remainingPath = variable.config.shouldRender.variable.replace(new RegExp(`^${leftVar?.path}\\.?`), '');
        const left = remainingPath ? get(leftFullValue, remainingPath) : leftFullValue;
        const right = variable.config.shouldRender.value;
        let result = false;
        try {
            result = await runCondition(variable.config.shouldRender.operator, left, right);
        }
        catch (error) {
            // eslint-disable-next-line no-console
            console.error(`Field condition failed. Hiding the field named ${variable.name} by default. Cause:`);
            // eslint-disable-next-line no-console
            console.error(error);
        }
        variable.patchState({ visible: result });
    }
}
function attachRenderConditionEvents(variable, context) {
    if (typeof variable.config.shouldRender === 'string') {
        // todo: treat jinja conditions
    }
    else if (variable.config.shouldRender) {
        const conditionVar = context.getVariable(variable.config.shouldRender.variable);
        return conditionVar?.onChange(() => resolveRenderCondition(variable, context));
    }
}
function useVariableSetupDep(ctx) {
    const [currentUpdate, setUpdate] = useState(Math.random());
    useEffect(() => {
        const unwatch = [];
        let current = ctx;
        while (current) {
            unwatch.push(current.onChangeVariableSetup(() => setUpdate(Math.random())));
            current = current.parent;
        }
        return () => unwatch.forEach(u => u());
    }, []);
    return currentUpdate;
}
export function useFieldEffect(variable) {
    const ctx = useJinjaContext();
    const variableSetup = useVariableSetupDep(ctx);
    useEffect(() => {
        if (!(ctx instanceof FormContextValue)) {
            throw new Error('useFieldEffect used outside a JinjaForm.');
        }
        resolveRenderCondition(variable, ctx);
        return attachRenderConditionEvents(variable, ctx);
    }, [variable, variableSetup]);
}
let formInitStatus = 'pending';
let formsInitPromise;
export function useJinjaFormsInit() {
    const [status, setStatus] = useState(formInitStatus);
    // makes sure Python, Jinja and Conditions are ready to run
    useEffect(() => {
        if (status === 'error' || status === 'success')
            return;
        (async () => {
            try {
                // mock values for initializing Python, Jinja and Conditions
                formsInitPromise ??= Promise.all([
                    runJinja('{{ True }}'),
                    runCondition('==', 1, 1),
                ]);
                await formsInitPromise;
                setStatus('success');
                formInitStatus = 'success';
            }
            catch (error) {
                // eslint-disable-next-line no-console
                console.error(error);
                setStatus('error');
                formInitStatus = 'error';
            }
        })();
    }, []);
    return status;
}
function watchJinjaExpression(expression, form, onChange) {
    if (!isJinjaExpression(expression))
        return onChange(expression);
    const unwatch = [];
    const possibleDeps = expression.match(varPathRegex) ?? [];
    const deps = [];
    for (const maybeDep of possibleDeps) {
        const dep = form.getVariable(maybeDep);
        if (dep)
            deps.push(dep);
    }
    const evaluate = debounce(async () => {
        const jinjaContext = {};
        for (const dep of deps)
            set(jinjaContext, dep.path, dep.get());
        const result = await runJinja(expression, jinjaContext);
        onChange(result);
    }, evaluationMS);
    for (const dep of deps) {
        unwatch.push(dep.onChange(evaluate));
    }
    // initializes the expression value
    evaluate();
    return () => unwatch.forEach(u => u());
}
export function useComputedInputsEffect(computed, form) {
    const variableSetup = useVariableSetupDep(form);
    useEffect(() => {
        if (!computed || !form)
            return;
        const unwatch = [];
        computed.forEach(cv => unwatch.push((watchJinjaExpression(cv.expression, form, v => cv.set(v)))));
        return () => unwatch.forEach(u => u?.());
    }, [variableSetup]);
}
export function useWatchJinjaExpression(expression) {
    const context = useJinjaContext();
    const variableSetup = useVariableSetupDep(context);
    const [value, setValue] = useState();
    useEffect(() => {
        if (!expression || !context)
            return;
        return watchJinjaExpression(expression, context, setValue);
    }, [expression, variableSetup]);
    return value;
}
export function useAscendInputsEffect(context) {
    const formFromContext = useJinjaContext();
    const form = context ?? formFromContext;
    useEffect(() => {
        if (!(form instanceof FormContextValue)) {
            // eslint-disable-next-line no-console
            return console.error('useComputedInputsEffect called outside a JinjaForm.');
        }
        const unwatch = [];
        const ascendedVariables = {};
        function setupAscendedVariable(variable, contextName) {
            const parts = variable.path.split('.');
            const parent = dropRight(parts).join('.');
            const name = last(parts) || variable.name;
            ascendedVariables[contextName] ??= {};
            ascendedVariables[contextName][variable.name] ??= new JinjaVariable(name, variable.get(), parent);
            unwatch.push(variable.onChange(value => ascendedVariables[contextName][variable.name].set(value)));
        }
        for (const key in form.variables) {
            for (const variable of form.variables[key]) {
                if (variable.config.ascend)
                    setupAscendedVariable(variable, variable.config.ascend);
            }
        }
        for (const ctxName in ascendedVariables) {
            const ctx = form.closest(ctxName);
            if (ctx) {
                ctx.addVariables(Object.values(ascendedVariables[ctxName]));
            }
            else {
                // eslint-disable-next-line no-console
                console.warn(`A context that doesn't exist (${ctxName}) has been referred by a variable (${Object.keys(ascendedVariables[ctxName]).join(', ')}).`);
            }
        }
        return () => unwatch.forEach(u => u());
    }, []);
}
//# sourceMappingURL=hooks.js.map