/**
 * collectDiagnostics — pure validation orchestration for an Architect document.
 *
 * Has no dependency on VS Code. Consumed by:
 *   - vscode-extension/src/diagnostics.js  (passes getRuleSeverity from workspace config)
 *   - cli/index.js                          (passes getRuleSeverity from catalog defaults)
 *
 * @param {object} document       VS Code TextDocument or PlainTextDocument
 * @param {function} getRuleSeverity  (ruleId: string) => number|null
 * @param {object} [options]
 * @param {string|null} [options.workspaceRoot]  Absolute path to the workspace root
 * @returns {object[]}  Array of diagnostic plain objects
 */

import { getTableStructure, isKnownTableStructure } from "./builtins/tableStructureRegistry.js";
import * as logger from "./logger.js";
import { collectDocumentFacts } from "./analysis/documentFacts.js";
import { createTelemetryRun } from "./telemetry.js";
import { validateBlockStructure } from "./structureValidator.js";
import { validateTypes } from "./validators/typeValidator.js";
import { validateNamingConventions } from "./validators/namingValidator.js";
import { validateStringInterpolation } from "./validators/interpolationValidator.js";
import { validateFunctionCalls, validateFunctionDeclarations } from "./validators/functionValidator.js";
import { validateMacros } from "./validators/macroValidator.js";
import { validateIndexUsage } from "./validators/indexUsageValidator.js";
import { validateArrayUsage } from "./validators/arrayUsageValidator.js";
import { validateRobodoc } from "./validators/robodocValidator.js";
import { validateKeywordCapitalization } from "./validators/keywordCapsValidator.js";
import { validateTypeKeywordCapitalization } from "./validators/typeKeywordCapsValidator.js";
import { validateDiscouragedTypes } from "./validators/discouragedTypeValidator.js";
import { validateDiscouragedLiterals } from "./validators/discouragedLiteralValidator.js";
import { validateDoIterators } from "./validators/doIteratorValidator.js";
import { validateVariableUsage } from "./validators/variableValidator.js";
import { validateIncludes, validateRequire } from "./validators/includeValidator.js";
import { validateHashMode } from "./validators/hashModeValidator.js";
import { validateOptionDirectives } from "./validators/optionValidator.js";
import { validateArgumentStrings } from "./validators/argumentStringValidator.js";
import { validateTodoTags } from "./validators/todoValidator.js";
import { validateBlockIndentation } from "./validators/indentationValidator.js";
import { validateArgumentTypes } from "./validators/argumentTypeValidator.js";
import { validateExitStatements } from "./validators/exitStatementValidator.js";
import { validateOperatorSpacing } from "./validators/operatorSpacingValidator.js";
import { validateReturnTypes } from "./validators/functionReturnTypeValidator.js";
import { validateFunctionWhitespace } from "./validators/functionWhitespaceValidator.js";
import { validateEolRequired } from "./validators/eolRequiredValidator.js";
import { buildSuppressionMap, isSuppressed } from "./suppressionParser.js";

