/**
 * Unit tests for indentationValidator.
 * Covers ACU-BLK-004: close keyword or ELSE at different column than open keyword.
 */

import { validateBlockIndentation } from "../../../core/src/validators/indentationValidator.js";
import { makeDocument, alwaysError, alwaysOff, messageIds } from "./helpers.js";

describe("indentationValidator", () => {
    describe("well-formed blocks produce no diagnostics", () => {
        test("matched IF / ENDIF at same column", () => {
            const doc = makeDocument(["#HASH-OFF", "IF x = 1", "SET y = 2", "ENDIF"]);
            expect(validateBlockIndentation(doc, alwaysError)).toHaveLength(0);
        });

        test("matched DO / ENDDO at same column", () => {
            const doc = makeDocument(["#HASH-OFF", "DO", "SET x = 1", "ENDDO"]);
            expect(validateBlockIndentation(doc, alwaysError)).toHaveLength(0);
        });

        test("matched IF / ELSE / ENDIF all at same column", () => {
            const doc = makeDocument(["#HASH-OFF", "IF x = 1", "SET y = 1", "ELSE", "SET y = 2", "ENDIF"]);
            expect(validateBlockIndentation(doc, alwaysError)).toHaveLength(0);
        });

        test("nested blocks with consistent indentation", () => {
            const doc = makeDocument([
                "#HASH-OFF",
                "IF x = 1",
                "  IF y = 2",
                "    SET z = 3",
                "  ENDIF",
                "ENDIF"
            ]);
            expect(validateBlockIndentation(doc, alwaysError)).toHaveLength(0);
        });

        test("empty document", () => {
            expect(validateBlockIndentation(makeDocument([""]), alwaysError)).toHaveLength(0);
        });
    });

    describe("ACU-BLK-004 — close keyword indentation mismatch", () => {
        test("ENDIF at greater indent than IF", () => {
            const doc = makeDocument(["#HASH-OFF", "IF x = 1", "  SET y = 2", "  ENDIF"]);
            expect(messageIds(validateBlockIndentation(doc, alwaysError))).toContain("ACU-BLK-004");
        });

        test("ENDDO at greater indent than DO", () => {
            const doc = makeDocument(["#HASH-OFF", "DO", "  SET x = 1", "  ENDDO"]);
            expect(messageIds(validateBlockIndentation(doc, alwaysError))).toContain("ACU-BLK-004");
        });

        test("ENDIF at lesser indent than IF", () => {
            const doc = makeDocument(["#HASH-OFF", "  IF x = 1", "    SET y = 2", "ENDIF"]);
            expect(messageIds(validateBlockIndentation(doc, alwaysError))).toContain("ACU-BLK-004");
        });

        test("diagnostic is placed at the close keyword line", () => {
            const doc = makeDocument(["#HASH-OFF", "IF x = 1", "  SET y = 2", "  ENDIF"]);
            const diags = validateBlockIndentation(doc, alwaysError);
            const blk004 = diags.find((d) => d.data.messageId === "ACU-BLK-004");
            expect(blk004.range.start.line).toBe(3);
        });

        test("params record keyword, column, openKeyword, lineNumber, openColumn", () => {
            const doc = makeDocument(["#HASH-OFF", "IF x = 1", "  SET y = 2", "  ENDIF"]);
            const diags = validateBlockIndentation(doc, alwaysError);
            const blk004 = diags.find((d) => d.data.messageId === "ACU-BLK-004");
            expect(blk004.data.params).toMatchObject({
                keyword: "ENDIF",
                column: 2,
                openKeyword: "IF",
                lineNumber: 2,
                openColumn: 0
            });
        });
    });

    describe("ACU-BLK-004 — ELSE indentation mismatch", () => {
        test("ELSE at greater indent than IF", () => {
            const doc = makeDocument(["#HASH-OFF", "IF x = 1", "  SET y = 1", "  ELSE", "  SET y = 2", "ENDIF"]);
            expect(messageIds(validateBlockIndentation(doc, alwaysError))).toContain("ACU-BLK-004");
        });

        test("ELSE at lesser indent than IF", () => {
            const doc = makeDocument(["#HASH-OFF", "  IF x = 1", "    SET y = 1", "ELSE", "  SET y = 2", "  ENDIF"]);
            expect(messageIds(validateBlockIndentation(doc, alwaysError))).toContain("ACU-BLK-004");
        });

        test("ELSE params record openKeyword as IF", () => {
            const doc = makeDocument(["#HASH-OFF", "IF x = 1", "  SET y = 1", "  ELSE", "  SET y = 2", "ENDIF"]);
            const diags = validateBlockIndentation(doc, alwaysError);
            const blk004 = diags.find((d) => d.data.messageId === "ACU-BLK-004");
            expect(blk004.data.params.keyword).toBe("ELSE");
            expect(blk004.data.params.openKeyword).toBe("IF");
        });

        test("ELSE after ENDIF on stack (empty stack) does not crash", () => {
            // Stray ELSE with no open block — no indentation diagnostic (structural error handled elsewhere)
            const doc = makeDocument(["#HASH-OFF", "ELSE"]);
            expect(() => validateBlockIndentation(doc, alwaysError)).not.toThrow();
        });
    });

    describe("nested blocks", () => {
        test("outer pair matched, inner pair mismatched — only inner fires", () => {
            const doc = makeDocument([
                "#HASH-OFF",
                "IF outer = 1",
                "  IF inner = 2",
                "    SET z = 3",
                "ENDIF",    // mismatched with inner IF (col 2), fires ACU-BLK-004
                "ENDIF"     // mismatched with outer IF (col 0)... wait outer is now gone after inner popped
            ]);
            // Inner ENDIF at col 0 closes inner IF at col 2 → ACU-BLK-004
            // After pop, stack has outer IF at col 0; outer ENDIF at col 0 → no mismatch
            const diags = validateBlockIndentation(doc, alwaysError);
            expect(messageIds(diags).filter((id) => id === "ACU-BLK-004")).toHaveLength(1);
            expect(diags[0].range.start.line).toBe(4); // line index 4 = "ENDIF" (first)
        });

        test("two mismatches in separate blocks each fire", () => {
            const doc = makeDocument([
                "#HASH-OFF",
                "IF x = 1",
                "  SET y = 1",
                "  ENDIF",   // mismatch
                "IF a = 1",
                "  SET b = 1",
                "  ENDIF"    // mismatch
            ]);
            expect(messageIds(validateBlockIndentation(doc, alwaysError))
                .filter((id) => id === "ACU-BLK-004")).toHaveLength(2);
        });
    });

    describe("severity gate", () => {
        test("returns empty array when getRuleSeverity returns null", () => {
            const doc = makeDocument(["#HASH-OFF", "IF x = 1", "  SET y = 2", "  ENDIF"]);
            expect(validateBlockIndentation(doc, alwaysOff)).toHaveLength(0);
        });
    });

    describe("keyword parsing", () => {
        test("keywords matched case-insensitively", () => {
            const doc = makeDocument(["#HASH-OFF", "if x = 1", "  set y = 2", "  endif"]);
            expect(messageIds(validateBlockIndentation(doc, alwaysError))).toContain("ACU-BLK-004");
        });

        test("keywords in // comments are ignored", () => {
            const doc = makeDocument(["// IF x = 1", "// ENDIF"]);
            expect(validateBlockIndentation(doc, alwaysError)).toHaveLength(0);
        });

        test("# prefix on keyword is accepted", () => {
            const doc = makeDocument(["#IF x = 1", "  SET y = 2", "  #ENDIF"]);
            expect(messageIds(validateBlockIndentation(doc, alwaysError))).toContain("ACU-BLK-004");
        });
    });
});
