/**
 * Semantic token classifier for Acurity Architect.
 *
 * Walks the AST token stream and classifies IDENTIFIER tokens as either:
 *   - Builtin constants  → VARIABLE + READONLY modifier
 *   - Builtin functions  → FUNCTION + DEFAULT_LIBRARY modifier (only when followed by LPAREN)
 *
 * Also walks document lines directly to classify comment annotations:
 *   - @mode:strict / @mode:legacy / @mode:default → ANNOTATION_KEYWORD
 *   - @rule-ignore keyword                         → ANNOTATION_KEYWORD
 *   - ACU-XXX-NNN rule IDs within @rule-ignore     → ANNOTATION_RULE_ID
 *
 * Returns plain objects so the VS Code provider wrapper can encode them into
 * the uint32 SemanticTokens format without this module depending on vscode.
 *
 * Token type / modifier indices must match the legend declared in
 * vscode-extension/src/semanticTokensProvider.js.
 */

import { buildDocumentAst } from "../ast/documentAst.js";
import { TokenType } from "../lexer/tokenTypes.js";
import { BuiltInFunctions, BuiltInGlobalVariables } from "../builtins/functions.js";

/**
 * Token type indices — must match TOKEN_TYPES array order in semanticTokensProvider.js.
 */
const SEMANTIC_TOKEN_TYPES = Object.freeze({
    VARIABLE:          0,
    FUNCTION:          1,
    KEYWORD:           2,
    ANNOTATION_KEYWORD: 3,
    ANNOTATION_RULE_ID: 4
});

/**
 * Token modifier bitmasks — must match TOKEN_MODIFIERS array order in semanticTokensProvider.js.
 * Each modifier occupies one bit: readonly=bit0, defaultLibrary=bit1.
 */
const SEMANTIC_TOKEN_MODIFIERS = Object.freeze({
    READONLY:        1, // bit 0
    DEFAULT_LIBRARY: 2  // bit 1
});

/**
 * Classifies semantic tokens in a document by walking the AST token stream.
 *
 * @param {object} document  VS Code TextDocument or PlainTextDocument
 * @returns {{ line: number, char: number, length: number, tokenType: number, tokenModifiers: number }[]}
 *   Tokens in document order (line ascending, then char ascending within the line).
 */
function classifySemanticTokens(document) {
    const { tokens } = buildDocumentAst(document);
    const results = [];

    for (let i = 0; i < tokens.length; i++) {
        const token = tokens[i];
        if (token.type !== TokenType.IDENTIFIER) continue;

        const upper = token.value.toUpperCase();

        if (Object.prototype.hasOwnProperty.call(BuiltInGlobalVariables, upper)) {
            results.push({
                line:           token.line,
                char:           token.char,
                length:         token.value.length,
                tokenType:      SEMANTIC_TOKEN_TYPES.VARIABLE,
                tokenModifiers: BuiltInGlobalVariables[upper].readOnly
                    ? SEMANTIC_TOKEN_MODIFIERS.READONLY
                    : 0
            });
            continue;
        }

        if (Object.prototype.hasOwnProperty.call(BuiltInFunctions, upper)) {
            // Only classify as function when the identifier is followed by LPAREN.
            // This avoids false positives when a variable name shadows a builtin name
            // without calling it as a function.
            let j = i + 1;
            while (j < tokens.length && tokens[j].type === TokenType.WHITESPACE) j++;
            if (j < tokens.length && tokens[j].type === TokenType.LPAREN) {
                results.push({
                    line:           token.line,
                    char:           token.char,
                    length:         token.value.length,
                    tokenType:      SEMANTIC_TOKEN_TYPES.FUNCTION,
                    tokenModifiers: SEMANTIC_TOKEN_MODIFIERS.DEFAULT_LIBRARY
                });
            }
        }
    }

    classifyAnnotationTokens(document, results);

    results.sort((a, b) => a.line !== b.line ? a.line - b.line : a.char - b.char);

    return results;
}

// Matches: optional whitespace, //, optional whitespace, then @mode:strict/legacy/default
const MODE_ANNOTATION_RE = /^(\s*\/\/\s*)(@mode:(?:strict|legacy|default))/i;

// Matches: optional whitespace, //, optional whitespace, then @rule-ignore
const RULE_IGNORE_KEYWORD_RE = /^(\s*\/\/\s*)(@rule-ignore)/i;

// Matches individual ACU-XXX-NNN rule IDs
const RULE_ID_RE = /ACU-[A-Z]+-\d{3}/gi;

/**
 * Walks document lines directly to find comment annotations and push tokens for them.
 * Results are appended to the provided array (sorting happens in the caller).
 *
 * @param {object} document
 * @param {object[]} results
 */
function classifyAnnotationTokens(document, results) {
    for (let lineIndex = 0; lineIndex < document.lineCount; lineIndex++) {
        const text = document.lineAt(lineIndex).text;

        const modeMatch = MODE_ANNOTATION_RE.exec(text);
        if (modeMatch) {
            results.push({
                line:           lineIndex,
                char:           modeMatch[1].length,
                length:         modeMatch[2].length,
                tokenType:      SEMANTIC_TOKEN_TYPES.ANNOTATION_KEYWORD,
                tokenModifiers: 0
            });
            continue;
        }

        const ruleIgnoreMatch = RULE_IGNORE_KEYWORD_RE.exec(text);
        if (ruleIgnoreMatch) {
            results.push({
                line:           lineIndex,
                char:           ruleIgnoreMatch[1].length,
                length:         ruleIgnoreMatch[2].length,
                tokenType:      SEMANTIC_TOKEN_TYPES.ANNOTATION_KEYWORD,
                tokenModifiers: 0
            });

            // Highlight each rule ID that follows on the same line.
            const afterKeyword = text.slice(ruleIgnoreMatch[1].length + ruleIgnoreMatch[2].length);
            RULE_ID_RE.lastIndex = 0;
            let idMatch;
            while ((idMatch = RULE_ID_RE.exec(afterKeyword)) !== null) {
                results.push({
                    line:           lineIndex,
                    char:           ruleIgnoreMatch[1].length + ruleIgnoreMatch[2].length + idMatch.index,
                    length:         idMatch[0].length,
                    tokenType:      SEMANTIC_TOKEN_TYPES.ANNOTATION_RULE_ID,
                    tokenModifiers: 0
                });
            }
        }
    }
}

export { classifySemanticTokens, SEMANTIC_TOKEN_TYPES, SEMANTIC_TOKEN_MODIFIERS };