function collectDiagnostics(document, getRuleSeverity, options = {}) {
    const workspaceRoot  = options.workspaceRoot  ?? null;
    const namingConfig   = options.namingConfig   ?? null;
    const robodocConfig  = options.robodocConfig  ?? null;

    logger.log("Validating document:", document.fileName || document.uri?.fsPath);
    const telemetry = createTelemetryRun("diagnostics.run", {
        file: document.fileName || document.uri?.fsPath,
        lineCount: document.lineCount
    });

    /**
     * Runs a single validator, wrapping it in telemetry phase timing and error isolation.
     * On error, logs the failure and returns an empty array so validation continues.
     * @param {string} phase - Telemetry phase name (e.g. "validator.block")
     * @param {Function} validatorFn - Zero-arg function that returns diagnostic[]
     * @param {string} [label] - When provided, logs the diagnostic count at DEBUG level
     * @returns {object[]}
     */
    function runValidator(phase, validatorFn, label) {
        telemetry?.startPhase(phase);
        try {
            const result = validatorFn();
            telemetry?.endPhase(phase);
            if (!Array.isArray(result)) {
                logger.warn(`${phase} (${label ?? phase}) returned unexpected value (expected array):`, result);
                return [];
            }
            if (label) logger.log(`${label} returned diagnostics:`, result.length);
            return result;
        } catch (error) {
            telemetry?.endPhase(phase);
            logger.error(`${phase} error:`, { message: error?.message, stack: error?.stack });
            return [];
        }
    }

    const diagnostics = [];

    telemetry?.startPhase("facts.collect");
    const facts = collectDocumentFacts(document);
    telemetry?.endPhase("facts.collect");
    const { functionScopes, userDefinedFunctionCatalog, includedVariablesByLine, declaredVariables, globalVariables } = facts;

    diagnostics.push(...runValidator("include.issues", () => validateIncludes(document, getRuleSeverity)));
    diagnostics.push(...runValidator("hash-mode", () => validateHashMode(document, getRuleSeverity, { facts })));
    diagnostics.push(...runValidator("validator.option", () => validateOptionDirectives(document, getRuleSeverity)));

    diagnostics.push(...runValidator("validator.block",
        () => validateBlockStructure(document, getRuleSeverity, { executionClassificationMap: facts.executionClassificationMap })));
    diagnostics.push(...runValidator("validator.blockIndentation",
        () => validateBlockIndentation(document, getRuleSeverity, { executionClassificationMap: facts.executionClassificationMap })));
    diagnostics.push(...runValidator("validator.keywordCaps",
        () => validateKeywordCapitalization(document, getRuleSeverity, { executionClassificationMap: facts.executionClassificationMap })));
    diagnostics.push(...runValidator("validator.typeKeywordCaps",
        () => validateTypeKeywordCapitalization(document, getRuleSeverity, { executionClassificationMap: facts.executionClassificationMap })));
    diagnostics.push(...runValidator("validator.discouragedTypes",
        () => validateDiscouragedTypes(document, getRuleSeverity, { executionClassificationMap: facts.executionClassificationMap })));
    diagnostics.push(...runValidator("validator.discouragedLiterals",
        () => validateDiscouragedLiterals(document, getRuleSeverity, { executionClassificationMap: facts.executionClassificationMap })));
    diagnostics.push(...runValidator("validator.doIterators",
        () => validateDoIterators(document, getRuleSeverity)));
    diagnostics.push(...runValidator("validator.type", () => validateTypes(document, getRuleSeverity, {
        variableTypes: declaredVariables,
        userDefinedFunctionCatalog,
        executionClassificationMap: facts.executionClassificationMap,
        functionScopes: facts.functionScopes
    })));
    diagnostics.push(...runValidator("validator.argumentType", () => validateArgumentTypes(document, getRuleSeverity, {
        variableTypes: declaredVariables,
        userDefinedFunctionCatalog,
        executionClassificationMap: facts.executionClassificationMap,
        functionScopes: facts.functionScopes
    })));
    diagnostics.push(...runValidator("validator.naming",
        () => validateNamingConventions(document, getRuleSeverity, { variableTypes: declaredVariables, namingConfig })));
    diagnostics.push(...runValidator("validator.interpolation", () => validateStringInterpolation(document, getRuleSeverity, {
        declaredVariables,
        executionClassificationMap: facts.executionClassificationMap,
        outputDelimiters: facts.outputDelimiters,
        interpolationTargetsByLine: facts.interpolationTargetsByLine,
        includedVariablesByLine: facts.includedVariablesByLine
    })));

    diagnostics.push(...runValidator("validator.functionDeclarations",
        () => validateFunctionDeclarations(document, getRuleSeverity)));
    diagnostics.push(...runValidator("validator.function", () => validateFunctionCalls(document, getRuleSeverity, {
        outputDelimiters: facts.outputDelimiters,
        executionClassificationMap: facts.executionClassificationMap,
        functionValidationTargetsByLine: facts.functionValidationTargetsByLine,
        userDefinedFunctionCatalog: facts.userDefinedFunctionCatalog,
        userDefinedFunctionCaseMap: facts.userDefinedFunctionCaseMap,
        functionDuplicates: facts.functionDuplicates,
        includedFunctionsByLine: facts.includedFunctionsByLine,
        includedFunctionCaseMapByLine: facts.includedFunctionCaseMapByLine,
        macroCatalog: facts.macroCatalog,
        nodeTypeByLine: facts.nodeTypeByLine,
        stmtByLine: facts.stmtByLine
    }), "Function validator"));
    diagnostics.push(...runValidator("validator.returnType",
        () => validateReturnTypes(document, getRuleSeverity, { functionScopes: facts.functionScopes }),
        "Return-type validator"));
    diagnostics.push(...runValidator("validator.macro", () => validateMacros(document, getRuleSeverity, {
        macroCatalog: facts.macroCatalog,
        macroCaseMap: facts.macroCaseMap,
        macroDuplicates: facts.macroDuplicates,
        executionClassificationMap: facts.executionClassificationMap
    }), "Macro validator"));
    diagnostics.push(...runValidator("validator.require",
        () => validateRequire(document, getRuleSeverity, { workspaceRoot }),
        "REQUIRE validator"));
    diagnostics.push(...runValidator("validator.indexUsage", () => validateIndexUsage(document, getRuleSeverity, {
        functionScopes: facts.functionScopes,
        executionClassificationMap: facts.executionClassificationMap,
        tableStructures: options.tableStructures ?? null
    }), "Index usage validator"));
    diagnostics.push(...runValidator("validator.arrayUsage", () => validateArrayUsage(document, getRuleSeverity, {
        functionScopes: facts.functionScopes,
        executionClassificationMap: facts.executionClassificationMap
    }), "Array usage validator"));
    diagnostics.push(...runValidator("validator.robodoc",
        () => validateRobodoc(document, getRuleSeverity, { robodocConfig }),
        "ROBODOC validator"));
    diagnostics.push(...runValidator("validator.argumentStrings", () => validateArgumentStrings(document, getRuleSeverity, {
        executionClassificationMap: facts.executionClassificationMap
    }), "Argument string validator"));
    diagnostics.push(...runValidator("validator.exitStatement",
        () => validateExitStatements(document, getRuleSeverity, { facts })));
    diagnostics.push(...runValidator("validator.operatorSpacing", () => validateOperatorSpacing(document, getRuleSeverity, {
        executionClassificationMap: facts.executionClassificationMap
    })));
    diagnostics.push(...runValidator("validator.functionWhitespace", () => validateFunctionWhitespace(document, getRuleSeverity, {
        executionClassificationMap: facts.executionClassificationMap
    })));
    diagnostics.push(...runValidator("validator.eolRequired", () => validateEolRequired(document, getRuleSeverity, {
        executionClassificationMap: facts.executionClassificationMap
    })));
    diagnostics.push(...runValidator("validator.todo", () => validateTodoTags(document, getRuleSeverity, {
        executionClassificationMap: facts.executionClassificationMap
    }), "TODO validator"));
    diagnostics.push(...runValidator("variables.usage",
        () => validateVariableUsage(document, getRuleSeverity, { facts, globalVariables })));

    telemetry?.startPhase("suppression.filter");
    const suppressionMap = buildSuppressionMap(document);
    const unsuppressed = diagnostics.filter((d) => {
        const messageId = d.data?.messageId;
        if (!messageId) return true;
        return !isSuppressed(messageId, d.range.start.line, suppressionMap);
    });
    telemetry?.endPhase("suppression.filter");

    telemetry?.emit({ diagnosticsCount: unsuppressed.length });

    return unsuppressed;
}

