/**
 * Unit tests for indexUsageValidator (INDEX lifecycle patterns).
 */

import { validateIndexUsage } from "../../../core/src/validators/indexUsageValidator.js";
import { validateArrayUsage } from "../../../core/src/validators/arrayUsageValidator.js";
import { makeDocument, alwaysWarn, alwaysOff, messageIds } from "./helpers.js";

// Convenience wrapper matching the old combined signature used by these tests.
function validateUsagePatterns(doc, severity, opts) {
    return [...validateIndexUsage(doc, severity, opts), ...validateArrayUsage(doc, severity, opts)
    ];
}

describe('validateUsagePatterns', () => {
    describe('happy path — no diagnostics', () => {
        test('returns empty for document with no index operations', () => {
            const doc = makeDocument(['SET x = 1', 'OUTPUT "done"']);
            expect(validateUsagePatterns(doc, alwaysWarn)).toHaveLength(0);
        });

        test('returns empty for well-formed open/load/read/close sequence', () => {
            const doc = makeDocument([
                'INDEX_OPEN_BY_KEY(hCursor)',
                'INDEX_LOAD_KEY_VALUE(hCursor, zKey)',
                'INDEX_READ_BY_KEY(hCursor)',
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateUsagePatterns(doc, alwaysWarn);
            // ACU-PAT-003 may fire (recommend SAVE/RESTORE) but PAT-001 should not
            const pat001 = diags.filter((d) => d.data.messageId === 'ACU-PAT-001');
            expect(pat001).toHaveLength(0);
        });
    });

    describe('ACU-PAT-001 — missing INDEX_CLOSE', () => {
        test('flags open with no close', () => {
            const doc = makeDocument([
                'INDEX_OPEN_BY_KEY(hCursor)',
                'INDEX_LOAD_KEY_VALUE(hCursor, zKey)',
                'INDEX_READ_BY_KEY(hCursor)'
            ]);
            const diags = validateUsagePatterns(doc, alwaysWarn);
            expect(messageIds(diags)).toContain('ACU-PAT-001');
        });
    });

    describe('ACU-PAT-002 — unmatched SAVE/RESTORE', () => {
        test('flags SAVE without RESTORE', () => {
            const doc = makeDocument([
                'INDEX_SAVE_POSITION(hCursor)',
                'INDEX_READ_BY_KEY(hCursor)',
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateUsagePatterns(doc, alwaysWarn);
            expect(messageIds(diags)).toContain('ACU-PAT-002');
        });

        test('flags RESTORE without SAVE', () => {
            const doc = makeDocument([
                'INDEX_READ_BY_KEY(hCursor)',
                'INDEX_RESTORE_POSITION(hCursor)',
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateUsagePatterns(doc, alwaysWarn);
            expect(messageIds(diags)).toContain('ACU-PAT-002');
        });
    });

    describe('ACU-PAT-004 — READ before LOAD', () => {
        test('flags READ occurring before LOAD', () => {
            const doc = makeDocument([
                'INDEX_READ_BY_KEY(hCursor)',
                'INDEX_LOAD_KEY_VALUE(hCursor, zKey)',
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateUsagePatterns(doc, alwaysWarn);
            expect(messageIds(diags)).toContain('ACU-PAT-004');
        });
    });

    describe('ACU-PAT-005 — READ before OPEN', () => {
        test('flags READ occurring before OPEN', () => {
            const doc = makeDocument([
                'INDEX_READ_BY_KEY(hCursor)',
                'INDEX_OPEN_BY_KEY(hCursor)',
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateUsagePatterns(doc, alwaysWarn);
            expect(messageIds(diags)).toContain('ACU-PAT-005');
        });
    });

    describe('ACU-PAT-006 — CLOSE before READ', () => {
        test('flags CLOSE occurring before READ', () => {
            const doc = makeDocument([
                'INDEX_OPEN_BY_KEY(hCursor)',
                'INDEX_LOAD_KEY_VALUE(hCursor, zKey)',
                'INDEX_CLOSE(hCursor)',
                'INDEX_READ_BY_KEY(hCursor)'
            ]);
            const diags = validateUsagePatterns(doc, alwaysWarn);
            expect(messageIds(diags)).toContain('ACU-PAT-006');
        });

        test('does NOT flag CLOSE for handle A when handle B has later cursor operations', () => {
            // sHandle1 completes its load/read, then closes. Only after that does
            // sHandle2 do its load/read and close. The global last-read is sHandle2's,
            // which comes after sHandle1's close — but ACU-PAT-006 must NOT fire because
            // sHandle1's close is only compared against sHandle1's own cursor operations.
            const doc = makeDocument([
                'INDEX_OPEN_STANDARD("MD", 0, sHandle1)',
                'INDEX_LOAD_KEY_STRING(sHandle1, "KEY", 3)',
                'INDEX_READ_BY_KEY(sHandle1, ">")',
                'INDEX_CLOSE(sHandle1)',
                'INDEX_OPEN_STANDARD("PH", 0, sHandle2)',
                'INDEX_LOAD_KEY_STRING(sHandle2, "KEY", 3)',
                'INDEX_READ_BY_KEY(sHandle2, ">")',
                'INDEX_CLOSE(sHandle2)',
            ]);
            const diags = validateUsagePatterns(doc, alwaysWarn);
            const pat006 = diags.filter((d) => d.data.messageId === 'ACU-PAT-006');
            expect(pat006).toHaveLength(0);
        });
    });

    describe('severity', () => {
        test('produces no diagnostics when severity is off', () => {
            const doc = makeDocument([
                'INDEX_OPEN_BY_KEY(hCursor)',
                'INDEX_READ_BY_KEY(hCursor)'
                // deliberately missing CLOSE
            ]);
            expect(validateUsagePatterns(doc, alwaysOff)).toHaveLength(0);
        });
    });

    // Minimal table structure injected via options — avoids async registry
    const testTableStructures = {
        "TEST": {
            code: "TEST",
            indexes: [
                {
                    name: "0",
                    segments: [
                        { position: 0, field: "TESTz_Div",  type: "ZSTRING", length: 2 },
                        { position: 1, field: "TESTs_Num",  type: "SHORT",   length: 2 },
                        { position: 2, field: "TESTd_Date", type: "DATE",    length: 4 }
                    ]
                }
            ]
        }
    };

    describe('ACU-PAT-014 — INDEX_LOAD_KEY type mismatch', () => {
        test('does NOT fire when all load types match segment types', () => {
            const doc = makeDocument([
                'INDEX_OPEN_STANDARD("TEST", 0, hCursor)',
                'INDEX_LOAD_KEY_STRING(hCursor, "X", 2)',   // pos 0 = ZSTRING → _STRING ok
                'INDEX_LOAD_KEY_SHORT(hCursor, 0)',          // pos 1 = SHORT → _SHORT ok
                'INDEX_LOAD_KEY_DATE(hCursor, MIN_DATE)',    // pos 2 = DATE  → _DATE ok
                'INDEX_READ_BY_KEY(hCursor)',
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateIndexUsage(doc, alwaysWarn, { tableStructures: testTableStructures });
            expect(messageIds(diags)).not.toContain('ACU-PAT-014');
        });

        test('fires when LONG is used where ZSTRING expected', () => {
            const doc = makeDocument([
                'INDEX_OPEN_STANDARD("TEST", 0, hCursor)',
                'INDEX_LOAD_KEY_LONG(hCursor, 99)',   // pos 0 = ZSTRING → mismatch
                'INDEX_READ_BY_KEY(hCursor)',
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateIndexUsage(doc, alwaysWarn, { tableStructures: testTableStructures });
            expect(messageIds(diags)).toContain('ACU-PAT-014');
        });

        test('fires on the correct line when mismatch is at position 1', () => {
            const doc = makeDocument([
                'INDEX_OPEN_STANDARD("TEST", 0, hCursor)',
                'INDEX_LOAD_KEY_STRING(hCursor, "X", 2)', // pos 0 = ZSTRING → ok
                'INDEX_LOAD_KEY_DATE(hCursor, MIN_DATE)', // pos 1 = SHORT → mismatch (line 2, 0-based)
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateIndexUsage(doc, alwaysWarn, { tableStructures: testTableStructures });
            const pat014 = diags.filter((d) => d.data.messageId === 'ACU-PAT-014');
            expect(pat014).toHaveLength(1);
            expect(pat014[0].range.start.line).toBe(2);
        });

        test('does NOT fire when tableStructures option is absent', () => {
            const doc = makeDocument([
                'INDEX_OPEN_STANDARD("TEST", 0, hCursor)',
                'INDEX_LOAD_KEY_LONG(hCursor, 99)',   // would be a mismatch if tested
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateIndexUsage(doc, alwaysWarn, {});
            expect(messageIds(diags)).not.toContain('ACU-PAT-014');
        });
    });

    describe('ACU-PAT-015 — INDEX_LOAD_KEY count exceeded', () => {
        test('does NOT fire when load count exactly equals segment count', () => {
            const doc = makeDocument([
                'INDEX_OPEN_STANDARD("TEST", 0, hCursor)',
                'INDEX_LOAD_KEY_STRING(hCursor, "X", 2)',
                'INDEX_LOAD_KEY_SHORT(hCursor, 0)',
                'INDEX_LOAD_KEY_DATE(hCursor, MIN_DATE)',
                'INDEX_READ_BY_KEY(hCursor)',
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateIndexUsage(doc, alwaysWarn, { tableStructures: testTableStructures });
            expect(messageIds(diags)).not.toContain('ACU-PAT-015');
        });

        test('fires on the 4th load when index only has 3 segments', () => {
            const doc = makeDocument([
                'INDEX_OPEN_STANDARD("TEST", 0, hCursor)',
                'INDEX_LOAD_KEY_STRING(hCursor, "X", 2)',
                'INDEX_LOAD_KEY_SHORT(hCursor, 0)',
                'INDEX_LOAD_KEY_DATE(hCursor, MIN_DATE)',
                'INDEX_LOAD_KEY_STRING(hCursor, "Y", 1)',  // exceeds count (line 4, 0-based)
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateIndexUsage(doc, alwaysWarn, { tableStructures: testTableStructures });
            const pat015 = diags.filter((d) => d.data.messageId === 'ACU-PAT-015');
            expect(pat015).toHaveLength(1);
            expect(pat015[0].range.start.line).toBe(4);
        });

        test('does NOT fire when tableStructures option is absent', () => {
            const doc = makeDocument([
                'INDEX_OPEN_STANDARD("TEST", 0, hCursor)',
                'INDEX_LOAD_KEY_STRING(hCursor, "X", 2)',
                'INDEX_LOAD_KEY_SHORT(hCursor, 0)',
                'INDEX_LOAD_KEY_DATE(hCursor, MIN_DATE)',
                'INDEX_LOAD_KEY_STRING(hCursor, "Y", 1)',  // would exceed if tested
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateIndexUsage(doc, alwaysWarn, {});
            expect(messageIds(diags)).not.toContain('ACU-PAT-015');
        });
    });

    describe('ACU-PAT-014/015 — INDEX_CLEAR_KEY resets load position', () => {
        test('after INDEX_CLEAR_KEY the load position resets so no PAT-015 fires', () => {
            const doc = makeDocument([
                'INDEX_OPEN_STANDARD("TEST", 0, hCursor)',
                'INDEX_LOAD_KEY_STRING(hCursor, "X", 2)', // pos 0
                'INDEX_LOAD_KEY_SHORT(hCursor, 0)',        // pos 1
                'INDEX_LOAD_KEY_DATE(hCursor, MIN_DATE)',  // pos 2 — fills index
                'INDEX_CLEAR_KEY(hCursor)',                 // reset: pos back to 0
                'INDEX_LOAD_KEY_STRING(hCursor, "Y", 2)', // pos 0 again — ok
                'INDEX_READ_BY_KEY(hCursor)',
                'INDEX_CLOSE(hCursor)'
            ]);
            const diags = validateIndexUsage(doc, alwaysWarn, { tableStructures: testTableStructures });
            expect(messageIds(diags)).not.toContain('ACU-PAT-015');
            expect(messageIds(diags)).not.toContain('ACU-PAT-014');
        });
    });
});
