"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateConditionalBlocks = validateConditionalBlocks;
exports.validateVariableOrder = validateVariableOrder;
const errors_1 = require("../errors");
function bracketResult(options, path, message, code) {
    return options.strict
        ? (0, errors_1.errorResult)(path, message, code)
        : (0, errors_1.warningResult)(path, message, code);
}
function getStepTypeName(step) {
    if (typeof step !== 'object' || !step || Array.isArray(step)) {
        return undefined;
    }
    const keys = Object.keys(step);
    return keys.length === 1 ? keys[0] : undefined;
}
const CONDITIONAL_OPENERS = new Set(['If']);
const CONDITIONAL_BRANCHES = new Set(['ElseIf', 'Else']);
const CONDITIONAL_CLOSERS = new Set(['End']);
function validateConditionalBlocks(steps, options = {}) {
    const results = [];
    const stack = [];
    for (let i = 0; i < steps.length; i++) {
        const typeName = getStepTypeName(steps[i]);
        if (typeName === undefined) {
            continue;
        }
        if (CONDITIONAL_OPENERS.has(typeName)) {
            stack.push({ ifIndex: i, hasElse: false });
        }
        else if (CONDITIONAL_BRANCHES.has(typeName)) {
            if (stack.length === 0) {
                results.push(bracketResult(options, ['steps', i], `"${typeName}" without a preceding "If"`, 'cross_step.orphan_branch'));
                continue;
            }
            const current = stack[stack.length - 1];
            if (typeName === 'Else') {
                if (current.hasElse) {
                    results.push((0, errors_1.errorResult)(['steps', i], 'Duplicate "Else" in the same If block', 'cross_step.duplicate_else'));
                }
                current.hasElse = true;
            }
            else if (typeName === 'ElseIf') {
                if (current.hasElse) {
                    results.push((0, errors_1.errorResult)(['steps', i], '"ElseIf" after "Else" is not allowed — "Else" must be the last branch before "End"', 'cross_step.elseif_after_else'));
                }
            }
        }
        else if (CONDITIONAL_CLOSERS.has(typeName)) {
            if (stack.length === 0) {
                results.push(bracketResult(options, ['steps', i], '"End" without a matching "If"', 'cross_step.orphan_end'));
            }
            else {
                stack.pop();
            }
        }
        if (typeName === 'StepGroup') {
            const descriptor = steps[i][typeName];
            if (descriptor && Array.isArray(descriptor.steps)) {
                const nestedResult = validateConditionalBlocks(descriptor.steps, options);
                for (const err of nestedResult.errors) {
                    err.path = ['steps', i, 'StepGroup', ...err.path];
                }
                for (const warn of nestedResult.warnings) {
                    warn.path = ['steps', i, 'StepGroup', ...warn.path];
                }
                results.push(nestedResult);
            }
        }
    }
    for (const open of stack) {
        results.push(bracketResult(options, ['steps', open.ifIndex], '"If" without a matching "End"', 'cross_step.unclosed_if'));
    }
    return results.length > 0 ? (0, errors_1.mergeResults)(...results) : (0, errors_1.emptyResult)();
}
const VARIABLE_REF_REGEX = /\{\{@([^}]+)\}\}/g;
function findVariableReferences(obj) {
    const refs = new Set();
    if (typeof obj === 'string') {
        let match;
        VARIABLE_REF_REGEX.lastIndex = 0;
        while ((match = VARIABLE_REF_REGEX.exec(obj))) {
            refs.add(match[1]);
        }
    }
    else if (Array.isArray(obj)) {
        for (const item of obj) {
            for (const ref of findVariableReferences(item)) {
                refs.add(ref);
            }
        }
    }
    else if (typeof obj === 'object' && obj) {
        for (const value of Object.values(obj)) {
            for (const ref of findVariableReferences(value)) {
                refs.add(ref);
            }
        }
    }
    return refs;
}
function getCreatedVariableName(step) {
    const typeName = getStepTypeName(step);
    if (typeName !== 'CreateVariable' && typeName !== 'GenerateEmailAddress') {
        return undefined;
    }
    const descriptor = step[typeName];
    if (typeof descriptor?.name === 'string') {
        return descriptor.name;
    }
    return undefined;
}
function validateVariableOrder(steps) {
    const results = [];
    const creationIndex = new Map();
    for (let i = 0; i < steps.length; i++) {
        const varName = getCreatedVariableName(steps[i]);
        if (varName !== undefined && !creationIndex.has(varName)) {
            creationIndex.set(varName, i);
        }
    }
    for (let i = 0; i < steps.length; i++) {
        const typeName = getStepTypeName(steps[i]);
        if (typeName === undefined) {
            continue;
        }
        const descriptor = steps[i][typeName];
        if (!descriptor) {
            continue;
        }
        const refs = findVariableReferences(descriptor);
        for (const varName of refs) {
            const createIdx = creationIndex.get(varName);
            if (createIdx !== undefined && createIdx > i) {
                results.push((0, errors_1.warningResult)(['steps', i, typeName], `Variable "{{@${varName}}}" is referenced before it is created (created at step ${createIdx}).`, 'cross_step.variable_before_creation'));
            }
        }
        if (typeName === 'StepGroup' && Array.isArray(descriptor.steps)) {
            const nestedResult = validateVariableOrder(descriptor.steps);
            for (const warn of nestedResult.warnings) {
                warn.path = ['steps', i, 'StepGroup', ...warn.path];
            }
            for (const err of nestedResult.errors) {
                err.path = ['steps', i, 'StepGroup', ...err.path];
            }
            results.push(nestedResult);
        }
    }
    return results.length > 0 ? (0, errors_1.mergeResults)(...results) : (0, errors_1.emptyResult)();
}
