/**
 * Unit tests for argumentTypeValidator — Phase 4 of the expression tree plan.
 *
 * ACU-FNC-009 fires when an inferred argument type is incompatible with the
 * declared parameter type for a built-in function.
 */

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

// Build the variableTypes Map in the same format as collectDiagnostics uses.
function varTypes(entries) {
    return new Map(entries.map(([name, type]) => [`global:${name}`, { name, type }]));
}

// ─────────────────────────────────────────────────────────────────────────────

describe("validateArgumentTypes — happy path", () => {
    test("returns empty for correct literal arg types", () => {
        // DD(DATE) — DATE literal, param expects DATE
        const doc = makeDocument(["#HASH-OFF", 'SET sNum = DD(01/01/2026)']);
        const vars = varTypes([["sNum", "SHORT"]]);
        expect(validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars })).toHaveLength(0);
    });

    test("returns empty when argument type is unknown (variable not in map)", () => {
        // sNum not in vars — inferred type is null — should not false-positive
        const doc = makeDocument(["#HASH-OFF", "SET sResult = DD(sNum)"]);
        const vars = varTypes([["sResult", "SHORT"]]);
        expect(validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars })).toHaveLength(0);
    });

    test("returns empty for varArgs functions regardless of argument types", () => {
        // PRINT (if registered as varArgs) — any types allowed
        // Use STR which accepts all types
        const doc = makeDocument(["#HASH-OFF", 'SET zResult = STR(01/01/2026)']);
        const vars = varTypes([["zResult", "STRING"]]);
        expect(validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars })).toHaveLength(0);
    });

    test("returns empty when severity is off", () => {
        const doc = makeDocument(["#HASH-OFF", "SET sNum = DD(1)"]);
        const vars = varTypes([["sNum", "SHORT"]]);
        expect(validateArgumentTypes(doc, alwaysOff, { variableTypes: vars })).toHaveLength(0);
    });

    test("returns empty for numeric widening — LONG arg to SHORT param", () => {
        // REPEAT expects [STRING, SHORT] — passing LONG for arg 2 is numerically compatible
        const doc = makeDocument(["#HASH-OFF", 'VAR lCount : LONG', "SET zResult = REPEAT(\"test\", lCount)"]);
        const vars = varTypes([["lCount", "LONG"], ["zResult", "STRING"]]);
        expect(validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars })).toHaveLength(0);
    });

    test("returns empty for correct variable arg — DATE var to DD(DATE)", () => {
        const doc = makeDocument(["#HASH-OFF", "VAR dToday : DATE", "SET sNum = DD(dToday)"]);
        const vars = varTypes([["dToday", "DATE"], ["sNum", "SHORT"]]);
        expect(validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars })).toHaveLength(0);
    });
});

// ─────────────────────────────────────────────────────────────────────────────

describe("validateArgumentTypes — ACU-FNC-009", () => {
    test("flags SHORT literal where DATE expected (DD arg 1)", () => {
        const doc = makeDocument(["#HASH-OFF", "SET sNum = DD(1)"]);
        const vars = varTypes([["sNum", "SHORT"]]);
        expect(messageIds(validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars }))).toContain("ACU-FNC-009");
    });

    test("flags STRING literal where DATE expected (DD arg 1)", () => {
        const doc = makeDocument(["#HASH-OFF", 'SET sNum = DD("hello")']);
        const vars = varTypes([["sNum", "SHORT"]]);
        expect(messageIds(validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars }))).toContain("ACU-FNC-009");
    });

    test("flags SHORT literal where STRING expected (LENGTH arg 1)", () => {
        const doc = makeDocument(["#HASH-OFF", "SET sNum = LENGTH(42)"]);
        const vars = varTypes([["sNum", "SHORT"]]);
        expect(messageIds(validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars }))).toContain("ACU-FNC-009");
    });

    test("flags STRING literal where SHORT expected (REPEAT arg 2)", () => {
        const doc = makeDocument(["#HASH-OFF", 'SET zResult = REPEAT("test", "x")']);
        const vars = varTypes([["zResult", "STRING"]]);
        expect(messageIds(validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars }))).toContain("ACU-FNC-009");
    });

    test("flags SHORT variable where DATE expected (DDADD arg 1)", () => {
        const doc = makeDocument(["#HASH-OFF", "VAR sNum : SHORT", "SET dResult = DDADD(sNum, 1)"]);
        const vars = varTypes([["sNum", "SHORT"], ["dResult", "DATE"]]);
        expect(messageIds(validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars }))).toContain("ACU-FNC-009");
    });

    test("diagnostic identifies correct argument number (n=2 for REPEAT arg 2)", () => {
        const doc = makeDocument(["#HASH-OFF", 'SET zResult = REPEAT("test", "x")']);
        const vars = varTypes([["zResult", "STRING"]]);
        const diags = validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars });
        expect(diags[0].message).toMatch(/Argument 2/);
    });

    test("diagnostic identifies the function name", () => {
        const doc = makeDocument(["#HASH-OFF", 'SET sNum = DD("hello")']);
        const vars = varTypes([["sNum", "SHORT"]]);
        const diags = validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars });
        expect(diags[0].message).toMatch(/DD/);
    });

    test("diagnostic message includes expected and actual type", () => {
        const doc = makeDocument(["#HASH-OFF", 'SET sNum = DD("hello")']);
        const vars = varTypes([["sNum", "SHORT"]]);
        const diags = validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars });
        expect(diags[0].message).toMatch(/DATE/);
        expect(diags[0].message).toMatch(/STRING/);
    });

    test("flags only the mismatched argument — correct arg 1, wrong arg 2 in REPEAT", () => {
        const doc = makeDocument(["#HASH-OFF", 'SET zResult = REPEAT("test", "x")']);
        const vars = varTypes([["zResult", "STRING"]]);
        const diags = validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars });
        // Only one diagnostic for arg 2 — arg 1 (STRING) is correct
        expect(diags).toHaveLength(1);
        expect(diags[0].message).toMatch(/Argument 2/);
    });
});

// ─────────────────────────────────────────────────────────────────────────────

describe("validateArgumentTypes — nested calls", () => {
    test("validates arguments of nested call inside outer call", () => {
        // GET_TOKEN(LENGTH(42), "-", 1) — LENGTH(42) is wrong (42 is SHORT, LENGTH expects STRING)
        const doc = makeDocument(["#HASH-OFF", 'SET zResult = GET_TOKEN(LENGTH(42), "-", 1)']);
        const vars = varTypes([["zResult", "STRING"]]);
        expect(messageIds(validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars }))).toContain("ACU-FNC-009");
    });

    test("validates outer call independently — flags ACU-FNC-009 when outer arg type is incorrect", () => {
        // GET_TOKEN(LENGTH("test"), "-", 1) — LENGTH returns LONG, GET_TOKEN expects STRING for arg 1
        // LONG is not STRING, so this should flag ACU-FNC-009 for GET_TOKEN arg 1
        const doc = makeDocument(["#HASH-OFF", 'SET zResult = GET_TOKEN(LENGTH("test"), "-", 1)']);
        const vars = varTypes([["zResult", "STRING"]]);
        expect(messageIds(validateArgumentTypes(doc, alwaysWarn, { variableTypes: vars }))).toContain("ACU-FNC-009");
    });
});
