import { describe, it, expect } from "vitest";
import { AgencyGenerator } from "./agencyGenerator.js";
import { parseAgency } from "../parser.js";
describe("AgencyGenerator - Function Parameter Type Hints", () => {
    describe("processFunctionDefinition", () => {
        const testCases = [
            {
                description: "single parameter with type hint",
                input: "def add(x: number) { x }",
                expectedOutput: "def add(x: number) {\nx\n}",
            },
            {
                description: "multiple parameters with type hints",
                input: "def add(x: number, y: number) { x }",
                expectedOutput: "def add(x: number, y: number) {\nx\n}",
            },
            {
                description: "mixed typed and untyped parameters",
                input: "def mixed(x: number, y) { x }",
                expectedOutput: "def mixed(x: number, y) {\nx\n}",
            },
            {
                description: "array type hint",
                input: "def process(items: number[]) { items }",
                expectedOutput: "def process(items: number[]) {\nitems\n}",
            },
            {
                description: "union type hint",
                input: "def flexible(value: string | number) { value }",
                expectedOutput: "def flexible(value: string | number) {\nvalue\n}",
            },
            {
                description: "type hint with docstring",
                input: 'def add(x: number, y: number) {\n  """Adds two numbers"""\n  x\n}',
                expectedOutput: 'def add(x: number, y: number) {\n  """\n  Adds two numbers\n  """\nx\n}',
            },
            {
                description: "multiple array types",
                input: "def multi(arr: string[], count: number) { arr }",
                expectedOutput: "def multi(arr: string[], count: number) {\narr\n}",
            },
            {
                description: "nested array type",
                input: "def nested(matrix: number[][]) { matrix }",
                expectedOutput: "def nested(matrix: number[][]) {\nmatrix\n}",
            },
            {
                description: "custom type name",
                input: "def handle(data: CustomType) { data }",
                expectedOutput: "def handle(data: CustomType) {\ndata\n}",
            },
            {
                description: "untyped parameters (backward compatibility)",
                input: "def old(x, y) { x }",
                expectedOutput: "def old(x, y) {\nx\n}",
            },
        ];
        testCases.forEach(({ description, input, expectedOutput }) => {
            it(`should correctly generate ${description}`, () => {
                const parseResult = parseAgency(input, {}, false);
                expect(parseResult.success).toBe(true);
                if (!parseResult.success)
                    return;
                const generator = new AgencyGenerator();
                const result = generator.generate(parseResult.result);
                // Normalize whitespace for comparison
                const normalizedOutput = result.output.trim();
                const normalizedExpected = expectedOutput.trim();
                expect(normalizedOutput).toBe(normalizedExpected);
            });
        });
    });
    describe("Type preservation", () => {
        it("should preserve primitive types", () => {
            const input = "def test(n: number, s: string, b: boolean) { n }";
            const parseResult = parseAgency(input, {}, false);
            expect(parseResult.success).toBe(true);
            if (!parseResult.success)
                return;
            const generator = new AgencyGenerator();
            const result = generator.generate(parseResult.result);
            expect(result.output).toContain("n: number");
            expect(result.output).toContain("s: string");
            expect(result.output).toContain("b: boolean");
        });
        it("should preserve array types", () => {
            const input = "def test(nums: number[], strs: string[]) { nums }";
            const parseResult = parseAgency(input, {}, false);
            expect(parseResult.success).toBe(true);
            if (!parseResult.success)
                return;
            const generator = new AgencyGenerator();
            const result = generator.generate(parseResult.result);
            expect(result.output).toContain("nums: number[]");
            expect(result.output).toContain("strs: string[]");
        });
        it("should preserve union types", () => {
            const input = "def test(val: string | number | boolean) { val }";
            const parseResult = parseAgency(input, {}, false);
            expect(parseResult.success).toBe(true);
            if (!parseResult.success)
                return;
            const generator = new AgencyGenerator();
            const result = generator.generate(parseResult.result);
            expect(result.output).toContain("val: string | number | boolean");
        });
        it("should preserve nested array types", () => {
            const input = "def test(matrix: number[][]) { matrix }";
            const parseResult = parseAgency(input, {}, false);
            expect(parseResult.success).toBe(true);
            if (!parseResult.success)
                return;
            const generator = new AgencyGenerator();
            const result = generator.generate(parseResult.result);
            expect(result.output).toContain("matrix: number[][]");
        });
    });
    describe("Mixed typed and untyped parameters", () => {
        it("should handle first parameter typed, second untyped", () => {
            const input = "def test(x: number, y) { x }";
            const parseResult = parseAgency(input, {}, false);
            expect(parseResult.success).toBe(true);
            if (!parseResult.success)
                return;
            const generator = new AgencyGenerator();
            const result = generator.generate(parseResult.result);
            expect(result.output).toContain("x: number");
            expect(result.output).toContain(", y)");
            expect(result.output).not.toContain("y:");
        });
        it("should handle first parameter untyped, second typed", () => {
            const input = "def test(x, y: string) { x }";
            const parseResult = parseAgency(input, {}, false);
            expect(parseResult.success).toBe(true);
            if (!parseResult.success)
                return;
            const generator = new AgencyGenerator();
            const result = generator.generate(parseResult.result);
            expect(result.output).toContain("y: string");
            expect(result.output).toMatch(/test\(x,/);
            expect(result.output).not.toContain("x:");
        });
        it("should handle alternating typed and untyped parameters", () => {
            const input = "def test(a, b: number, c, d: string) { a }";
            const parseResult = parseAgency(input, {}, false);
            expect(parseResult.success).toBe(true);
            if (!parseResult.success)
                return;
            const generator = new AgencyGenerator();
            const result = generator.generate(parseResult.result);
            expect(result.output).toContain("b: number");
            expect(result.output).toContain("d: string");
            expect(result.output).not.toContain("a:");
            expect(result.output).not.toContain("c:");
        });
    });
});
describe("AgencyGenerator - new expressions", () => {
    function formatAgency(input) {
        const parseResult = parseAgency(input, {}, false);
        expect(parseResult.success).toBe(true);
        if (!parseResult.success)
            return "";
        const generator = new AgencyGenerator();
        return generator.generate(parseResult.result).output.trim();
    }
    it("should format `new Foo(args)` expressions", () => {
        const input = `node main() {
  let c = new Counter(0)
}`;
        const output = formatAgency(input);
        expect(output).toContain("new Counter(0)");
    });
    it("rejects `class` definitions with an actionable error", () => {
        const input = `class User {
  name: string
}`;
        const parseResult = parseAgency(input, {}, false);
        expect(parseResult.success).toBe(false);
        // Lock in the migration hint so a future regression that drops back to
        // a generic "expected ... 'class' ..." error fails this test.
        if (!parseResult.success) {
            expect(parseResult.message).toMatch(/no longer supported/i);
            expect(parseResult.message).toMatch(/new Foo/);
        }
    });
    // Whitespace tolerance — the reservedClassParser probe must fire on
    // common formatting variants so migrators always see the actionable
    // error, not a downstream "unexpected token" surprise.
    for (const sep of ["  ", "\t", " \t "]) {
        it(`rejects \`class${JSON.stringify(sep)}Foo\` (whitespace variant)`, () => {
            const parseResult = parseAgency(`class${sep}User {\n  name: string\n}`, {}, false);
            expect(parseResult.success).toBe(false);
            if (!parseResult.success) {
                expect(parseResult.message).toMatch(/no longer supported/i);
            }
        });
    }
});
describe("AgencyGenerator - Doc Comments", () => {
    function formatAgency(input) {
        const parseResult = parseAgency(input, {}, false);
        expect(parseResult.success).toBe(true);
        if (!parseResult.success)
            return "";
        const generator = new AgencyGenerator();
        return generator.generate(parseResult.result).output.trim();
    }
    it("should preserve /** syntax for doc comments", () => {
        const input = `/** This is a doc comment */\ndef foo() {\n  print("hi")\n}`;
        const output = formatAgency(input);
        expect(output).toContain("/** This is a doc comment */");
    });
    it("should preserve /* syntax for regular multi-line comments", () => {
        const input = `/* This is a regular comment */\ndef foo() {\n  print("hi")\n}`;
        const output = formatAgency(input);
        expect(output).toContain("/* This is a regular comment */");
        expect(output).not.toContain("/**");
    });
    it("should preserve multi-line doc comments", () => {
        const input = `/**\nThis is a multi-line\ndoc comment\n*/\ndef foo() {\n  print("hi")\n}`;
        const output = formatAgency(input);
        expect(output).toContain("/**");
        expect(output).toContain("This is a multi-line");
    });
    it("should round-trip node docstrings", () => {
        const input = `node main() {\n  """Main entry point."""\n  print("hello")\n}`;
        const output = formatAgency(input);
        expect(output).toContain('"""');
        expect(output).toContain("Main entry point.");
    });
});
describe("AgencyGenerator - bang (!) validated type annotations", () => {
    function formatAgency(input) {
        const parseResult = parseAgency(input, {}, false);
        expect(parseResult.success).toBe(true);
        if (!parseResult.success)
            return "";
        const generator = new AgencyGenerator();
        return generator.generate(parseResult.result).output.trim();
    }
    it("should preserve ! on assignment type annotations", () => {
        const input = `node main() {\n  const x: number! = 42\n}`;
        const output = formatAgency(input);
        expect(output).toContain("const x: number! = 42");
    });
    it("should preserve ! on function parameter types", () => {
        const input = `def process(data: number!) {\n  print(data)\n}`;
        const output = formatAgency(input);
        expect(output).toContain("data: number!");
    });
    it("should preserve ! on function return types", () => {
        const input = `def process(x: number): string! {\n  return x\n}`;
        const output = formatAgency(input);
        expect(output).toContain("): string!");
    });
    it("should preserve ! on node return types", () => {
        const input = `node main(): number! {\n  return 42\n}`;
        const output = formatAgency(input);
        expect(output).toContain("(): number!");
    });
    it("should not add ! when not present", () => {
        const input = `node main() {\n  const x: number = 42\n}`;
        const output = formatAgency(input);
        expect(output).toContain("const x: number = 42");
        expect(output).not.toContain("number!");
    });
});
describe("AgencyGenerator - Result type formatting", () => {
    function formatAgency(input) {
        const parseResult = parseAgency(input, {}, false);
        expect(parseResult.success).toBe(true);
        if (!parseResult.success)
            return "";
        const generator = new AgencyGenerator();
        return generator.generate(parseResult.result).output.trim();
    }
    it("should format Result<Foo> with single type param", () => {
        const input = `def check(): Result<number> {\n  return success(42)\n}`;
        const output = formatAgency(input);
        expect(output).toContain("Result<number>");
        expect(output).not.toContain("Result<number,");
    });
    it("should format bare Result without type params", () => {
        const input = `def check(): Result {\n  return success(42)\n}`;
        const output = formatAgency(input);
        expect(output).toContain(": Result");
        expect(output).not.toContain("Result<");
    });
    it("should format Result<Foo, Bar> with non-default failure type", () => {
        const input = `def check(): Result<number, number> {\n  return success(42)\n}`;
        const output = formatAgency(input);
        expect(output).toContain("Result<number, number>");
    });
    it("should normalize Result<Foo, string> to Result<Foo>", () => {
        const input = `def check(): Result<number, string> {\n  return success(42)\n}`;
        const output = formatAgency(input);
        expect(output).toContain("Result<number>");
        expect(output).not.toContain("Result<number, string>");
    });
});
describe("AgencyGenerator - object type formatting", () => {
    function formatAgency(input) {
        const parseResult = parseAgency(input, {}, false);
        expect(parseResult.success).toBe(true);
        if (!parseResult.success)
            return "";
        const generator = new AgencyGenerator();
        return generator.generate(parseResult.result).output.trim();
    }
    it("should format object type as 'object' not 'Record<string, any>'", () => {
        const input = `def process(data: object) {\n  print(data)\n}`;
        const output = formatAgency(input);
        expect(output).toContain("data: object");
        expect(output).not.toContain("Record<string, any>");
    });
    it("should format object type in type aliases", () => {
        const input = `type NextStep = { type: "showPolicy"; policy: object }`;
        const output = formatAgency(input);
        expect(output).toContain("policy: object");
        expect(output).not.toContain("Record<string, any>");
    });
    it("should format object type in union type aliases", () => {
        const input = `type NextStep =\n  | { type: "showPolicy"; policy: object }\n  | { type: "writePolicy"; policy: object }`;
        const output = formatAgency(input);
        expect(output).toContain("policy: object");
        expect(output).not.toContain("Record<string, any>");
    });
    it("should format object type in variable declarations", () => {
        const input = `node main() {\n  const x: object = {}\n}`;
        const output = formatAgency(input);
        expect(output).toContain("const x: object");
        expect(output).not.toContain("Record<string, any>");
    });
    it("should format object type in return types", () => {
        const input = `def getData(): object {\n  return {}\n}`;
        const output = formatAgency(input);
        expect(output).toContain("): object");
        expect(output).not.toContain("Record<string, any>");
    });
    it("should format object type in node return types", () => {
        const input = `node main(): object {\n  return {}\n}`;
        const output = formatAgency(input);
        expect(output).toContain("(): object");
        expect(output).not.toContain("Record<string, any>");
    });
    it("should format object[] array type", () => {
        const input = `def process(items: object[]) {\n  print(items)\n}`;
        const output = formatAgency(input);
        expect(output).toContain("items: object[]");
        expect(output).not.toContain("Record<string, any>");
    });
    it("should format object type in schema expressions", () => {
        const input = `node main() {\n  const s = schema(object)\n}`;
        const output = formatAgency(input);
        expect(output).toContain("schema(object)");
        expect(output).not.toContain("Record<string, any>");
    });
});
describe("AgencyGenerator - schema(Type) expressions", () => {
    function formatAgency(input) {
        const parseResult = parseAgency(input, {}, false);
        expect(parseResult.success).toBe(true);
        if (!parseResult.success)
            return "";
        const generator = new AgencyGenerator();
        return generator.generate(parseResult.result).output.trim();
    }
    it("should format schema(number)", () => {
        const input = `node main() {\n  const s = schema(number)\n}`;
        const output = formatAgency(input);
        expect(output).toContain("schema(number)");
    });
    it("should format schema(Result<number>)", () => {
        const input = `node main() {\n  const s = schema(Result<number>)\n}`;
        const output = formatAgency(input);
        expect(output).toContain("schema(Result<number>)");
    });
});
describe("AgencyGenerator - string interpolation with nested calls", () => {
    function formatAgency(input) {
        const parseResult = parseAgency(input, {}, false);
        expect(parseResult.success).toBe(true);
        if (!parseResult.success)
            return "";
        const generator = new AgencyGenerator();
        return generator.generate(parseResult.result).output.trim();
    }
    it("preserves inline block arguments inside a string interpolation", () => {
        // The interpolation expression is a function call whose second argument
        // is an inline block (`\k -> ...`). Previously the generator routed
        // through expressionToString, which knows nothing about block args,
        // so the block silently disappeared from the output.
        const input = `node main() {\n  let s = "x: " + map(arr, \\k -> k).join(",")\n}`;
        const output = formatAgency(input);
        expect(output).toContain("\\k -> k");
        expect(output).toContain('.join(",")');
    });
    it("preserves quoted string arguments inside a string interpolation", () => {
        // expressionToString rendered string literals without their quotes,
        // so `.join("")` collapsed to `.join()`.
        const input = `node main() {\n  let s = "x: " + arr.join("")\n}`;
        const output = formatAgency(input);
        expect(output).toContain('.join("")');
    });
});
describe("AgencyGenerator - Result Patterns", () => {
    // Format with lowering DISABLED so raw `resultPattern` nodes reach the
    // formatter (the formatter operates on the un-lowered AST).
    function formatAgency(input) {
        const parseResult = parseAgency(input, {}, false, false);
        expect(parseResult.success).toBe(true);
        if (!parseResult.success)
            return "";
        const generator = new AgencyGenerator();
        return generator.generate(parseResult.result).output.trim();
    }
    // Round-trip parses → formats → parses and compares the resulting AST
    // (modulo `loc`). This is the strongest possible check: it catches both
    // missing output and *extra* output, which `toContain` would miss.
    function expectRoundTripStable(input) {
        const formatted = formatAgency(input);
        const reparsed = parseAgency(formatted, {}, false, false);
        expect(reparsed.success).toBe(true);
        if (!reparsed.success)
            return;
        const firstParse = parseAgency(input, {}, false, false);
        expect(firstParse.success).toBe(true);
        if (!firstParse.success)
            return;
        // Strip `loc` fields recursively so we compare structure only.
        const stripLoc = (node) => {
            if (Array.isArray(node))
                return node.map(stripLoc);
            if (node && typeof node === "object") {
                const out = {};
                for (const [k, v] of Object.entries(node)) {
                    if (k === "loc")
                        continue;
                    out[k] = stripLoc(v);
                }
                return out;
            }
            return node;
        };
        expect(stripLoc(reparsed.result.nodes)).toEqual(stripLoc(firstParse.result.nodes));
    }
    it("formats `r is success` (bare boolean form) — round trip", () => {
        const input = `node main() {\n  let r = success(1)\n  let x = r is success\n}`;
        const output = formatAgency(input);
        expect(output).toContain("r is success");
        expect(output).not.toContain("isSuccess");
        expectRoundTripStable(input);
    });
    it("formats `r is failure` (bare boolean form) — round trip", () => {
        const input = `node main() {\n  let r = failure("e")\n  let x = r is failure\n}`;
        const output = formatAgency(input);
        expect(output).toContain("r is failure");
        expect(output).not.toContain("isFailure");
        expectRoundTripStable(input);
    });
    it("formats `r is success(v)` with binding — round trip", () => {
        const input = `node main() {\n  let r = success(1)\n  if (r is success(v)) {\n    print(v)\n  }\n}`;
        const output = formatAgency(input);
        expect(output).toContain("r is success(v)");
        expectRoundTripStable(input);
    });
    it("formats `r is failure(e)` with binding — round trip", () => {
        const input = `node main() {\n  let r = failure("e")\n  if (r is failure(e)) {\n    print(e)\n  }\n}`;
        const output = formatAgency(input);
        expect(output).toContain("r is failure(e)");
        expectRoundTripStable(input);
    });
    it("formats result patterns as match arm LHS — round trip", () => {
        const input = `node main() {\n  let r = success(1)\n  match (r) {\n    success(v) => print(v)\n    failure(e) => print(e)\n  }\n}`;
        const output = formatAgency(input);
        expect(output).toContain("success(v)");
        expect(output).toContain("failure(e)");
        expectRoundTripStable(input);
    });
    it("formats result patterns nested inside an array match pattern — round trip", () => {
        const input = `node main() {\n  let arr = [success(1), failure("e")]\n  match (arr) {\n    [success(v), _] => print(v)\n    _ => print("none")\n  }\n}`;
        const output = formatAgency(input);
        expect(output).toContain("[success(v), _]");
        expectRoundTripStable(input);
    });
});
describe("AgencyGenerator - preserveOrder mode", () => {
    function formatPreserveOrder(input) {
        const parseResult = parseAgency(input, {}, false);
        expect(parseResult.success).toBe(true);
        if (!parseResult.success)
            return "";
        const generator = new AgencyGenerator({ preserveOrder: true });
        return generator.generate(parseResult.result).output.trim();
    }
    it("does not sort imports alphabetically", () => {
        const input = `import { zebra } from "z"\nimport { apple } from "a"\n`;
        const output = formatPreserveOrder(input);
        expect(output.indexOf("zebra")).toBeLessThan(output.indexOf("apple"));
    });
    it("does not hoist mid-file imports to the top", () => {
        const input = `def first(): number { return 1 }\n\nimport { later } from "wherever"\n\ndef second(): number { return 2 }\n`;
        const output = formatPreserveOrder(input);
        const firstIdx = output.indexOf("def first");
        const importIdx = output.indexOf('import { later }');
        const secondIdx = output.indexOf("def second");
        expect(firstIdx).toBeGreaterThanOrEqual(0);
        expect(importIdx).toBeGreaterThan(firstIdx);
        expect(secondIdx).toBeGreaterThan(importIdx);
    });
});
