/**
 * Unit tests for structureValidator (block structure validation).
 * Covers ACU-BLK-001 (stray END), ACU-BLK-002 (mismatched END), ACU-BLK-003 (unclosed block).
 */

import { validateBlockStructure } from "../../../core/src/structureValidator.js";
import { makeDocument, alwaysError, alwaysOff, messageIds } from "./helpers.js";

describe('structureValidator', () => {
    describe('well-formed blocks produce no diagnostics', () => {
        test('matched IF / ENDIF', () => {
            const doc = makeDocument(['IF x = 1', 'SET y = 2', 'ENDIF']);
            expect(validateBlockStructure(doc, alwaysError)).toHaveLength(0);
        });

        test('matched DO / ENDDO', () => {
            const doc = makeDocument(['DO', 'SET x = 1', 'ENDDO']);
            expect(validateBlockStructure(doc, alwaysError)).toHaveLength(0);
        });

        test('matched CASE / ENDCASE', () => {
            const doc = makeDocument(['CASE x', 'SET y = 1', 'ENDCASE']);
            expect(validateBlockStructure(doc, alwaysError)).toHaveLength(0);
        });

        test('matched FUNCTION / ENDFUNCTION', () => {
            const doc = makeDocument(['FUNCTION Foo():STRING', 'RETURN "x"', 'ENDFUNCTION']);
            expect(validateBlockStructure(doc, alwaysError)).toHaveLength(0);
        });

        test('nested IF inside DO', () => {
            const doc = makeDocument(['DO', 'IF x = 1', 'SET y = 2', 'ENDIF', 'ENDDO']);
            expect(validateBlockStructure(doc, alwaysError)).toHaveLength(0);
        });

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

    describe('ACU-BLK-001 — END keyword without a matching open', () => {
        test('ENDIF with no preceding IF', () => {
            const doc = makeDocument(['#HASH-OFF', 'ENDIF']);
            expect(messageIds(validateBlockStructure(doc, alwaysError))).toContain('ACU-BLK-001');
        });

        test('ENDDO with no preceding DO', () => {
            const doc = makeDocument(['#HASH-OFF', 'ENDDO']);
            expect(messageIds(validateBlockStructure(doc, alwaysError))).toContain('ACU-BLK-001');
        });
    });

    describe('ACU-BLK-002 — END keyword does not match innermost open block', () => {
        test('ENDIF attempts to close a DO block', () => {
            const doc = makeDocument(['#HASH-OFF', 'DO', 'ENDIF']);
            expect(messageIds(validateBlockStructure(doc, alwaysError))).toContain('ACU-BLK-002');
        });

        test('ENDDO attempts to close an IF block', () => {
            const doc = makeDocument(['#HASH-OFF', 'IF x = 1', 'ENDDO']);
            expect(messageIds(validateBlockStructure(doc, alwaysError))).toContain('ACU-BLK-002');
        });
    });

    describe('ACU-BLK-003 — block opened but never closed', () => {
        test('unclosed IF', () => {
            const doc = makeDocument(['#HASH-OFF', 'IF x = 1', 'SET y = 2']);
            expect(messageIds(validateBlockStructure(doc, alwaysError))).toContain('ACU-BLK-003');
        });

        test('unclosed FUNCTION', () => {
            const doc = makeDocument(['#HASH-OFF', 'FUNCTION Foo():STRING', 'RETURN "x"']);
            expect(messageIds(validateBlockStructure(doc, alwaysError))).toContain('ACU-BLK-003');
        });

        test('diagnostic is placed at the opening keyword line', () => {
            const doc = makeDocument(['#HASH-OFF', 'IF x = 1', 'SET y = 2']);
            const diags = validateBlockStructure(doc, alwaysError);
            const blk003 = diags.find((d) => d.data.messageId === 'ACU-BLK-003');
            expect(blk003.range.start.line).toBe(1);
        });

        test('two unclosed blocks each produce ACU-BLK-003', () => {
            const doc = makeDocument(['#HASH-OFF', 'DO', 'IF x = 1']);
            const diags = validateBlockStructure(doc, alwaysError);
            expect(messageIds(diags).filter((id) => id === 'ACU-BLK-003')).toHaveLength(2);
        });
    });

    describe('getRuleSeverity integration', () => {
        test('returns empty array when getRuleSeverity returns null (rule is off)', () => {
            const doc = makeDocument(['#HASH-OFF', 'ENDIF']);
            expect(validateBlockStructure(doc, alwaysOff)).toHaveLength(0);
        });
    });

    describe('keyword parsing', () => {
        test('keywords are matched case-insensitively', () => {
            const doc = makeDocument(['if x = 1', 'set y = 2', 'endif']);
            expect(validateBlockStructure(doc, alwaysError)).toHaveLength(0);
        });

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

        test('# prefix on keyword is accepted', () => {
            const doc = makeDocument(['#IF x = 1', '#ENDIF']);
            expect(validateBlockStructure(doc, alwaysError)).toHaveLength(0);
        });
    });
});