/**
 * Async variant of collectDiagnostics that pre-loads table structures referenced
 * in INDEX_OPEN_STANDARD calls before delegating to the sync orchestrator.
 *
 * @param {object} document       VS Code TextDocument or PlainTextDocument
 * @param {function} getRuleSeverity  (ruleId: string) => number|null
 * @param {object} [options]      Same options as collectDiagnostics
 * @returns {Promise<object[]>}   Array of diagnostic plain objects
 */
async function collectDiagnosticsAsync(document, getRuleSeverity, options = {}) {
    const text = document.getText();
    // Scan for INDEX_OPEN_STANDARD / SQL_INDEX_OPEN_STANDARD("CODE", ...) literals to find table codes used.
    const tableCodePattern = /(?:SQL_)?INDEX_OPEN_STANDARD\s*\(\s*["']([A-Za-z0-9_]+)["']/g;
    const foundCodes = new Set();
    let m;
    while ((m = tableCodePattern.exec(text)) !== null) {
        const code = m[1].toUpperCase();
        if (isKnownTableStructure(code)) foundCodes.add(code);
    }

    const tableStructures = {};
    for (const code of foundCodes) {
        try {
            const structure = await getTableStructure(code);
            if (structure) tableStructures[code] = structure;
        } catch (err) {
            logger.warn(`collectDiagnosticsAsync: failed to load table structure for ${code}:`, err?.message);
        }
    }

    return collectDiagnostics(document, getRuleSeverity, { ...options, tableStructures });
}

export { collectDiagnostics, collectDiagnosticsAsync };
