/**
 * Unit tests for core/src/analysis/blockContext.js
 */

import { getBlockContextAtLine } from "../../core/src/analysis/blockContext.js";

// Each document gets a unique URI so the AST cache doesn't bleed between tests.
let _seq = 0;
function makeDoc(lines) {
    const uid = ++_seq;
    return {
        lineCount: lines.length,
        lineAt: (i) => ({ text: lines[i] }),
        getText: () => lines.join("\n"),
        uri: { fsPath: `/test/blockContext_${uid}.txt`, toString: () => `file:///test/blockContext_${uid}.txt` },
        version: 1
    };
}

describe("getBlockContextAtLine", () => {
    describe("empty / top-level", () => {
        test("empty document → empty stack, hashMode on", () => {
            const doc = makeDoc([""]);
            const { stack, hashMode } = getBlockContextAtLine(doc, 0);
            expect(stack).toHaveLength(0);
            expect(hashMode).toBe("on");
        });

        test("cursor before any IF → empty stack", () => {
            const doc = makeDoc(["#HASH-OFF", "IF (x = 1)", "ENDIF"]);
            const { stack } = getBlockContextAtLine(doc, 1); // before IF line
            expect(stack).toHaveLength(0);
        });

        test("cursor after ENDIF → empty stack", () => {
            const doc = makeDoc(["#HASH-OFF", "IF (x = 1)", "ENDIF", ""]);
            const { stack } = getBlockContextAtLine(doc, 3);
            expect(stack).toHaveLength(0);
        });
    });

    describe("single IF block", () => {
        test("cursor inside IF → stack has IF", () => {
            // #HASH-OFF on line 0 switches to hash-off mode; bare lines become code.
            const doc = makeDoc(["#HASH-OFF", "IF (x = 1)", "  SET y = 2", "ENDIF"]);
            const { stack } = getBlockContextAtLine(doc, 2); // line 2 = inside IF
            expect(stack).toHaveLength(1);
            expect(stack[0].kind).toBe("IF");
            expect(stack[0].lineIndex).toBe(1);
        });
    });

    describe("single DO block", () => {
        test("cursor inside DO → stack has DO", () => {
            const doc = makeDoc(["#HASH-OFF", "DO i = 1 TO 10", "  SET x = i", "ENDDO"]);
            const { stack } = getBlockContextAtLine(doc, 2);
            expect(stack).toHaveLength(1);
            expect(stack[0].kind).toBe("DO");
        });
    });

    describe("single CASE block", () => {
        test("cursor inside CASE → stack has CASE", () => {
            const doc = makeDoc(["#HASH-OFF", "CASE x", "VALUE 1", "ENDCASE"]);
            const { stack } = getBlockContextAtLine(doc, 2);
            expect(stack).toHaveLength(1);
            expect(stack[0].kind).toBe("CASE");
        });
    });

    describe("single FUNCTION block", () => {
        test("cursor inside FUNCTION → stack has FUNCTION", () => {
            const doc = makeDoc(["#HASH-OFF", "FUNCTION Foo() : STRING", "RETURN \"x\"", "ENDFUNCTION"]);
            const { stack } = getBlockContextAtLine(doc, 2);
            expect(stack).toHaveLength(1);
            expect(stack[0].kind).toBe("FUNCTION");
        });
    });

    describe("nested blocks", () => {
        test("cursor inside IF → DO → innermost is DO", () => {
            const doc = makeDoc([
                "#HASH-OFF",
                "IF (x = 1)",
                "  DO i = 1 TO 5",
                "    SET y = i",
                "  ENDDO",
                "ENDIF"
            ]);
            const { stack } = getBlockContextAtLine(doc, 3); // inside DO
            expect(stack).toHaveLength(2);
            expect(stack[0].kind).toBe("IF");
            expect(stack[1].kind).toBe("DO");
        });

        test("cursor between ENDDO and ENDIF → only IF remains", () => {
            const doc = makeDoc([
                "#HASH-OFF",
                "IF (x = 1)",
                "  DO i = 1 TO 5",
                "  ENDDO",
                "  SET z = 1",
                "ENDIF"
            ]);
            const { stack } = getBlockContextAtLine(doc, 4);
            expect(stack).toHaveLength(1);
            expect(stack[0].kind).toBe("IF");
        });
    });

    describe("HASH-ON / HASH-OFF tracking", () => {
        test("defaults to hashMode on", () => {
            const doc = makeDoc(["#SET x = 1"]);
            const { hashMode } = getBlockContextAtLine(doc, 1);
            expect(hashMode).toBe("on");
        });

        test("HASH-OFF before cursor → hashMode off", () => {
            // #HASH-OFF switches mode; subsequent lines are bare code
            const doc = makeDoc(["#HASH-OFF", "SET x = 1"]);
            const { hashMode } = getBlockContextAtLine(doc, 1);
            expect(hashMode).toBe("off");
        });

        test("HASH-OFF then HASH-ON before cursor → hashMode on", () => {
            const doc = makeDoc(["#HASH-OFF", "#HASH-ON", "#SET x = 1"]);
            const { hashMode } = getBlockContextAtLine(doc, 2);
            expect(hashMode).toBe("on");
        });

        test("HASH-OFF on cursor line itself is not counted", () => {
            const doc = makeDoc(["#SET x = 1", "#HASH-OFF"]);
            // cursorLineIndex = 1 — that line is not yet committed
            const { hashMode } = getBlockContextAtLine(doc, 1);
            expect(hashMode).toBe("on");
        });
    });

    describe("MACRO is not tracked as a block opener", () => {
        test("MACRO line does not push to stack", () => {
            const doc = makeDoc(["#HASH-OFF", "MACRO MY_MACRO", "SET x = 1"]);
            const { stack } = getBlockContextAtLine(doc, 2);
            expect(stack).toHaveLength(0);
        });
    });
});
