/**
 * In-scope variable completion provider for Acurity Architect.
 *
 * Surfaces declared variables (global, function-local, and function parameters)
 * as completion items wherever a valid identifier is being typed.
 *
 * The built-in completion provider already suppresses its items at statement
 * start (line has no `=`, no open paren, no expression-opener keyword).
 * This provider fills that gap: variables are valid both at statement start
 * (LHS of assignment / SET target) and in expression context (RHS, function
 * arguments, IF conditions, etc.).
 *
 * Items are sortText-ranked below built-in functions and global constants so
 * that the most frequently used completions stay near the top.
 */

import * as vscode from "vscode";
import { LANGUAGE_ID } from "../../core/src/constants.js";
import { collectDocumentFacts } from "../../core/src/analysis/documentFacts.js";
import { getFunctionScopeAtLine } from "../../core/src/analysis/functionAnalysis.js";
import { extractOutputDelimiters } from "../../core/src/analysis/executionContext.js";
import { parseIncludeDirective } from "../../core/src/parser.js";
import { isAfterBlockCloser } from "../../core/src/analysis/completionContext.js";

// ---------------------------------------------------------------------------
// Suppression helpers (mirrors builtinCompletionProvider logic)
// ---------------------------------------------------------------------------

function isSuppressedLine(lineText) {
    const trimmed = lineText.trimStart();
    return (
        trimmed.startsWith("//") ||
        /^#\s*\/\//.test(trimmed) ||
        /^#\s*NOTE\b/i.test(trimmed)
    );
}

function isOutputLine(lineText) {
    const trimmed = lineText.trimStart();
    return (
        trimmed.startsWith(">>") ||
        trimmed.startsWith("??") ||
        /^#\s*\?\?/.test(trimmed)
    );
}

function isInsideInterpolation(linePrefix, oicc, cicc) {
    if (oicc === cicc) {
        let count = 0;
        for (const ch of linePrefix) { if (ch === oicc) count++; }
        return count % 2 === 1;
    }
    let depth = 0;
    for (const ch of linePrefix) {
        if (ch === oicc) depth++;
        else if (ch === cicc && depth > 0) depth--;
    }
    return depth > 0;
}

function isVarDeclarationLine(lineText) {
    // Suppress completions on TYPE-first declaration lines (e.g. `SHORT sStatus, sHandle`)
    // and VAR-keyword lines, so we don't suggest existing names while the user is naming new vars.
    return /^\s*(VAR\s+.*:|(?:STRING|SHORT|LONG|DOUBLE|BOOLEAN|DATE|TIME|BIGINT|INTEGER|TIMESTAMP)\s+\w)/i.test(lineText);
}

/** Returns the identifier fragment immediately before the cursor (case-preserved). */
function getTypedPrefix(linePrefix) {
    const match = linePrefix.match(/([A-Za-z_][A-Za-z0-9_]*)$/);
    return match ? match[1] : "";
}

// ---------------------------------------------------------------------------
// Scope resolution
// ---------------------------------------------------------------------------

/**
 * Collects all variables in scope at `lineIndex` from the pre-built facts.
 * Returns an array of `{ name: string, type: string }` objects.
 *
 * Includes:
 *   • globals declared in this file
 *   • variables pulled in via #INCLUDE directives active at this line
 *   • params and locals of the enclosing function (if any)
 *
 * @param {object} facts  Result of collectDocumentFacts(document)
 * @param {number} lineIndex
 * @returns {Array<{name: string, type: string}>}
 */
function getVariablesInScope(facts, lineIndex) {
    const result = [];
    const seen   = new Set();

    function add(name, type) {
        if (!seen.has(name)) {
            seen.add(name);
            result.push({ name, type: String(type) });
        }
    }

    // 1. Globals declared in this file
    for (const [name, info] of facts.globalVariables) {
        add(name, info.type);
    }

    // 2. Variables brought into scope by #INCLUDE directives
    const includedAtLine = facts.includedVariablesByLine?.get(lineIndex);
    if (includedAtLine) {
        for (const [name, info] of includedAtLine) {
            // info may be a type string or an object with a .type field
            add(name, (info && typeof info === "object") ? info.type : info);
        }
    }

    // 3. Enclosing function: params first (highest priority), then locals
    const scope = getFunctionScopeAtLine(facts.functionScopes, lineIndex);
    if (scope) {
        for (const [name, type] of scope.params) add(name, type);
        for (const [name, type] of scope.locals)  add(name, type);
    }

    return result;
}

