/**
 * Index-aware completion provider for Acurity Architect.
 *
 * Feature 4 — INDEX_OPEN_STANDARD second argument:
 *   When the cursor is inside the second argument of INDEX_OPEN_STANDARD and
 *   the first argument is a known string-literal table code, lists the valid
 *   numeric index numbers for that table.  The PK index is omitted (it is not
 *   accessible via INDEX_OPEN_STANDARD in normal use).  Each item shows a
 *   compact field-name preview inline and the full segment schema in the
 *   documentation popup.
 *
 * Feature 5 — INDEX_LOAD_KEY_ suffix:
 *   When the cursor is after the `INDEX_LOAD_KEY_` prefix, annotates the
 *   completion items with the expected suffix for the next segment on the
 *   most-recently opened handle, and ranks it first.
 */

import * as vscode from "vscode";
import { LANGUAGE_ID } from "../../core/src/constants.js";
import { getTableStructure, isKnownTableStructure } from "../../core/src/builtins/tableStructureRegistry.js";
import { TABLE_REGISTRY, getTableEntry } from "../../core/src/builtins/tableSynonymsRegistry.js";
import { buildLoadPositionsAtLine, TYPE_TO_LOAD_KEY_SUFFIX } from "../../core/src/analysis/indexLoadPositionAnalysis.js";
import { getVariablesInScope } from "./variableCompletionProvider.js";
import { collectDocumentFacts } from "../../core/src/analysis/documentFacts.js";

// ---------------------------------------------------------------------------
// Feature 4a: table code completions for INDEX_OPEN_STANDARD first argument
// ---------------------------------------------------------------------------

/**
 * Detects whether the cursor is sitting inside the FIRST argument (tableCode)
 * of an INDEX_OPEN_STANDARD call.
 *
 * Returns the absolute character position where that argument starts (after
 * the opening paren and any whitespace), or null if the context does not match.
 *
 * @param {string} linePrefix
 * @returns {{ arg0CharStart: number }|null}
 */
