/**
 * Unit tests for parser utilities.
 */

import {
    parseVarDeclaration,
    parseAssignment,
    parseFunctionDeclaration,
    parseMacroDeclaration,
    parseInclude,
    parseRequire,
    stripTrailingComment,
    extractVariables
} from "../../core/src/parser.js";

describe('parser', () => {
    describe('parseVarDeclaration', () => {
        test('should parse simple variable declaration', () => {
            const result = parseVarDeclaration('var myVar : string');
            
            expect(result).not.toBeNull();
            expect(result.variables).toEqual(['myVar']);
            expect(result.type).toBe('STRING');
        });

        test('should parse multiple variables', () => {
            const result = parseVarDeclaration('var a, b, c : short');
            
            expect(result).not.toBeNull();
            expect(result.variables).toEqual(['a', 'b', 'c']);
            expect(result.type).toBe('SHORT');
        });

        test('should handle hash prefix', () => {
            const result = parseVarDeclaration('#var myVar : long');
            
            expect(result).not.toBeNull();
            expect(result.variables).toEqual(['myVar']);
            expect(result.type).toBe('LONG');
        });

        test('should parse BIGINT using standard VAR format', () => {
            const result = parseVarDeclaration('VAR lCounter : BIGINT');

            expect(result).not.toBeNull();
            expect(result.variables).toEqual(['lCounter']);
            expect(result.type).toBe('BIGINT');
        });

        test('should parse BIGINT using alternate type-first format', () => {
            const result = parseVarDeclaration('BIGINT lCounter');

            expect(result).not.toBeNull();
            expect(result.variables).toEqual(['lCounter']);
            expect(result.type).toBe('BIGINT');
        });

        test('should parse multiple BIGINT variables using alternate format', () => {
            const result = parseVarDeclaration('BIGINT lA, lB');

            expect(result).not.toBeNull();
            expect(result.variables).toEqual(['lA', 'lB']);
            expect(result.type).toBe('BIGINT');
        });

        test('should return null for non-var lines', () => {
            const result = parseVarDeclaration('set x = 5');

            expect(result).toBeNull();
        });
    });

    describe('parseAssignment', () => {
        test('should parse simple assignment', () => {
            const result = parseAssignment('x = 5');
            
            expect(result).not.toBeNull();
            expect(result.target).toBe('x');
            expect(result.expression).toBe('5');
        });

        test('should parse SET statement', () => {
            const result = parseAssignment('SET myVar = 100');
            
            expect(result).not.toBeNull();
            expect(result.target).toBe('myVar');
            expect(result.expression).toBe('100');
        });

        test('should parse hash-prefixed SET statement', () => {
            const result = parseAssignment('#SET myVar = 100');

            expect(result).not.toBeNull();
            expect(result.target).toBe('myVar');
            expect(result.expression).toBe('100');
        });

        test('should parse hash-prefixed bare assignment', () => {
            const result = parseAssignment('#myVar = otherValue');

            expect(result).not.toBeNull();
            expect(result.target).toBe('myVar');
            expect(result.expression).toBe('otherValue');
        });

        test('should handle complex expressions', () => {
            const result = parseAssignment('result = a + b * c');
            
            expect(result).not.toBeNull();
            expect(result.target).toBe('result');
            expect(result.expression).toBe('a + b * c');
        });

        test('should return null for non-assignment lines', () => {
            const result = parseAssignment('var x : string');
            
            expect(result).toBeNull();
        });
    });

    describe('parseFunctionDeclaration', () => {
        test('should parse function with no parameters', () => {
            const result = parseFunctionDeclaration('function myFunc():string');
            
            expect(result).not.toBeNull();
            expect(result.name).toBe('myFunc');
            expect(result.params).toEqual([]);
        });

        test('should parse function with parameters', () => {
            const result = parseFunctionDeclaration('function calc(a:short, b:short):long');
            
            expect(result).not.toBeNull();
            expect(result.name).toBe('calc');
            expect(result.params).toHaveLength(2);
            expect(result.params[0]).toEqual({ name: 'a', type: 'SHORT' });
            expect(result.params[1]).toEqual({ name: 'b', type: 'SHORT' });
        });

        test('should handle hash prefix', () => {
            const result = parseFunctionDeclaration('#FUNCTION getData():string');
            
            expect(result).not.toBeNull();
            expect(result.name).toBe('getData');
        });
    });

    describe('stripTrailingComment', () => {
        test('should remove trailing comments', () => {
            const result = stripTrailingComment('var x : string // this is a comment');
            
            expect(result).toBe('var x : string ');
        });

        test('should not remove // inside strings', () => {
            const result = stripTrailingComment('set x = "http://example.com"');
            
            expect(result).toBe('set x = "http://example.com"');
        });

        test('should handle mixed quotes', () => {
            const result = stripTrailingComment('set x = "it\'s ok" // comment');
            
            expect(result).toBe('set x = "it\'s ok" ');
        });
    });

    describe('extractVariables', () => {
        test('should extract variable names from expression', () => {
            const result = extractVariables('a + b * c');
            
            expect(result).toEqual(['a', 'b', 'c']);
        });

        test('should exclude function calls', () => {
            const result = extractVariables('myVar + myFunc(x)');
            
            expect(result).toEqual(['myVar', 'x']);
            expect(result).not.toContain('myFunc');
        });

        test('should exclude IF keyword', () => {
            const result = extractVariables('IF x');
            
            expect(result).toEqual(['x']);
            expect(result).not.toContain('IF');
        });
    });

    describe('parseMacroDeclaration', () => {
        test('should parse simple macro without parameters', () => {
            const result = parseMacroDeclaration('MACRO _reset E');
            
            expect(result).not.toBeNull();
            expect(result.name).toBe('_reset');
            expect(result.params).toEqual([]);
            expect(result.body).toBe('E');
        });

        test('should parse macro with single parameter', () => {
            const result = parseMacroDeclaration('MACRO _copies(?) &l?X');
            
            expect(result).not.toBeNull();
            expect(result.name).toBe('_copies');
            expect(result.params).toEqual(['?']);
            expect(result.body).toBe('&l?X');
        });

        test('should parse macro with multiple named parameters', () => {
            const result = parseMacroDeclaration('MACRO _centre_at(x,y,z) *c15D z *c6F');
            
            expect(result).not.toBeNull();
            expect(result.name).toBe('_centre_at');
            expect(result.params).toEqual(['x', 'y', 'z']);
            expect(result.body).toBe('*c15D z *c6F');
        });

        test('should parse macro with mixed parameter style', () => {
            const result = parseMacroDeclaration('MACRO _centre_this(?, p) AD4,p; FN15;SA: DT#; LO6;  LB?#');
            
            expect(result).not.toBeNull();
            expect(result.name).toBe('_centre_this');
            expect(result.params).toEqual(['?', 'p']);
            expect(result.body).toBe('AD4,p; FN15;SA: DT#; LO6;  LB?#');
        });

        test('should handle hash prefix', () => {
            const result = parseMacroDeclaration('#MACRO _font_7(?) (8U(s1p?v0s0b4101T');
            
            expect(result).not.toBeNull();
            expect(result.name).toBe('_font_7');
            expect(result.params).toEqual(['?']);
        });

        test('should handle trailing comment', () => {
            const result = parseMacroDeclaration('MACRO _test myBody // comment');
            
            expect(result).not.toBeNull();
            expect(result.name).toBe('_test');
            expect(result.body).toBe('myBody ');
        });

        test('should return null for non-macro lines', () => {
            const result = parseMacroDeclaration('VAR x : STRING');
            
            expect(result).toBeNull();
        });
    });

    describe('parseInclude', () => {
        test('parses bare INCLUDE filename', () => {
            expect(parseInclude('INCLUDE mylib.txt')).toEqual({ filename: 'mylib.txt' });
        });

        test('parses double-quoted INCLUDE filename', () => {
            expect(parseInclude('INCLUDE "my lib.txt"')).toEqual({ filename: 'my lib.txt' });
        });

        test('parses single-quoted INCLUDE filename', () => {
            expect(parseInclude("INCLUDE 'mylib.txt'")).toEqual({ filename: 'mylib.txt' });
        });

        test('parses INCLUDEONCE', () => {
            expect(parseInclude('INCLUDEONCE utils.txt')).toEqual({ filename: 'utils.txt' });
        });

        test('parses INCLUDETEST', () => {
            expect(parseInclude('INCLUDETEST mock.txt')).toEqual({ filename: 'mock.txt' });
        });

        test('parses with hash prefix', () => {
            expect(parseInclude('#INCLUDE mylib.txt')).toEqual({ filename: 'mylib.txt' });
        });

        test('returns null for non-INCLUDE lines', () => {
            expect(parseInclude('SET x = 1')).toBeNull();
        });

        test('returns null for empty line', () => {
            expect(parseInclude('')).toBeNull();
        });

        test('strips trailing comment and still parses filename', () => {
            expect(parseInclude('INCLUDE mylib.txt // load library')).toEqual({ filename: 'mylib.txt' });
        });
    });

    describe('parseRequire', () => {
        test('parses bare REQUIRE filename', () => {
            expect(parseRequire('REQUIRE mylib.txt')).toEqual({ filename: 'mylib.txt' });
        });

        test('parses double-quoted REQUIRE filename', () => {
            expect(parseRequire('REQUIRE "mylib.txt"')).toEqual({ filename: 'mylib.txt' });
        });

        test('parses single-quoted REQUIRE filename', () => {
            expect(parseRequire("REQUIRE 'mylib.txt'")).toEqual({ filename: 'mylib.txt' });
        });

        test('returns null for non-REQUIRE lines', () => {
            expect(parseRequire('INCLUDE mylib.txt')).toBeNull();
        });
    });
});
