/**
 * Unit tests for namingValidator.
 */

import { validateNamingConventions } from "../../../core/src/validators/namingValidator.js";
import { makeDocument, alwaysWarn, alwaysOff, messageIds } from "./helpers.js";

// Build a variableTypes map with scope metadata.
// Keys use the same composite format as collectDiagnostics: "${scopeId}:${name}".
// Naming validator only iterates entries and reads varInfo.name, so the exact
// scope id doesn't affect naming validation — "global" is used for globals and
// "0" (dummy scope start) for function-scoped entries.
function globalVar(name, type, line = 0) {
    return [`global:${name}`, { name, type, line, isFunctionParameter: false, isFunctionScoped: false }];
}
function paramVar(name, type, line = 0) {
    return [`0:${name}`, { name, type, line, isFunctionParameter: true, isFunctionScoped: true }];
}
function localVar(name, type, line = 0) {
    return [`0:${name}`, { name, type, line, isFunctionParameter: false, isFunctionScoped: true }];
}

function makeVarTypes(entries) {
    return new Map(entries);
}

// Document with one line per variable declaration (line text doesn't matter much for naming validator)
function docForVars(count) {
    return makeDocument(Array.from({ length: count }, (_, i) => `VAR line${i} : STRING`));
}

describe('validateNamingConventions', () => {
    describe('happy path — no diagnostics', () => {
        test('returns empty array when global STRING variable has correct gz prefix', () => {
            const doc = docForVars(1);
            const vars = makeVarTypes([globalVar('gzName', 'STRING')]);
            expect(validateNamingConventions(doc, alwaysWarn, { variableTypes: vars })).toHaveLength(0);
        });

        test('returns empty array when global variable has single type prefix', () => {
            const doc = docForVars(1);
            const vars = makeVarTypes([globalVar('zName', 'STRING')]);
            expect(validateNamingConventions(doc, alwaysWarn, { variableTypes: vars })).toHaveLength(0);
        });

        test('returns empty array when parameter has correct ps prefix', () => {
            const doc = docForVars(1);
            const vars = makeVarTypes([paramVar('psCount', 'SHORT')]);
            expect(validateNamingConventions(doc, alwaysWarn, { variableTypes: vars })).toHaveLength(0);
        });

        test('returns empty array when variable name has three or more lowercase prefix chars', () => {
            const doc = docForVars(1);
            // Three+ leading lowercase chars — validator skips (only checks prefix length 1 or 2)
            const vars = makeVarTypes([globalVar('myxVariable', 'STRING')]);
            expect(validateNamingConventions(doc, alwaysWarn, { variableTypes: vars })).toHaveLength(0);
        });

        test('returns empty array when variable name starts with uppercase (no prefix)', () => {
            const doc = docForVars(1);
            // No lowercase prefix at all — validator skips
            const vars = makeVarTypes([globalVar('MyVariable', 'STRING')]);
            expect(validateNamingConventions(doc, alwaysWarn, { variableTypes: vars })).toHaveLength(0);
        });
    });

    describe('ACU-NAM-001 — h prefix (database field)', () => {
        test('flags variable with h prefix', () => {
            const doc = docForVars(1);
            const vars = makeVarTypes([globalVar('hField', 'STRING')]);
            const diags = validateNamingConventions(doc, alwaysWarn, { variableTypes: vars });
            expect(messageIds(diags)).toContain('ACU-NAM-001');
        });
    });

    describe('ACU-NAM-002 — scope prefix mismatch', () => {
        test('flags global variable declared with parameter scope prefix', () => {
            const doc = docForVars(1);
            // pzName has 'p' scope prefix but is global
            const vars = makeVarTypes([globalVar('pzName', 'STRING')]);
            const diags = validateNamingConventions(doc, alwaysWarn, { variableTypes: vars });
            expect(messageIds(diags)).toContain('ACU-NAM-002');
        });

        test('flags parameter variable declared with global scope prefix', () => {
            const doc = docForVars(1);
            // gzName has 'g' scope prefix but is a parameter
            const vars = makeVarTypes([paramVar('gzName', 'STRING')]);
            const diags = validateNamingConventions(doc, alwaysWarn, { variableTypes: vars });
            expect(messageIds(diags)).toContain('ACU-NAM-002');
        });
    });

    describe('ACU-NAM-003 — type prefix mismatch', () => {
        test('flags STRING variable with short type prefix', () => {
            const doc = docForVars(1);
            // zsName suggests SHORT (s) but is STRING
            // 'gs' has scope prefix 'g' (global) and type prefix 's' (SHORT), but var is STRING — mismatch.
            const vars = makeVarTypes([globalVar('gsName', 'STRING')]);
            const diags = validateNamingConventions(doc, alwaysWarn, { variableTypes: vars });
            expect(messageIds(diags)).toContain('ACU-NAM-003');
        });
    });

    describe('severity', () => {
        test('produces no diagnostics when severity is off', () => {
            const doc = docForVars(1);
            const vars = makeVarTypes([globalVar('hField', 'STRING')]);
            expect(validateNamingConventions(doc, alwaysOff, { variableTypes: vars })).toHaveLength(0);
        });
    });

    describe('namingConfig injection', () => {
        test('uses provided charToTypeMap instead of defaults — no false positive when custom char matches', () => {
            // Remap 'x' to STRING. A variable 'gxName' (global STRING) should produce no ACU-NAM-003.
            const namingConfig = { charToTypeMap: { x: "STRING" } };
            const doc = docForVars(1);
            const vars = makeVarTypes([globalVar('gxName', 'STRING')]);
            const diags = validateNamingConventions(doc, alwaysWarn, { variableTypes: vars, namingConfig });
            // 'x' → STRING is now valid; scope 'g' = global is correct → no diagnostics.
            expect(messageIds(diags)).not.toContain('ACU-NAM-003');
        });

        test('uses provided scopeToCharMap instead of defaults — accepts custom scope char', () => {
            // Remap global scope to 'w'. A variable 'wzName' should be valid for global STRING.
            const namingConfig = {
                scopeToCharMap: { global: "w" },
                charToTypeMap:  { z: "STRING" }
            };
            const doc = docForVars(1);
            const vars = makeVarTypes([globalVar('wzName', 'STRING')]);
            const diags = validateNamingConventions(doc, alwaysWarn, { variableTypes: vars, namingConfig });
            expect(diags).toHaveLength(0);
        });

        test('flags default-scope variable as wrong when scopeToCharMap is remapped', () => {
            // Remap global scope to 'w'. Old default 'g' prefix should now trigger ACU-NAM-002.
            const namingConfig = { scopeToCharMap: { global: "w" } };
            const doc = docForVars(1);
            const vars = makeVarTypes([globalVar('gzName', 'STRING')]);
            const diags = validateNamingConventions(doc, alwaysWarn, { variableTypes: vars, namingConfig });
            expect(messageIds(diags)).toContain('ACU-NAM-002');
        });

        test('falls back to defaults when namingConfig is omitted', () => {
            // Standard gzName for global STRING — should be clean with default config.
            const doc = docForVars(1);
            const vars = makeVarTypes([globalVar('gzName', 'STRING')]);
            expect(validateNamingConventions(doc, alwaysWarn, { variableTypes: vars })).toHaveLength(0);
        });

        test('falls back to defaults when namingConfig is null', () => {
            const doc = docForVars(1);
            const vars = makeVarTypes([globalVar('gzName', 'STRING')]);
            expect(validateNamingConventions(doc, alwaysWarn, { variableTypes: vars, namingConfig: null })).toHaveLength(0);
        });
    });
});
