/**
 * Exit-statement validator for Acurity Architect.
 *
 * ACU-STY-008  (strict mode only — requires // @mode:strict annotation)
 *
 *   EXIT as the final top-level statement is redundant; the report terminates
 *   implicitly. Remove it.
 *
 *   END-OF-CODE is the deprecated equivalent of EXIT.
 *   - At report end (last top-level statement): redundant — remove it.
 *   - Anywhere else: deprecated — replace with EXIT.
 *
 * Quick-fix variants (carried in diagnostic.data.params.variant):
 *   "redundant"   — delete the line (EXIT or END-OF-CODE at last position)
 *   "deprecated"  — replace END-OF-CODE with EXIT (mid-report position)
 */

import { buildDocumentAst } from "../ast/documentAst.js";
import { NodeType } from "../ast/nodes.js";
import { createDiagnostic } from "../diagnostics/diagnosticFactory.js";

/**
 * Walks all statements recursively, invoking visitor(stmt, isTopLevel).
 * @param {object[]} stmts
 * @param {function} visitor
 * @param {boolean} isTopLevel
 */
function walkAllStatements(stmts, visitor, isTopLevel) {
    for (const stmt of stmts) {
        visitor(stmt, isTopLevel);
        if (Array.isArray(stmt.body))       walkAllStatements(stmt.body,      visitor, false);
        if (Array.isArray(stmt.consequent)) walkAllStatements(stmt.consequent, visitor, false);
        if (Array.isArray(stmt.alternate))  walkAllStatements(stmt.alternate,  visitor, false);
        if (stmt.branches) {
            for (const branch of stmt.branches) {
                if (Array.isArray(branch.body)) walkAllStatements(branch.body, visitor, false);
            }
        }
        if (Array.isArray(stmt.otherwise)) walkAllStatements(stmt.otherwise, visitor, false);
    }
}

/**
 * Builds a Range from a line index and keyword text, resolved against the raw line.
 * Assumption: keywords ("EXIT", "END-OF-CODE") appear as standalone statements and are
 * not embedded inside string literals on these lines. This validator only runs on AST
 * nodes of type ExitStatement / EndOfCodeStatement, so the keyword is always the actual
 * statement token. If that assumption ever changes, replace indexOf with token char positions.
 */
function keywordRange(lineIndex, lineText, keyword) {
    const col = lineText.indexOf(keyword);
    if (col === -1) {
        // Fallback: highlight the whole line
        return { start: { line: lineIndex, character: 0 }, end: { line: lineIndex, character: lineText.length } };
    }
    return {
        start: { line: lineIndex, character: col },
        end:   { line: lineIndex, character: col + keyword.length }
    };
}

/**
 * @param {object} document
 * @param {Function} getRuleSeverity
 * @param {object} [options={}]
 * @param {object} [options.facts] - Pre-computed document facts
 * @returns {object[]}
 */
function validateExitStatements(document, getRuleSeverity, options = {}) {
    const { facts = {} } = options;
    if (facts.mode !== "strict") {
        return [];
    }

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

    // Find the line of the last top-level statement (to detect trailing terminators).
    let lastTopLevelLine = -1;
    for (const stmt of topLevel) {
        if (stmt.line > lastTopLevelLine) {
            lastTopLevelLine = stmt.line;
        }
    }

    const diagnostics = [];

    walkAllStatements(topLevel, (stmt, isTopLevel) => {
        const lineIndex = stmt.line;
        const lineText  = document.lineAt(lineIndex).text;

        if (stmt.type === NodeType.ExitStatement && isTopLevel && lineIndex === lastTopLevelLine) {
            // Redundant trailing EXIT — remove it
            const diag = createDiagnostic(
                keywordRange(lineIndex, lineText, "EXIT"),
                {
                    messageId: "ACU-STY-008",
                    params: {
                        keyword: "EXIT",
                        variant: "redundant",
                        issue: "at report end is unnecessary — the report terminates implicitly; remove this statement"
                    }
                },
                getRuleSeverity
            );
            if (diag) diagnostics.push(diag);
        }

        if (stmt.type === NodeType.EndOfCodeStatement) {
            if (isTopLevel && lineIndex === lastTopLevelLine) {
                // Redundant trailing END-OF-CODE — remove it
                const diag = createDiagnostic(
                    keywordRange(lineIndex, lineText, "END-OF-CODE"),
                    {
                        messageId: "ACU-STY-008",
                        params: {
                            keyword: "END-OF-CODE",
                            variant: "redundant",
                            issue: "at report end is unnecessary — the report terminates implicitly; remove this statement"
                        }
                    },
                    getRuleSeverity
                );
                if (diag) diagnostics.push(diag);
            } else {
                // Deprecated END-OF-CODE not at last position — replace with EXIT
                const diag = createDiagnostic(
                    keywordRange(lineIndex, lineText, "END-OF-CODE"),
                    {
                        messageId: "ACU-STY-008",
                        params: {
                            keyword: "END-OF-CODE",
                            variant: "deprecated",
                            issue: "is deprecated; use EXIT instead"
                        }
                    },
                    getRuleSeverity
                );
                if (diag) diagnostics.push(diag);
            }
        }
    }, true);

    return diagnostics;
}

export { validateExitStatements };