// ---------------------------------------------------------------------------
// Item builder
// ---------------------------------------------------------------------------

const KIND_FOR_TYPE = {
    SHORT:     vscode.CompletionItemKind.Variable,
    LONG:      vscode.CompletionItemKind.Variable,
    BIGINT:    vscode.CompletionItemKind.Variable,
    INTEGER:   vscode.CompletionItemKind.Variable,
    DOUBLE:    vscode.CompletionItemKind.Variable,
    STRING:    vscode.CompletionItemKind.Variable,
    BOOLEAN:   vscode.CompletionItemKind.Variable,
    DATE:      vscode.CompletionItemKind.Variable,
    TIME:      vscode.CompletionItemKind.Variable,
    TIMESTAMP: vscode.CompletionItemKind.Variable,
};

/**
 * Builds completion items for all in-scope variables whose name starts with
 * `typedPrefix` (case-insensitive).
 *
 * @param {Array<{name: string, type: string}>} vars
 * @param {string} typedPrefix  Case-preserved prefix the user has typed
 * @returns {vscode.CompletionItem[]}
 */
function buildVariableItems(vars, typedPrefix) {
    const upperPrefix = typedPrefix.toUpperCase();
    const items = [];

    for (const { name, type } of vars) {
        if (!name.toUpperCase().startsWith(upperPrefix)) continue;

        const kind = KIND_FOR_TYPE[type] ?? vscode.CompletionItemKind.Variable;
        const item = new vscode.CompletionItem(name, kind);
        item.insertText  = name;
        item.detail      = type;
        item.filterText  = name;
        // Sort after built-in functions (1_) and global constants (2_) from builtinCompletionProvider
        item.sortText    = `3_${name.toUpperCase()}`;

        const markdown = new vscode.MarkdownString();
        markdown.appendCodeblock(`${name}\nType: ${type}`, "");
        markdown.appendMarkdown("\nLocal variable.");
        item.documentation = markdown;

        items.push(item);
    }

    return items;
}

// ---------------------------------------------------------------------------
// Core completion logic
// ---------------------------------------------------------------------------

function createVariableCompletionItems(document, position) {
    const lineText   = document.lineAt(position.line).text;
    const linePrefix = lineText.slice(0, position.character);

    // Comments and block-closer lines: no completions
    if (isSuppressedLine(lineText))        return [];
    if (isAfterBlockCloser(linePrefix))    return [];

    // Var-declaration lines: suppress to avoid polluting the name-writing experience
    if (isVarDeclarationLine(lineText))    return [];

    // Include-family lines: no expression context
    if (parseIncludeDirective(lineText) || /^\s*#?\s*(INCLUDE(?:ONCE|TEST)?|REQUIRE)\s/i.test(lineText)) {
        return [];
    }

    // Output lines: only inside interpolation delimiters
    if (isOutputLine(lineText)) {
        const { oicc, cicc } = extractOutputDelimiters(document);
        if (!isInsideInterpolation(linePrefix, oicc, cicc)) return [];
    }

    const typedPrefix = getTypedPrefix(linePrefix);
    if (!typedPrefix) return [];

    let facts;
    try {
        facts = collectDocumentFacts(document);
    } catch {
        return [];
    }

    const vars  = getVariablesInScope(facts, position.line);
    return buildVariableItems(vars, typedPrefix);
}

// ---------------------------------------------------------------------------
// Registration
// ---------------------------------------------------------------------------

function initializeVariableCompletionProvider(context) {
    const provider = vscode.languages.registerCompletionItemProvider(
        LANGUAGE_ID,
        {
            provideCompletionItems(document, position) {
                return createVariableCompletionItems(document, position);
            }
        }
        // No trigger character — ambient IntelliSense on any identifier character
    );
    context.subscriptions.push(provider);
}

export { initializeVariableCompletionProvider, getVariablesInScope };
