/**
 * Completion provider for Acurity built-in functions and constants.
 */

import * as vscode from "vscode";
import { LANGUAGE_ID } from "../../core/src/constants.js";
import { BuiltInFunctions, BuiltInGlobalVariables } from "../../core/src/builtins/functions.js";
import { extractOutputDelimiters } from "../../core/src/analysis/executionContext.js";
import { parseIncludeDirective } from "../../core/src/parser.js";
import { isStatementStart, isAfterBlockCloser } from "../../core/src/analysis/completionContext.js";

// ---------------------------------------------------------------------------
// Setting
// ---------------------------------------------------------------------------

function isCompletionEnabled() {
    return vscode.workspace.getConfiguration("acurity").get("code-completion", true);
}

// ---------------------------------------------------------------------------
// Context detection
// ---------------------------------------------------------------------------

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)
    );
}

/**
 * Returns true if the cursor (end of linePrefix) is inside an open
 * OICC/CICC delimiter pair.
 *
 * When OICC === CICC (e.g. both "@"), parity counting applies:
 * an odd number of delimiter characters to the left means "inside".
 *
 * When they differ, depth counting applies.
 */
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;
}

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

// ---------------------------------------------------------------------------
// Item builders
// ---------------------------------------------------------------------------

function formatParamType(type) {
    return Array.isArray(type) ? type.join("|") : String(type);
}

function buildFunctionInsertText(name, entry) {
    if (entry.insertSnippet) {
        return new vscode.SnippetString(entry.insertSnippet);
    }

    const params = entry.params || [];
    if (params.length === 0) {
        return new vscode.SnippetString(`${name}()`);
    }

    const paramLabels = entry.paramLabels || [];
    const parts = params.map((paramType, i) => {
        const typeLabel = formatParamType(paramType);
        const paramLabel = paramLabels[i];
        const label = paramLabel ? `${paramLabel}: ${typeLabel}` : typeLabel;
        return `\${${i + 1}:${label}}`;
    });

    if (entry.varArgs) {
        parts.push(`\${${params.length + 1}:...}`);
    }

    return new vscode.SnippetString(`${name}(${parts.join(", ")})`);
}

function buildFunctionDocumentation(name, entry) {
    const params = entry.params || [];
    const paramModes = entry.paramModes || [];
    const markdown = new vscode.MarkdownString();

    const paramStr = params.map((t, i) => {
        const mode = paramModes[i];
        const isVar = mode && mode !== "REF";
        return isVar ? `${formatParamType(t)}: VAR` : formatParamType(t);
    }).join(", ");
    const varArgsSuffix = entry.varArgs ? (params.length ? ", ..." : "...") : "";

    markdown.appendCodeblock(`${name}(${paramStr}${varArgsSuffix})\n→ ${entry.returns || ""}`, "");

    if (entry.description) {
        markdown.appendMarkdown(`\n${entry.description}`);
    }

    if (params.length > 0) {
        markdown.appendMarkdown("\n\n| # | Type | Mode |\n|---|------|------|\n");
        params.forEach((paramType, i) => {
            const mode = paramModes[i] || "REF";
            markdown.appendMarkdown(`| ${i + 1} | ${formatParamType(paramType)} | ${mode} |\n`);
        });
    }

    return markdown;
}

function buildConstantDocumentation(name, entry) {
    const markdown = new vscode.MarkdownString();
    markdown.appendCodeblock(`${name}\nType: ${entry.type}`, "");
    if (entry.description) {
        markdown.appendMarkdown(`\n${entry.description}`);
    } else {
        markdown.appendMarkdown("\nBuilt-in global variable.");
    }
    return markdown;
}

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

function buildItems(upperPrefix) {
    const results = [];

    for (const [name, entry] of Object.entries(BuiltInFunctions)) {
        if (name.toUpperCase().startsWith(upperPrefix)) {
            const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.Function);
            item.insertText = buildFunctionInsertText(name, entry);
            item.detail = `→ ${entry.returns || ""}`;
            item.documentation = buildFunctionDocumentation(name, entry);
            item.filterText = name.toUpperCase();
            item.sortText = `1_${name.toUpperCase()}`;
            results.push(item);
        }
    }

    for (const [name, entry] of Object.entries(BuiltInGlobalVariables)) {
        if (name.toUpperCase().startsWith(upperPrefix)) {
            const item = new vscode.CompletionItem(name, vscode.CompletionItemKind.GlobalVariable);
            item.insertText = name;
            item.detail = String(entry.type);
            item.documentation = buildConstantDocumentation(name, entry);
            item.filterText = name.toUpperCase();
            item.sortText = `2_${name.toUpperCase()}`;
            results.push(item);
        }
    }

    return results;
}

function createBuiltinCompletionItems(document, position) {
    if (!isCompletionEnabled()) {
        return [];
    }

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

    if (isSuppressedLine(lineText)) {
        return [];
    }

    // INCLUDE-family lines: after the keyword the argument is a filepath, not an expression.
    // Future: pivot to filename completions here.
    if (parseIncludeDirective(lineText) || /^\s*#?\s*(INCLUDE(?:ONCE|TEST)?|REQUIRE)\s/i.test(lineText)) {
        return [];
    }

    if (isOutputLine(lineText)) {
        const { oicc, cicc } = extractOutputDelimiters(document);
        if (!isInsideInterpolation(linePrefix, oicc, cicc)) {
            return [];
        }
        // Extract prefix from inside the open delimiter
        const lastOiccIdx = linePrefix.lastIndexOf(oicc);
        const interpolationPrefix = linePrefix.slice(lastOiccIdx + 1);
        const typedPrefix = getTypedPrefix(interpolationPrefix);
        if (!typedPrefix) return [];
        return buildItems(typedPrefix);
    }

    if (isAfterBlockCloser(linePrefix) || isStatementStart(linePrefix)) return [];

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

    return buildItems(typedPrefix);
}

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

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

export {
    initializeBuiltinCompletionProvider,
    createBuiltinCompletionItems,
};
