/**
 * Unit tests for functionValidator.
 * Focused on the parts testable without full VS Code context:
 * - duplicate function declarations
 * - standalone call return value unused
 * Tests rely on pre-computed options to avoid include resolution.
 */

import { validateFunctionCalls } from "../../../core/src/validators/functionValidator.js";
import { makeDocument, alwaysWarn, alwaysOff, messageIds } from "./helpers.js";
import { buildDocumentAst } from "../../../core/src/ast/documentAst.js";
import { walkStatements } from "../../../core/src/ast/astHelpers.js";

describe('validateFunctionCalls', () => {
    describe('happy path — no diagnostics', () => {
        test('returns empty array for document with no function calls', () => {
            const doc = makeDocument(['SET x = 1', 'OUTPUT "done"']);
            const diagnostics = validateFunctionCalls(doc, alwaysWarn, {
                userDefinedFunctionCatalog: {},
                functionScopes: [],
                executionClassificationMap: [
                    { isOutput: false, isExecutable: true },
                    { isOutput: true, isExecutable: false }
                ]
            });
            expect(diagnostics).toEqual([]);
        });

        test('does not throw on empty document', () => {
            const doc = makeDocument(['']);
            expect(() => validateFunctionCalls(doc, alwaysWarn, {
                userDefinedFunctionCatalog: {},
                functionScopes: [],
                executionClassificationMap: [{ isOutput: false, isExecutable: true }]
            })).not.toThrow();
        });
    });

    describe('ACU-FNC-003 — duplicate function declaration', () => {
        test('flags when the same function is declared twice', () => {
            const doc = makeDocument([
                'FUNCTION MyFunc() : STRING',
                '    RETURN "a"',
                'ENDFUNCTION',
                'FUNCTION MyFunc() : STRING',
                '    RETURN "b"',
                'ENDFUNCTION'
            ]);
            const diags = validateFunctionCalls(doc, alwaysWarn, {
                userDefinedFunctionCatalog: {},
                functionScopes: [],
                functionDuplicates: [
                    { functionName: 'MYFUNC', lineIndex: 0, isDuplicate: false },
                    { functionName: 'MYFUNC', lineIndex: 3, isDuplicate: true }
                ],
                executionClassificationMap: Array.from({ length: 6 }, () => ({ isOutput: false, isExecutable: true }))
            });
            expect(messageIds(diags)).toContain('ACU-FNC-003');
        });
    });

    describe('severity', () => {
        test('produces no diagnostics when severity is off', () => {
            const doc = makeDocument([
                'FUNCTION MyFunc() : STRING',
                '    RETURN "a"',
                'ENDFUNCTION',
                'FUNCTION MyFunc() : STRING',
                '    RETURN "b"',
                'ENDFUNCTION'
            ]);
            const diags = validateFunctionCalls(doc, alwaysOff, {
                userDefinedFunctionCatalog: {},
                functionScopes: [],
                executionClassificationMap: Array.from({ length: 6 }, () => ({ isOutput: false, isExecutable: true }))
            });
            expect(diags).toHaveLength(0);
        });
    });

    describe('ACU-FNC-001 — undefined function', () => {
        test('flags call to function not defined in document or includes', () => {
            const doc = makeDocument(['SET x = UnknownFunc()']);
            const diags = validateFunctionCalls(doc, alwaysWarn, {
                userDefinedFunctionCatalog: {},
                functionScopes: [],
                executionClassificationMap: [{ isOutput: false, isExecutable: true }],
                functionValidationTargetsByLine: {
                    0: [{ snippet: 'UnknownFunc()', position: 8 }]
                }
            });
            expect(messageIds(diags)).toContain('ACU-FNC-001');
        });
    });

    describe('ACU-FNC-002 — function called before its definition', () => {
        test('flags call that appears before the FUNCTION declaration line', () => {
            const doc = makeDocument([
                'SET x = MyFunc()',
                'FUNCTION MyFunc() : STRING',
                '    RETURN "a"',
                'ENDFUNCTION'
            ]);
            const catalog = {
                MYFUNC: { lineIndex: 1, declaredName: 'MyFunc', params: [], returnType: 'STRING' }
            };
            const diags = validateFunctionCalls(doc, alwaysWarn, {
                userDefinedFunctionCatalog: catalog,
                functionScopes: [],
                executionClassificationMap: Array.from({ length: 4 }, () => ({ isOutput: false, isExecutable: true })),
                functionValidationTargetsByLine: {
                    0: [{ snippet: 'MyFunc()', position: 8 }]
                }
            });
            expect(messageIds(diags)).toContain('ACU-FNC-002');
        });
    });

    describe('ACU-FNC-006 — wrong argument count', () => {
        test('flags call with fewer arguments than the function declares', () => {
            const doc = makeDocument([
                'FUNCTION MyFunc(pA : STRING, pB : STRING) : STRING',
                '    RETURN pA',
                'ENDFUNCTION',
                'SET x = MyFunc("one")'
            ]);
            const catalog = {
                MYFUNC: {
                    lineIndex: 0,
                    declaredName: 'MyFunc',
                    params: [{ name: 'pA', type: 'STRING' }, { name: 'pB', type: 'STRING' }],
                    returnType: 'STRING'
                }
            };
            const diags = validateFunctionCalls(doc, alwaysWarn, {
                userDefinedFunctionCatalog: catalog,
                functionScopes: [],
                executionClassificationMap: Array.from({ length: 4 }, () => ({ isOutput: false, isExecutable: true })),
                functionValidationTargetsByLine: {
                    3: [{ snippet: 'MyFunc("one")', position: 8 }]
                }
            });
            expect(messageIds(diags)).toContain('ACU-FNC-006');
        });
    });

    describe('ACU-FNC-008 — unused return value', () => {
        test('flags standalone function call whose return value is discarded', () => {
            // #HASH-OFF switches to HASH-OFF mode so bare lines are executable.
            const doc = makeDocument([
                '#HASH-OFF',
                'FUNCTION MyFunc() : STRING',
                '    RETURN "a"',
                'ENDFUNCTION',
                'MyFunc()'
            ]);
            const catalog = {
                MYFUNC: { lineIndex: 1, declaredName: 'MyFunc', params: [], returnType: 'STRING' }
            };
            const { ast } = buildDocumentAst(doc);
            const nodeTypeByLine = new Map();
            const stmtByLine = new Map();
            walkStatements(ast.statements, stmt => {
                if (typeof stmt.line === 'number') {
                    nodeTypeByLine.set(stmt.line, stmt.type);
                    stmtByLine.set(stmt.line, stmt);
                }
            });
            const diags = validateFunctionCalls(doc, alwaysWarn, {
                userDefinedFunctionCatalog: catalog,
                functionScopes: [],
                executionClassificationMap: Array.from({ length: 5 }, () => ({ isOutput: false, isExecutable: true })),
                nodeTypeByLine,
                stmtByLine
            });
            expect(messageIds(diags)).toContain('ACU-FNC-008');
        });
    });
});
