/**
 * Type checking validator for Acurity Architect.
 * Validates type compatibility in assignments.
 *
 * Phase 3 of the expression tree plan: uses the Pratt parser + expressionTypeInference
 * to resolve source types instead of the previous ad-hoc string-scanning approach.
 */

import { AcurityTypes, areTypesCompatible } from "../types/acurityTypes.js";
import { buildTokenRange } from "../parser.js";
import { createDiagnostic } from "../diagnostics/diagnosticFactory.js";
import { getFunctionScopeAtLine } from "../analysis/functionAnalysis.js";
import { buildDocumentAst } from "../ast/documentAst.js";
import { parseExpression } from "../ast/expressionParser.js";
import { NodeType } from "../ast/nodes.js";
import { inferExpressionType } from "../analysis/expressionTypeInference.js";
import { buildVariablesMap } from "./validatorHelpers.js";

// ---------------------------------------------------------------------------
// AST walking
// ---------------------------------------------------------------------------

/**
 * Recursively collects all AssignStatement and SetStatement nodes from an
 * array of AST statements, including those nested inside IF/DO/CASE/FUNCTION
 * blocks.
 * @param {object[]} statements
 * @param {object[]} [out]
 * @returns {object[]}
 */
function collectAssignmentNodes(statements, out = []) {
    for (const node of statements) {
        if (node.type === NodeType.AssignStatement || node.type === NodeType.SetStatement) {
            out.push(node);
        }
        if (node.consequent?.length) collectAssignmentNodes(node.consequent, out);
        if (node.alternate?.length)  collectAssignmentNodes(node.alternate, out);
        // Generic body branch covers FunctionDeclaration, DoStatement (iterator/forever/while),
        // and any other node type in collectAssignmentNodes that exposes a body statement array.
        if (Array.isArray(node.body)) collectAssignmentNodes(node.body, out);
        if (node.branches) {
            for (const branch of node.branches) {
                if (branch.body) collectAssignmentNodes(branch.body, out);
            }
        }
        if (Array.isArray(node.otherwise)) collectAssignmentNodes(node.otherwise, out);
    }
    return out;
}

// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------

/**
 * Validates type compatibility in assignments across a document.
 * @param {object} document - TextDocument (VS Code or test shim)
 * @param {Function} getRuleSeverity
 * @param {object} [options]
 * @param {Map<string, {type: string}>} [options.variableTypes] - Scoped variable type map
 * @param {object} [options.userDefinedFunctionCatalog]
 * @param {object[]|null} [options.executionClassificationMap]
 * @param {object[]} [options.functionScopes]
 * @returns {object[]} Diagnostics array
 */
function validateTypes(document, getRuleSeverity, options = {}) {
    const diagnostics = [];
    const variableTypes = options.variableTypes || new Map();
    const userDefinedFunctionCatalog = options.userDefinedFunctionCatalog || {};
    const executionClassificationMap = options.executionClassificationMap || null;
    const functionScopes = options.functionScopes || [];

    const { ast } = buildDocumentAst(document);
    const assignments = collectAssignmentNodes(ast.statements);

    // Cache variables maps by scopeId — most assignments share the same scope.
    const variableMapCache = new Map();

    for (const node of assignments) {
        const lineIndex = node.line;

        if (executionClassificationMap && executionClassificationMap[lineIndex]?.isOutput) {
            continue;
        }

        const scope = getFunctionScopeAtLine(functionScopes, lineIndex);
        const scopeId = scope ? String(scope.startLine) : "global";

        const targetVar = variableTypes.get(`${scopeId}:${node.target}`)
            || variableTypes.get(`global:${node.target}`);

        if (!targetVar) {
            continue; // Undeclared variable — handled by undeclared-variable validator
        }

        if (!variableMapCache.has(scopeId)) {
            variableMapCache.set(scopeId, buildVariablesMap(variableTypes, scopeId));
        }

        const exprNode = parseExpression(node.value);
        const sourceType = inferExpressionType(exprNode, {
            variables: variableMapCache.get(scopeId),
            userDefinedFunctionCatalog
        });

        if (sourceType && !areTypesCompatible(targetVar.type, sourceType)) {
            const lineText = document.lineAt(lineIndex).text;
            const diag = createDiagnostic(
                buildTokenRange(lineText, lineIndex, node.target, 0),
                {
                    messageId: "ACU-TYPE-002",
                    params: { sourceType, targetType: targetVar.type, target: node.target }
                },
                getRuleSeverity
            );
            if (diag) {
                diagnostics.push(diag);
            }
        }
    }

    return diagnostics;
}

export { validateTypes };
