/**
 * Unit tests for doIteratorValidator.
 *
 * ACU-SYN-004: DO iterator with neither TO nor BY clause.
 * ACU-SYN-005: DO iterator with BY but no TO, no BREAK, and not UNTIL-closed.
 */

import { validateDoIterators } from "../../../core/src/validators/doIteratorValidator.js";
import { makeDocument, alwaysError, alwaysWarn, alwaysOff, messageIds } from "./helpers.js";

// ─────────────────────────────────────────────────────────────────────────────
// ACU-SYN-004 — missing clause
// ─────────────────────────────────────────────────────────────────────────────

describe("validateDoIterators — ACU-SYN-004 (missing clause)", () => {
    test("fires when DO iterator has neither TO nor BY", () => {
        const doc = makeDocument([
            "#HASH-OFF",
            "VAR sNum : SHORT",
            "DO sNum = 0",
            "ENDDO",
        ]);
        const diags = validateDoIterators(doc, alwaysError);
        expect(messageIds(diags)).toContain("ACU-SYN-004");
    });

    test("does not fire when TO is present", () => {
        const doc = makeDocument([
            "#HASH-OFF",
            "VAR sNum : SHORT",
            "DO sNum = 0 TO 9",
            "ENDDO",
        ]);
        const diags = validateDoIterators(doc, alwaysError);
        expect(messageIds(diags)).not.toContain("ACU-SYN-004");
    });

    test("does not fire when BY is present (even without TO)", () => {
        const doc = makeDocument([
            "#HASH-OFF",
            "VAR sNum : SHORT",
            "DO sNum = 0 BY 1",
            "ENDDO",
        ]);
        const diags = validateDoIterators(doc, alwaysError);
        expect(messageIds(diags)).not.toContain("ACU-SYN-004");
    });

    test("suppressed when getRuleSeverity returns null", () => {
        const doc = makeDocument([
            "#HASH-OFF",
            "VAR sNum : SHORT",
            "DO sNum = 0",
            "ENDDO",
        ]);
        expect(validateDoIterators(doc, alwaysOff)).toHaveLength(0);
    });
});

// ─────────────────────────────────────────────────────────────────────────────
// ACU-SYN-005 — BY with no TO and no BREAK (indefinite loop)
// ─────────────────────────────────────────────────────────────────────────────

describe("validateDoIterators — ACU-SYN-005 (no upper bound)", () => {
    test("fires when BY present, no TO, no BREAK, ENDDO closure", () => {
        const doc = makeDocument([
            "#HASH-OFF",
            "VAR sNum : SHORT",
            "DO sNum = 0 BY 1",
            "ENDDO",
        ]);
        const diags = validateDoIterators(doc, alwaysWarn);
        expect(messageIds(diags)).toContain("ACU-SYN-005");
    });

    test("does not fire when TO is also present", () => {
        const doc = makeDocument([
            "#HASH-OFF",
            "VAR sNum : SHORT",
            "DO sNum = 0 TO 9 BY 1",
            "ENDDO",
        ]);
        const diags = validateDoIterators(doc, alwaysWarn);
        expect(messageIds(diags)).not.toContain("ACU-SYN-005");
    });

    test("does not fire when BREAK is in the body", () => {
        const doc = makeDocument([
            "#HASH-OFF",
            "VAR sNum : SHORT",
            "DO sNum = 0 BY 1",
            "    IF sNum > 10",
            "        BREAK",
            "    ENDIF",
            "ENDDO",
        ]);
        const diags = validateDoIterators(doc, alwaysWarn);
        expect(messageIds(diags)).not.toContain("ACU-SYN-005");
    });

    test("does not fire when loop is closed by UNTIL", () => {
        const doc = makeDocument([
            "#HASH-OFF",
            "VAR sNum : SHORT",
            "DO sNum = 0 BY 1",
            "UNTIL sNum >= 9",
        ]);
        const diags = validateDoIterators(doc, alwaysWarn);
        expect(messageIds(diags)).not.toContain("ACU-SYN-005");
    });

    test("BREAK in nested DO body does not count as exit for outer loop", () => {
        // BREAK in inner loop exits the inner loop, not the outer one.
        const doc = makeDocument([
            "#HASH-OFF",
            "VAR sNum : SHORT",
            "VAR sInner : SHORT",
            "DO sNum = 0 BY 1",
            "    DO sInner = 0 TO 5",
            "        BREAK",
            "    ENDDO",
            "ENDDO",
        ]);
        const diags = validateDoIterators(doc, alwaysWarn);
        // sNum loop has no direct BREAK — should still fire
        expect(messageIds(diags)).toContain("ACU-SYN-005");
    });

    test("suppressed when getRuleSeverity returns null", () => {
        const doc = makeDocument([
            "#HASH-OFF",
            "VAR sNum : SHORT",
            "DO sNum = 0 BY 1",
            "ENDDO",
        ]);
        expect(validateDoIterators(doc, alwaysOff)).toHaveLength(0);
    });

    test("variable name appears in diagnostic params", () => {
        const doc = makeDocument([
            "#HASH-OFF",
            "VAR sCounter : SHORT",
            "DO sCounter = 0 BY 5",
            "ENDDO",
        ]);
        const diags = validateDoIterators(doc, alwaysWarn);
        const diag = diags.find(d => d.data && d.data.messageId === "ACU-SYN-005");
        expect(diag).toBeDefined();
        expect(diag.data.params.variable).toBe("sCounter");
    });
});
