import * as vscode from "../__mocks__/vscode.js";
import { createMockDocument } from "../helpers/mockDocument.js";
import { createBuiltinCompletionItems } from "../../vscode-extension/src/builtinCompletionProvider.js";

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/** Builds a single-line document with the cursor at the end of that line. */
function docAt(line) {
    const doc = createMockDocument([line]);
    const position = new vscode.Position(0, line.length);
    return { doc, position };
}

/** Builds a multi-line document with the cursor at end of the given lineIndex. */
function docAtLine(lines, lineIndex) {
    const doc = createMockDocument(lines);
    const position = new vscode.Position(lineIndex, lines[lineIndex].length);
    return { doc, position };
}

// ---------------------------------------------------------------------------
// Context suppression
// ---------------------------------------------------------------------------

describe("builtinCompletionProvider — context suppression", () => {
    it("returns empty array for plain comment lines (//)", () => {
        const { doc, position } = docAt("// GET_TOKEN is a string function");
        expect(createBuiltinCompletionItems(doc, position)).toEqual([]);
    });

    it("returns empty array for output lines (>>)", () => {
        const { doc, position } = docAt(">> The value is GET");
        expect(createBuiltinCompletionItems(doc, position)).toEqual([]);
    });

    it("returns empty array for conditional output lines (??)", () => {
        const { doc, position } = docAt("?? GET_TOKEN(x, y, 1) = \"hello\"");
        expect(createBuiltinCompletionItems(doc, position)).toEqual([]);
    });

    it("returns empty array for HASH comment lines (#//)", () => {
        const { doc, position } = docAt("#// GET_TOKEN is mentioned here");
        expect(createBuiltinCompletionItems(doc, position)).toEqual([]);
    });

    it("returns empty array for HASH NOTE lines (#NOTE)", () => {
        const { doc, position } = docAt("#NOTE GET_TOKEN is mentioned here");
        expect(createBuiltinCompletionItems(doc, position)).toEqual([]);
    });

    it("returns empty array when no identifier prefix is typed", () => {
        const { doc, position } = docAt("SET x = ");
        expect(createBuiltinCompletionItems(doc, position)).toEqual([]);
    });
});

// ---------------------------------------------------------------------------
// Function completions
// ---------------------------------------------------------------------------

