import { describe, it, expect } from "vitest";
import { parseAgency } from "../parser.js";
import { TypeScriptBuilder } from "./typescriptBuilder.js";
import { TypescriptPreprocessor } from "../preprocessors/typescriptPreprocessor.js";
import { buildCompilationUnit } from "../compilationUnit.js";
import { printTs } from "../ir/prettyPrint.js";
import { discoverFixturePairs } from "../../tests/fixtureDiscovery.js";
import path from "path";
function normalizeWhitespace(code) {
    return (code
        .replace(/\r\n/g, "\n")
        .split("\n")
        .map((line) => line.trimEnd())
        .join("\n")
        .replace(/\n\n\n+/g, "\n\n")
        .trim()
        .concat("\n"));
}
export function generateWithBuilder(agencySource, moduleId = "test.agency") {
    const parseResult = parseAgency(agencySource, {}, false);
    if (!parseResult.success) {
        throw new Error(`Failed to parse: ${parseResult.message}`);
    }
    const info = buildCompilationUnit(parseResult.result);
    const preprocessor = new TypescriptPreprocessor(parseResult.result, {}, info);
    const preprocessedProgram = preprocessor.preprocess();
    const builder = new TypeScriptBuilder(undefined, info, moduleId);
    const ir = builder.build(preprocessedProgram);
    return printTs(ir);
}
const FIXTURES_DIR = path.resolve(__dirname, "../../tests/typescriptBuilder");
describe("TypeScript Builder Integration Tests", () => {
    const fixtures = discoverFixturePairs(FIXTURES_DIR, ".mjs");
    if (fixtures.length === 0) {
        it("should find test fixtures (add .agency + .mjs pairs to tests/typescriptBuilder/)", () => {
            // No fixtures yet — this is expected initially.
            // Add .agency files and run `make builder-fixtures` to generate .mjs files.
            expect(true).toBe(true);
        });
        return;
    }
    describe.each(fixtures)("Fixture: $name", ({ name, filePath, agencyContent, companionContent }) => {
        it("should generate correct TypeScript output", () => {
            let generatedTS;
            try {
                generatedTS = generateWithBuilder(agencyContent, name + ".agency");
            }
            catch (error) {
                throw new Error(`Failed to generate TypeScript for fixture: ${name}\nFile: ${filePath}\nError: ${error instanceof Error ? error.message : String(error)}`);
            }
            expect(normalizeWhitespace(generatedTS)).toBe(normalizeWhitespace(companionContent));
        });
    });
});
describe("Named argument validation", () => {
    // Named arg validation is now done at runtime by AgencyFunction.invoke(),
    // not at compile time. These tests verify the builder compiles successfully.
    it("should compile when skipping a required argument (validated at runtime)", () => {
        expect(() => generateWithBuilder(`
def greet(name: string, greeting: string = "Hello") {
  print(name)
}
greet(greeting: "Hi")
`)).not.toThrow();
    });
    it("should compile with unknown named argument (validated at runtime)", () => {
        expect(() => generateWithBuilder(`
def foo(a: string) {
  print(a)
}
foo(a: "hi", extra: "oops")
`)).not.toThrow();
    });
    it("should accept correct named arguments", () => {
        expect(() => generateWithBuilder(`
def greet(name: string, greeting: string = "Hello") {
  print(name)
}
greet(name: "world", greeting: "Hi")
`)).not.toThrow();
    });
    it("should accept reordered named arguments", () => {
        expect(() => generateWithBuilder(`
def greet(name: string, greeting: string = "Hello") {
  print(name)
}
greet(greeting: "Hi", name: "world")
`)).not.toThrow();
    });
    it("should compile with positional after named (validated at runtime)", () => {
        expect(() => generateWithBuilder(`
def greet(name: string, greeting: string = "Hello") {
  print(name)
}
greet(name: "world", "Hi")
`)).not.toThrow();
    });
    it("should compile with duplicate named argument (validated at runtime)", () => {
        expect(() => generateWithBuilder(`
def greet(name: string, greeting: string = "Hello") {
  print(name)
}
greet(name: "world", name: "other")
`)).not.toThrow();
    });
    it("should accept named args with block parameters", () => {
        expect(() => generateWithBuilder(`
def twice(label: string, block: () => string): string {
  return block() + block()
}
twice(label: "test") as {
  return "hi"
}
`)).not.toThrow();
    });
});
describe("Safe functions and methods", () => {
    it.each([
        { safe: true, funcName: "safeFnIf", emitsRetryableFalse: false, block: "if" },
        { safe: false, funcName: "unsafeFnIf", emitsRetryableFalse: true, block: "if" },
        { safe: true, funcName: "safeFnFor", emitsRetryableFalse: false, block: "for" },
        { safe: false, funcName: "unsafeFnFor", emitsRetryableFalse: true, block: "for" },
        { safe: true, funcName: "safeFnWhile", emitsRetryableFalse: false, block: "while" },
        { safe: false, funcName: "unsafeFnWhile", emitsRetryableFalse: true, block: "while" },
    ])("function $funcName with impure call in $block block (safe=$safe) emitsRetryableFalse=$emitsRetryableFalse", ({ safe, funcName, emitsRetryableFalse, block }) => {
        const safeKeyword = safe ? "safe " : "";
        const blocks = {
            "if": `if (shouldSave) {\n    return saveItem(id)\n  }`,
            "for": `for (item in items) {\n    saveItem(item)\n  }`,
            "while": `while (shouldSave) {\n    return saveItem(id)\n  }`,
        };
        const code = `
import { saveItem } from "./tools.js"

${safeKeyword}def ${funcName}(id: string, shouldSave: boolean, items: string[]): string {
  ${blocks[block]}
  return id
}
`;
        const output = generateWithBuilder(code);
        const funcMatch = output.match(new RegExp(`async function __${funcName}_impl\\([\\s\\S]*?finally`));
        expect(funcMatch).toBeTruthy();
        if (emitsRetryableFalse) {
            expect(funcMatch[0]).toContain("__retryable = false");
        }
        else {
            expect(funcMatch[0]).not.toContain("__retryable = false");
        }
    });
});
describe("schema(Type) expression", () => {
    it("should compile schema(Type) for named type aliases", () => {
        expect(() => generateWithBuilder(`
type Category = "bug" | "feature"
node main() {
  const s = schema(Category)
}
`)).not.toThrow();
    });
    it("should compile schema(Type) for builtin types", () => {
        expect(() => generateWithBuilder(`
node main() {
  const s = schema(number)
}
`)).not.toThrow();
    });
    it("should compile schema(Result<number>)", () => {
        expect(() => generateWithBuilder(`
node main() {
  const s = schema(Result<number>)
}
`)).not.toThrow();
    });
    it("generated code contains new Schema(...)", () => {
        const output = generateWithBuilder(`
type Category = "bug" | "feature"
node main() {
  const s = schema(Category)
}
`);
        expect(output).toContain("new Schema(");
    });
});
describe("subthread rejects continue/session at codegen", () => {
    // `subthread` is identity-bound to its parent's context; resuming
    // via `continue` or `session` would be ambiguous, so the builder
    // throws. See `lib/backends/typescriptBuilder.ts` (subthread continue/session check).
    it("throws when subthread() uses `continue`", () => {
        expect(() => generateWithBuilder(`
node main() {
  thread {
    subthread(continue: "t1") {
    }
  }
}
`)).toThrow(/subthread.*continue.*session/i);
    });
    it("throws when subthread() uses `session`", () => {
        expect(() => generateWithBuilder(`
node main() {
  thread {
    subthread(session: "my-session") {
    }
  }
}
`)).toThrow(/subthread.*continue.*session/i);
    });
});
import { mapTypeToValidationSchema } from "./typescriptGenerator/typeToZodSchema.js";
describe("mapTypeToValidationSchema", () => {
    it("generates Result validation schema for bare Result", () => {
        const schema = mapTypeToValidationSchema({ type: "resultType", successType: { type: "primitiveType", value: "any" }, failureType: { type: "primitiveType", value: "any" } }, {});
        expect(schema).toContain("z.literal(true)");
        expect(schema).toContain("z.literal(false)");
    });
    it("generates Result validation schema with typed success", () => {
        const schema = mapTypeToValidationSchema({ type: "resultType", successType: { type: "primitiveType", value: "number" }, failureType: { type: "primitiveType", value: "string" } }, {});
        expect(schema).toContain("z.number()");
        expect(schema).toContain("z.literal(true)");
    });
    it("delegates non-Result types to mapTypeToZodSchema", () => {
        const schema = mapTypeToValidationSchema({ type: "primitiveType", value: "number" }, {});
        expect(schema).toBe("z.number()");
    });
});