function detectIndexOpenTableContext(linePrefix) {
    const FUNC_RE = /\b(?:SQL_)?INDEX_OPEN_STANDARD\s*\(/ig;
    let lastMatch = null;
    let m;
    while ((m = FUNC_RE.exec(linePrefix)) !== null) lastMatch = m;
    if (!lastMatch) return null;

    const callExpr = linePrefix.slice(lastMatch.index);
    const parenIdx = callExpr.indexOf("(");
    if (parenIdx === -1) return null;

    let depth = 0, inString = false, stringChar = null, argIndex = 0;

    for (let i = parenIdx; i < callExpr.length; i++) {
        const ch = callExpr[i];
        if (inString) { if (ch === stringChar) inString = false; continue; }
        if (ch === '"' || ch === "'") { inString = true; stringChar = ch; continue; }
        if (ch === "(") depth++;
        else if (ch === ")") { depth--; if (depth === 0) return null; }
        else if (ch === "," && depth === 1) argIndex++;
    }

    // Only fire when we are in the first argument (no comma encountered yet)
    if (argIndex !== 0) return null;

    // Absolute position of the first non-whitespace character after "("
    const afterParen = callExpr.slice(parenIdx + 1);
    const wsLen = (afterParen.match(/^(\s*)/) || [""])[0].length;
    return { arg0CharStart: lastMatch.index + parenIdx + 1 + wsLen };
}

/**
 * Builds completion items for the tableCode argument of INDEX_OPEN_STANDARD.
 *
 * Items are sourced from the full TABLE_REGISTRY (171 entries).  Tables that
 * have a known structure (index/field metadata) are ranked first.
 *
 * @returns {vscode.CompletionItem[]}
 */
function buildTableCodeCompletions() {
    return Object.entries(TABLE_REGISTRY).map(([code, entry]) => {
        const hasStructure = isKnownTableStructure(code);
        const item = new vscode.CompletionItem(`"${code}"`, vscode.CompletionItemKind.EnumMember);
        item.insertText = `"${code}"`;
        // filterText without quotes so VS Code matches against the typed code letters
        item.filterText = code;
        item.detail = entry.name;
        // Tables with full structure metadata sort above the rest
        item.sortText = hasStructure ? `0_${code}` : `1_${code}`;

        const markdown = new vscode.MarkdownString();
        markdown.appendMarkdown(`**${code}** — ${entry.name}`);
        if (entry.category) {
            markdown.appendMarkdown(`\n\nCategory: ${entry.category}`);
        }
        if (hasStructure) {
            markdown.appendMarkdown(`\n\n✓ Index and field structure available`);
        }
        item.documentation = markdown;
        return item;
    });
}

// ---------------------------------------------------------------------------
// Feature 4b: index number completions for INDEX_OPEN_STANDARD second argument
// ---------------------------------------------------------------------------

/**
 * Scans linePrefix to detect whether the cursor is sitting inside the SECOND
 * argument (indexNumber) of an INDEX_OPEN_STANDARD call, and returns the table
 * code from the first argument plus the character offset where arg 1 begins.
 *
 * Uses explicit paren/string depth counting rather than a regex so it handles
 * any text in the second-arg slot — including snippet placeholder text such as
 * "indexNumber: SHORT".
 *
 * Returns null if the cursor is not in the second-argument position.
 *
 * @param {string} linePrefix  Text of the current line up to the cursor
 * @returns {{ tableCode: string, arg1CharStart: number }|null}
 */
function detectIndexOpenContext(linePrefix) {
    // Find the rightmost INDEX_OPEN_STANDARD( or SQL_INDEX_OPEN_STANDARD( before the cursor
    const FUNC_RE = /\b(?:SQL_)?INDEX_OPEN_STANDARD\s*\(/ig;
    let lastMatch = null;
    let m;
    while ((m = FUNC_RE.exec(linePrefix)) !== null) lastMatch = m;
    if (!lastMatch) return null;

    // Walk the call expression (from the opening paren to end of linePrefix),
    // counting paren depth and commas to find which argument we are in.
    const callExpr = linePrefix.slice(lastMatch.index);
    const parenIdx = callExpr.indexOf("(");
    if (parenIdx === -1) return null;

    let depth = 0;
    let inString = false;
    let stringChar = null;
    let argIndex = 0;
    let arg0Start = -1;
    let arg0End   = -1;
    let arg1Start = -1; // offset within callExpr

    for (let i = parenIdx; i < callExpr.length; i++) {
        const ch = callExpr[i];

        if (inString) {
            if (ch === stringChar) inString = false;
            continue;
        }
        if (ch === '"' || ch === "'") { inString = true; stringChar = ch; continue; }

        if (ch === "(") {
            depth++;
            if (depth === 1 && arg0Start === -1) arg0Start = i + 1;
        } else if (ch === ")") {
            depth--;
            if (depth === 0) return null; // cursor is past the closing paren
        } else if (ch === "," && depth === 1) {
            if (argIndex === 0) {
                arg0End   = i;
                arg1Start = i + 1; // first char of arg 1 in callExpr
            }
            argIndex++;
        }
    }

    // We must be exactly at arg index 1 (second argument)
    if (argIndex !== 1 || arg0End === -1) return null;

    // Resolve the first argument (table code) — must be a string literal
    const tableArg = callExpr.slice(arg0Start, arg0End).trim();
    const tableMatch = tableArg.match(/^["']([^"']+)["']$/);
    if (!tableMatch) return null;

    // arg1CharStart in absolute linePrefix coordinates, trimmed past whitespace
    const arg1AbsRaw = lastMatch.index + arg1Start;
    const leadingSpaces = (callExpr.slice(arg1Start).match(/^(\s*)/) || [""])[0].length;

    return {
        tableCode: tableMatch[1].toUpperCase(),
        arg1CharStart: arg1AbsRaw + leadingSpaces
    };
}

/** Maximum number of field names to show inline before appending `...+N`. */
const FIELD_PREVIEW_MAX = 3;

/**
 * Builds a compact inline field preview string, e.g.:
 *   "RAz_Division, RAz_Fund, RAz_Long_Payroll ...+2"
 *
 * @param {Array<{field: string}>} segments
 * @returns {string}
 */
function buildFieldPreview(segments) {
    if (segments.length === 0) return "(empty)";
    const shown = segments.slice(0, FIELD_PREVIEW_MAX).map(s => s.field);
    const overflow = segments.length - shown.length;
    return overflow > 0
        ? `${shown.join(", ")} ...+${overflow}`
        : shown.join(", ");
}

/**
 * Builds completion items for the index-number argument of INDEX_OPEN_STANDARD.
 *
 * Only numeric indexes are listed — PK and any other named indexes are omitted
 * because INDEX_OPEN_STANDARD takes a SHORT integer, not a string.
 *
 * @param {string} tableCode
 * @returns {Promise<vscode.CompletionItem[]>}
 */
async function buildIndexNumberCompletions(tableCode) {
    if (!isKnownTableStructure(tableCode)) {
        return [];
    }

    const structure = await getTableStructure(tableCode);
    if (!structure) {
        return [];
    }

    // Only numeric-named indexes are valid for the indexNumber argument
    const numericIndexes = structure.indexes.filter(idx => /^\d+$/.test(idx.name));

    const te = getTableEntry(tableCode);
    const tableLabel = te ? `${tableCode} (${te.name})` : tableCode;

    return numericIndexes.map((index, i) => {
        const item = new vscode.CompletionItem(index.name, vscode.CompletionItemKind.EnumMember);
        item.insertText = index.name;
        item.detail = buildFieldPreview(index.segments);
        item.sortText = String(i).padStart(3, "0");

        const dupeFlag = index.duplicates ? "duplicates allowed" : "unique";
        const modFlag = index.modifiable ? ", modifiable" : "";

        const markdown = new vscode.MarkdownString();
        markdown.appendMarkdown(`**${tableLabel}** · Index \`${index.name}\` — ${dupeFlag}${modFlag}\n\n`);
        markdown.appendMarkdown(`| Pos | Field | Type | Length |\n`);
        markdown.appendMarkdown(`|-----|-------|------|--------|\n`);
        for (const seg of index.segments) {
            markdown.appendMarkdown(`| ${seg.position} | \`${seg.field}\` | ${seg.type} | ${seg.length} |\n`);
        }

        item.documentation = markdown;
        return item;
    });
}

// ---------------------------------------------------------------------------
// Feature 4c: SHORT variable completions for INDEX_OPEN_STANDARD handle argument
// ---------------------------------------------------------------------------

/**
 * Detects whether the cursor is sitting inside the THIRD argument (handle, index 2)
 * of an INDEX_OPEN_STANDARD or SQL_INDEX_OPEN_STANDARD call.  Returns the absolute
 * character position where that argument starts and which function family was matched,
 * or null if the context does not match.
 *
 * @param {string} linePrefix
 * @returns {{ arg2CharStart: number, isSql: boolean }|null}
 */
function detectIndexOpenHandleContext(linePrefix) {
    const FUNC_RE = /\b(SQL_INDEX_OPEN_STANDARD|INDEX_OPEN_STANDARD)\s*\(/ig;
    let lastMatch = null;
    let m;
    while ((m = FUNC_RE.exec(linePrefix)) !== null) lastMatch = m;
    if (!lastMatch) return null;

    const callExpr   = linePrefix.slice(lastMatch.index);
    const parenIdx   = callExpr.indexOf("(");
    if (parenIdx === -1) return null;

    let depth = 0, inString = false, stringChar = null;
    let argIndex = 0;
    let arg2Start = -1;

    for (let i = parenIdx; i < callExpr.length; i++) {
        const ch = callExpr[i];
        if (inString) { if (ch === stringChar) inString = false; continue; }
        if (ch === '"' || ch === "'") { inString = true; stringChar = ch; continue; }
        if (ch === "(") {
            depth++;
        } else if (ch === ")") {
            depth--;
            if (depth === 0) return null;
        } else if (ch === "," && depth === 1) {
            argIndex++;
            if (argIndex === 2) arg2Start = i + 1;
        }
    }

    if (argIndex !== 2 || arg2Start === -1) return null;

    const leadingSpaces = (callExpr.slice(arg2Start).match(/^(\s*)/) || [""])[0].length;
    const isSql = lastMatch[1].toUpperCase().startsWith("SQL_");
    return { arg2CharStart: lastMatch.index + arg2Start + leadingSpaces, isSql };
}

/**
 * Builds completion items for the handle argument.
 * SQL_INDEX_OPEN_STANDARD uses a LONG handle; INDEX_OPEN_STANDARD uses a SHORT handle.
 *
 * @param {vscode.TextDocument} document
 * @param {number} lineIndex
 * @param {boolean} isSql - true when inside SQL_INDEX_OPEN_STANDARD (LONG handle)
 * @returns {vscode.CompletionItem[]}
 */
function buildHandleVariableCompletions(document, lineIndex, isSql) {
    let facts;
    try {
        facts = collectDocumentFacts(document);
    } catch {
        return [];
    }

    const handleType = isSql ? "LONG" : "SHORT";
    const vars = getVariablesInScope(facts, lineIndex);
    return vars
        .filter(v => v.type === handleType)
        .map(v => {
            const item = new vscode.CompletionItem(v.name, vscode.CompletionItemKind.Variable);
            item.insertText   = v.name;
            item.detail       = `${handleType} — handle`;
            item.filterText   = v.name;
            item.sortText     = `0_${v.name.toUpperCase()}`;
            return item;
        });
}

// ---------------------------------------------------------------------------
// Feature 5 helpers
// ---------------------------------------------------------------------------

/**
 * Detects whether the line prefix ends with an INDEX_LOAD_KEY_ or SQL_INDEX_LOAD_KEY_
 * stem (possibly with a partial suffix already typed).
 *
 * Returns { prefix: string } with the matched stem, or null if no match.
 *
 * @param {string} linePrefix
 * @returns {{ prefix: string }|null}
 */
function detectLoadKeyContext(linePrefix) {
    if (/\bSQL_INDEX_LOAD_KEY_[A-Z]*$/i.test(linePrefix)) return { prefix: "SQL_INDEX_LOAD_KEY_" };
    if (/\bINDEX_LOAD_KEY_[A-Z]*$/i.test(linePrefix)) return { prefix: "INDEX_LOAD_KEY_" };
    return null;
}

const LOAD_KEY_SUFFIXES = ["STRING", "SHORT", "LONG", "DATE"];

/**
 * Builds INDEX_LOAD_KEY_* or SQL_INDEX_LOAD_KEY_* completion items, ranking the
 * expected suffix first.  The `loadKeyPrefix` parameter determines which family
 * of items to generate.
 *
 * @param {vscode.TextDocument} document
 * @param {number} lineIndex
 * @param {string} loadKeyPrefix  e.g. "INDEX_LOAD_KEY_" or "SQL_INDEX_LOAD_KEY_"
 * @returns {Promise<vscode.CompletionItem[]>}
 */
async function buildLoadKeySuffixCompletions(document, lineIndex, loadKeyPrefix) {
    // Find the most recently opened (and not yet closed) handle up to this line
    const loadPositions = buildLoadPositionsAtLine(document, lineIndex);

    // Pick the last-opened handle (iterate to end of map to get most recent)
    let activeState = null;
    for (const state of loadPositions.values()) {
        activeState = state;
    }

    let expectedSuffix = null;
    let segmentHint = null;

    if (activeState) {
        const structure = await getTableStructure(activeState.tableCode);
        if (structure) {
            const index = structure.indexes.find(idx => idx.name === activeState.indexName);
            if (index) {
                const seg = index.segments[activeState.nextLoadPosition];
                if (seg) {
                    expectedSuffix = TYPE_TO_LOAD_KEY_SUFFIX[seg.type] || null;
                    segmentHint = `position ${seg.position}: \`${seg.field}\` (${seg.type}(${seg.length}))`;
                }
            }
        }
    }

    return LOAD_KEY_SUFFIXES.map((suffix, i) => {
        const isExpected = suffix === expectedSuffix;
        const fullName = `${loadKeyPrefix}${suffix}`;
        const item = new vscode.CompletionItem(fullName, vscode.CompletionItemKind.Function);
        item.insertText = new vscode.SnippetString(`${fullName}(\${1:handle}, \${2:value}${suffix === "STRING" ? ", \${3:length}" : ""})`);
        item.filterText = fullName;
        // Expected suffix sorts first; others follow in definition order
        item.sortText = isExpected ? `0_${suffix}` : `1_${String(i).padStart(2, "0")}_${suffix}`;

        if (isExpected && segmentHint) {
            item.detail = `Expected — ${segmentHint}`;
        } else if (segmentHint) {
            item.detail = `Expected: ${loadKeyPrefix}${expectedSuffix}`;
        } else {
            item.detail = fullName;
        }

        return item;
    });
}

// ---------------------------------------------------------------------------
// Provider registration
// ---------------------------------------------------------------------------

function initializeIndexCompletionProvider(context) {
    const provider = vscode.languages.registerCompletionItemProvider(
        LANGUAGE_ID,
        {
            async provideCompletionItems(document, position) {
                const lineText = document.lineAt(position.line).text;
                const linePrefix = lineText.slice(0, position.character);

                // No completions inside comments
                if (linePrefix.trimStart().startsWith("//")) {
                    return [];
                }

                // Feature 4a: table code inside INDEX_OPEN_STANDARD first arg
                const tableCtx = detectIndexOpenTableContext(linePrefix);
                if (tableCtx) {
                    const items = buildTableCodeCompletions();

                    // If the character immediately after the cursor is a closing quote
                    // (VS Code auto-pair inserted ""), extend the range to cover it so
                    // that accepting a completion replaces "" rather than leaving a
                    // stray trailing quote.
                    const charAfterCursor = lineText[position.character];
                    const isInsideAutoPairedQuote = charAfterCursor === '"' || charAfterCursor === "'";
                    const rangeEnd = isInsideAutoPairedQuote ? position.character + 1 : position.character;

                    const replaceRange = new vscode.Range(
                        position.line, tableCtx.arg0CharStart,
                        position.line, rangeEnd
                    );

                    // Determine the correct filterText for each item based on what
                    // the user has typed so far in this argument:
                    //
                    //  • partial arg starts with a quote (e.g. `"` or `"M`):
                    //      VS Code's filter prefix will be `"` or `M` respectively.
                    //      When it is `"`, our code-only filterText ("MD") does NOT
                    //      start with `"` so VS Code filters everything out and
                    //      dismisses the list.  Using `"MD"` as filterText keeps
                    //      items alive for a `"` prefix.  When the user then types
                    //      a letter (prefix = `M`), the wordAtCursor branch below
                    //      takes over and sets filterText = `M` correctly.
                    //
                    //  • user typed code letters without a quote (e.g. `M`):
                    //      Set filterText to those letters so VS Code narrows by them.
                    //
                    //  • nothing typed yet (empty prefix):
                    //      Leave filterText as the bare code so all items are shown.
                    const partialArg = linePrefix.slice(tableCtx.arg0CharStart);
                    const argStartsWithQuote = /^["']/.test(partialArg);
                    const wordAtCursor = linePrefix.match(/([A-Za-z0-9]+)$/)?.[1] ?? "";

                    for (const item of items) {
                        item.range = replaceRange;
                        if (wordAtCursor) {
                            item.filterText = wordAtCursor;
                        } else if (argStartsWithQuote) {
                            // item.filterText is currently the bare code (set in
                            // buildTableCodeCompletions); wrap it in quotes so the
                            // item survives a '"' filter prefix.
                            item.filterText = `"${item.filterText}"`;
                        }
                        // else: leave filterText = bare code; empty prefix matches all
                    }
                    return items;
                }

                // Feature 4c: handle variable inside INDEX_OPEN_STANDARD / SQL_INDEX_OPEN_STANDARD third arg
                const handleCtx = detectIndexOpenHandleContext(linePrefix);
                if (handleCtx) {
                    const items = buildHandleVariableCompletions(document, position.line, handleCtx.isSql);
                    const wordAtCursor = linePrefix.match(/([A-Za-z_][A-Za-z0-9_]*)$/)?.[1] ?? "";
                    const replaceRange = new vscode.Range(
                        position.line, handleCtx.arg2CharStart,
                        position.line, position.character
                    );
                    for (const item of items) {
                        item.range = replaceRange;
                        if (wordAtCursor) item.filterText = wordAtCursor;
                    }
                    return items;
                }

                // Feature 4b: index number inside INDEX_OPEN_STANDARD second arg
                const openCtx = detectIndexOpenContext(linePrefix);
                if (openCtx) {
                    const items = await buildIndexNumberCompletions(openCtx.tableCode);

                    // Range: replace from the start of arg 1 to the cursor so that
                    // accepting a completion replaces any snippet placeholder text
                    // (e.g. "indexNumber: SHORT") with the chosen integer.
                    const replaceRange = new vscode.Range(
                        position.line, openCtx.arg1CharStart,
                        position.line, position.character
                    );

                    // filterText: if the text at the cursor ends with an alphabetic
                    // word (e.g. the "SHORT" of a placeholder), VS Code would filter
                    // out the integer completion labels ("0", "1"…).  Setting
                    // filterText to that same word tells VS Code these items DO match.
                    const wordAtCursor = linePrefix.match(/([A-Za-z_][A-Za-z0-9_]*)$/)?.[1] ?? "";

                    for (const item of items) {
                        item.range = replaceRange;
                        if (wordAtCursor) item.filterText = wordAtCursor;
                    }
                    return items;
                }

                // Feature 5: INDEX_LOAD_KEY_ / SQL_INDEX_LOAD_KEY_ suffix
                const loadKeyCtx = detectLoadKeyContext(linePrefix);
                if (loadKeyCtx) {
                    return buildLoadKeySuffixCompletions(document, position.line, loadKeyCtx.prefix);
                }

                return [];
            }
        },
        // Triggers: "(" for table code (4a), comma for index number (4b), underscore for LOAD_KEY (5).
        // '"' and "'" are also triggers so that when VS Code auto-pairs a quote to "" the provider
        // re-fires.  At that point argStartsWithQuote is true and each item's filterText is set to
        // '"CODE"', which starts with '"' — so VS Code does not dismiss the list.
        "(", ",", "_", " ", '"', "'",
    );

    context.subscriptions.push(provider);
}

export { initializeIndexCompletionProvider };