describe("builtinCompletionProvider — function completions", () => {
    it("returns a function item matching the typed prefix", () => {
        const { doc, position } = docAt("SET x = GET_TOK");
        const items = createBuiltinCompletionItems(doc, position);
        const labels = items.map((i) => i.label);
        expect(labels).toContain("GET_TOKEN");
    });

    it("function item kind is CompletionItemKind.Function", () => {
        const { doc, position } = docAt("SET x = GET_TOK");
        const items = createBuiltinCompletionItems(doc, position);
        const item = items.find((i) => i.label === "GET_TOKEN");
        expect(item.kind).toBe(vscode.CompletionItemKind.Function);
    });

    it("function insert text is a SnippetString with parameter placeholders", () => {
        // GET_TOKEN has 3 params: STRING, STRING, SHORT
        const { doc, position } = docAt("SET x = GET_TOK");
        const items = createBuiltinCompletionItems(doc, position);
        const item = items.find((i) => i.label === "GET_TOKEN");
        expect(item.insertText).toBeInstanceOf(vscode.SnippetString);
        expect(item.insertText.value).toMatch(/GET_TOKEN\(/);
        expect(item.insertText.value).toMatch(/\$\{1:/);
        expect(item.insertText.value).toMatch(/\$\{2:/);
        expect(item.insertText.value).toMatch(/\$\{3:/);
    });

    it("zero-param function insert text is name() with no tabstops", () => {
        // SYS_SECONDS has no params
        const { doc, position } = docAt("SET x = SYS_SEC");
        const items = createBuiltinCompletionItems(doc, position);
        const item = items.find((i) => i.label === "SYS_SECONDS");
        expect(item.insertText).toBeInstanceOf(vscode.SnippetString);
        expect(item.insertText.value).toBe("SYS_SECONDS()");
    });

    it("varArgs function insert text ends with a variadic placeholder", () => {
        // READ_ACCOUNT has 2 fixed params + varArgs
        const { doc, position } = docAt("SET x = READ_ACC");
        const items = createBuiltinCompletionItems(doc, position);
        const item = items.find((i) => i.label === "READ_ACCOUNT");
        expect(item.insertText.value).toMatch(/\.\.\./);
    });

    it("function detail shows return type", () => {
        const { doc, position } = docAt("SET x = GET_TOK");
        const items = createBuiltinCompletionItems(doc, position);
        const item = items.find((i) => i.label === "GET_TOKEN");
        expect(item.detail).toContain("STRING");
    });

    it("function documentation includes description from registry", () => {
        const { doc, position } = docAt("SET x = GET_TOK");
        const items = createBuiltinCompletionItems(doc, position);
        const item = items.find((i) => i.label === "GET_TOKEN");
        expect(item.documentation.value).toContain("token");
    });

    it("function sortText starts with '1_'", () => {
        const { doc, position } = docAt("SET x = GET_TOK");
        const items = createBuiltinCompletionItems(doc, position);
        const item = items.find((i) => i.label === "GET_TOKEN");
        expect(item.sortText).toMatch(/^1_/);
    });

    it("does not return constants in function results", () => {
        // NEW_LINE_STR is a constant — should not appear when typing "NEW"
        // unless constants are also returned (they are, but kinds differ)
        const { doc, position } = docAt("SET x = NEW_LINE");
        const items = createBuiltinCompletionItems(doc, position);
        const constantItem = items.find((i) => i.label === "NEW_LINE_STR");
        if (constantItem) {
            expect(constantItem.kind).toBe(vscode.CompletionItemKind.GlobalVariable);
        }
    });
});

// ---------------------------------------------------------------------------
// BuiltInGlobalVariable completions
// ---------------------------------------------------------------------------

describe("builtinCompletionProvider — global variable completions", () => {
    it("returns a global variable item matching the typed prefix", () => {
        const { doc, position } = docAt("SET x = NEW_LINE");
        const items = createBuiltinCompletionItems(doc, position);
        const labels = items.map((i) => i.label);
        expect(labels).toContain("NEW_LINE_STR");
    });

    it("global variable item kind is CompletionItemKind.GlobalVariable", () => {
        const { doc, position } = docAt("SET x = NEW_LINE");
        const items = createBuiltinCompletionItems(doc, position);
        const item = items.find((i) => i.label === "NEW_LINE_STR");
        expect(item.kind).toBe(vscode.CompletionItemKind.GlobalVariable);
    });

    it("global variable insert text is the plain name (no parens or snippet)", () => {
        const { doc, position } = docAt("SET x = NEW_LINE");
        const items = createBuiltinCompletionItems(doc, position);
        const item = items.find((i) => i.label === "NEW_LINE_STR");
        expect(typeof item.insertText).toBe("string");
        expect(item.insertText).toBe("NEW_LINE_STR");
    });

    it("global variable detail shows type", () => {
        const { doc, position } = docAt("SET x = NEW_LINE");
        const items = createBuiltinCompletionItems(doc, position);
        const item = items.find((i) => i.label === "NEW_LINE_STR");
        expect(item.detail).toContain("STRING");
    });

    it("global variable sortText starts with '2_'", () => {
        const { doc, position } = docAt("SET x = NEW_LINE");
        const items = createBuiltinCompletionItems(doc, position);
        const item = items.find((i) => i.label === "NEW_LINE_STR");
        expect(item.sortText).toMatch(/^2_/);
    });
});

// ---------------------------------------------------------------------------
// Output line interpolation
// ---------------------------------------------------------------------------

describe("builtinCompletionProvider — output line interpolation", () => {
    it("returns completions when cursor is inside @...@ on a >> line", () => {
        // Default OICC/CICC is "@". Cursor inside the open delimiter.
        const line = ">> The result is @GET_TOK";
        const doc = createMockDocument([line]);
        // cursor at end of line — inside the open "@"
        const position = new vscode.Position(0, line.length);
        const items = createBuiltinCompletionItems(doc, position);
        const labels = items.map((i) => i.label);
        expect(labels).toContain("GET_TOKEN");
    });

    it("returns empty array when cursor is outside @...@ on a >> line", () => {
        // Cursor is in plain output text, not inside delimiters
        const line = ">> The result is @GET_TOKEN(x,y,1)@ and some text GET";
        const doc = createMockDocument([line]);
        const position = new vscode.Position(0, line.length);
        const items = createBuiltinCompletionItems(doc, position);
        expect(items).toEqual([]);
    });

    it("returns completions inside @...@ on a ?? line", () => {
        const line = "?? @DB_STA";
        const doc = createMockDocument([line]);
        const position = new vscode.Position(0, line.length);
        const items = createBuiltinCompletionItems(doc, position);
        const labels = items.map((i) => i.label);
        expect(labels).toContain("DB_STATUS");
    });
});

// ---------------------------------------------------------------------------
// Condition statement lines
// ---------------------------------------------------------------------------

describe("builtinCompletionProvider — condition statement lines", () => {
    it("returns completions on an IF line", () => {
        const { doc, position } = docAt("IF GET_TOK");
        const items = createBuiltinCompletionItems(doc, position);
        const labels = items.map((i) => i.label);
        expect(labels).toContain("GET_TOKEN");
    });

    it("returns completions on a CASE line", () => {
        const { doc, position } = docAt("CASE DB_STA");
        const items = createBuiltinCompletionItems(doc, position);
        const labels = items.map((i) => i.label);
        expect(labels).toContain("DB_STATUS");
    });

    it("returns completions on a DO WHILE line", () => {
        const { doc, position } = docAt("DO WHILE GET_TOK");
        const items = createBuiltinCompletionItems(doc, position);
        const labels = items.map((i) => i.label);
        expect(labels).toContain("GET_TOKEN");
    });

    it("returns completions on a UNTIL line", () => {
        const { doc, position } = docAt("UNTIL DB_STA");
        const items = createBuiltinCompletionItems(doc, position);
        const labels = items.map((i) => i.label);
        expect(labels).toContain("DB_STATUS");
    });
});

// ---------------------------------------------------------------------------
// HASH-ON executable lines
// ---------------------------------------------------------------------------

describe("builtinCompletionProvider — HASH-ON executable lines", () => {
    it("returns completions on a #SET line", () => {
        const { doc, position } = docAt("#SET x = GET_TOK");
        const items = createBuiltinCompletionItems(doc, position);
        const labels = items.map((i) => i.label);
        expect(labels).toContain("GET_TOKEN");
    });

    it("returns completions on a #IF line", () => {
        const { doc, position } = docAt("#IF DB_STA");
        const items = createBuiltinCompletionItems(doc, position);
        const labels = items.map((i) => i.label);
        expect(labels).toContain("DB_STATUS");
    });
});

// ---------------------------------------------------------------------------
// Setting: acurity.code-completion
// ---------------------------------------------------------------------------

describe("builtinCompletionProvider — acurity.code-completion setting", () => {
    afterEach(() => {
        vscode.workspace.clearConfigOverrides();
    });

    it("returns empty array when acurity.code-completion is false", () => {
        vscode.workspace.setConfigOverride("acurity", "code-completion", false);
        const { doc, position } = docAt("GET_TOK");
        expect(createBuiltinCompletionItems(doc, position)).toEqual([]);
    });

    it("returns items normally when acurity.code-completion is true", () => {
        vscode.workspace.setConfigOverride("acurity", "code-completion", true);
        const { doc, position } = docAt("SET x = GET_TOK");
        const items = createBuiltinCompletionItems(doc, position);
        expect(items.length).toBeGreaterThan(0);
    });
});

// ---------------------------------------------------------------------------
// Sort order
// ---------------------------------------------------------------------------

describe("builtinCompletionProvider — sort order", () => {
    it("functions sort before constants when both match prefix", () => {
        // Both GET_TOKEN (function) and e.g. DB_STATUS (constant) won't share a prefix,
        // so use a prefix that hits both types by checking sortText values directly.
        const { doc, position } = docAt("SET x = GET_TOK");
        const items = createBuiltinCompletionItems(doc, position);
        const fnItem = items.find((i) => i.label === "GET_TOKEN");
        expect(fnItem.sortText < "2_").toBe(true); // functions sort before constants
    });
});
