import { describe, it, expect } from "vitest";
import { writeFileSync, unlinkSync } from "fs";
import path from "path";
import os from "os";
import { parseAgency } from "../parser.js";
import { SymbolTable } from "../symbolTable.js";
import { buildCompilationUnit } from "../compilationUnit.js";
import { typeCheck } from "./index.js";
function warningsFrom(source) {
    const file = path.join(os.tmpdir(), `tc-int-${Date.now()}-${Math.random().toString(36).slice(2)}.agency`);
    writeFileSync(file, source);
    try {
        const absPath = path.resolve(file);
        const symbolTable = SymbolTable.build(absPath);
        const parseResult = parseAgency(source, {});
        if (!parseResult.success)
            throw new Error("Parse failed");
        const program = parseResult.result;
        const info = buildCompilationUnit(program, symbolTable, absPath, source);
        const { errors } = typeCheck(program, {}, info);
        return errors.filter((e) => e.severity === "warning");
    }
    finally {
        unlinkSync(file);
    }
}
describe("interrupt kind warnings", () => {
    it("warns when calling a function with interrupts outside a handler", () => {
        const warnings = warningsFrom(`
      def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
      node main() {
        deploy()
      }
    `);
        expect(warnings).toHaveLength(1);
        expect(warnings[0].message).toContain("myapp::deploy");
    });
    it("warns at the node boundary with transitive interrupt kinds", () => {
        // Warnings only fire inside `node` bodies — `def` bodies are
        // expected to propagate, so the unhandled call inside
        // `orchestrate` is intentionally silent. The warning at the
        // `node main` call site still mentions the full transitive set
        // of kinds, so the user sees what they need to handle.
        const warnings = warningsFrom(`
      def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
      def orchestrate() {
        deploy()
      }
      node main() {
        orchestrate()
      }
    `);
        expect(warnings).toHaveLength(1);
        expect(warnings[0].message).toContain("'orchestrate'");
        expect(warnings[0].message).toContain("myapp::deploy");
    });
    it("does not warn for unhandled interrupt calls inside a def body", () => {
        // `def` is composable, not an entry point — propagating
        // interrupts to the caller's handler is the intended pattern.
        // The warning would otherwise flood every stdlib-style helper
        // that calls `read`/`glob`/etc. and train users to ignore it.
        const warnings = warningsFrom(`
      def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
      def orchestrate() {
        deploy()
      }
    `);
        expect(warnings).toHaveLength(0);
    });
    it("does not warn when call is inside a handleBlock", () => {
        const warnings = warningsFrom(`
      def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
      node main() {
        handle {
          deploy()
        } with (interrupt) {
          return approve()
        }
      }
    `);
        expect(warnings).toHaveLength(0);
    });
    it("does not warn when call has withModifier approve", () => {
        const warnings = warningsFrom(`
      def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
      node main() {
        deploy() with approve
      }
    `);
        expect(warnings).toHaveLength(0);
    });
    it("does not warn when call has withModifier reject", () => {
        const warnings = warningsFrom(`
      def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
      node main() {
        deploy() with reject
      }
    `);
        expect(warnings).toHaveLength(0);
    });
    it("still warns when call has withModifier propagate", () => {
        const warnings = warningsFrom(`
      def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
      node main() {
        deploy() with propagate
      }
    `);
        expect(warnings).toHaveLength(1);
        expect(warnings[0].message).toContain("myapp::deploy");
    });
    it("does not warn for functions with no interrupts", () => {
        const warnings = warningsFrom(`
      def add(a: number, b: number): number {
        return a + b
      }
      node main() {
        add(1, 2)
      }
    `);
        expect(warnings).toHaveLength(0);
    });
    it("does not warn when call is inside a nested handleBlock", () => {
        const warnings = warningsFrom(`
      def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
      node main() {
        handle {
          handle {
            deploy()
          } with (interrupt) {
            return approve()
          }
        } with (interrupt) {
          return reject()
        }
      }
    `);
        expect(warnings).toHaveLength(0);
    });
    it("warns for imported functions with interrupts", () => {
        const suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
        const libFile = path.join(os.tmpdir(), `tc-lib-${suffix}.agency`);
        const mainFile = path.join(os.tmpdir(), `tc-main-${suffix}.agency`);
        const mainSource = `
      import { deploy } from "${libFile}"
      node main() {
        deploy()
      }
    `;
        writeFileSync(libFile, `
      export def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
    `);
        writeFileSync(mainFile, mainSource);
        try {
            const absPath = path.resolve(mainFile);
            const symbolTable = SymbolTable.build(absPath);
            const parseResult = parseAgency(mainSource, {});
            if (!parseResult.success)
                throw new Error("Parse failed");
            const info = buildCompilationUnit(parseResult.result, symbolTable, absPath, mainSource);
            const { errors } = typeCheck(parseResult.result, {}, info);
            const warnings = errors.filter((e) => e.severity === "warning");
            expect(warnings).toHaveLength(1);
            expect(warnings[0].message).toContain("myapp::deploy");
        }
        finally {
            unlinkSync(mainFile);
            unlinkSync(libFile);
        }
    });
    it("warns for imported nodes with interrupts", () => {
        const suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
        const libFile = path.join(os.tmpdir(), `tc-lib-node-${suffix}.agency`);
        const mainFile = path.join(os.tmpdir(), `tc-main-node-${suffix}.agency`);
        const mainSource = `
      import node { checkout } from "${libFile}"
      node main() {
        return checkout()
      }
    `;
        writeFileSync(libFile, `
      export node checkout() {
        interrupt payment::charge("Charge?")
      }
    `);
        writeFileSync(mainFile, mainSource);
        try {
            const absPath = path.resolve(mainFile);
            const symbolTable = SymbolTable.build(absPath);
            const parseResult = parseAgency(mainSource, {});
            if (!parseResult.success)
                throw new Error("Parse failed");
            const info = buildCompilationUnit(parseResult.result, symbolTable, absPath, mainSource);
            const { errors } = typeCheck(parseResult.result, {}, info);
            const warnings = errors.filter((e) => e.severity === "warning");
            expect(warnings).toHaveLength(1);
            expect(warnings[0].message).toContain("payment::charge");
        }
        finally {
            unlinkSync(mainFile);
            unlinkSync(libFile);
        }
    });
    it("warns for imported functions with alias", () => {
        const suffix = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
        const libFile = path.join(os.tmpdir(), `tc-lib-alias-${suffix}.agency`);
        const mainFile = path.join(os.tmpdir(), `tc-main-alias-${suffix}.agency`);
        const mainSource = `
      import { deploy as d } from "${libFile}"
      node main() {
        d()
      }
    `;
        writeFileSync(libFile, `
      export def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
    `);
        writeFileSync(mainFile, mainSource);
        try {
            const absPath = path.resolve(mainFile);
            const symbolTable = SymbolTable.build(absPath);
            const parseResult = parseAgency(mainSource, {});
            if (!parseResult.success)
                throw new Error("Parse failed");
            const info = buildCompilationUnit(parseResult.result, symbolTable, absPath, mainSource);
            const { errors } = typeCheck(parseResult.result, {}, info);
            const warnings = errors.filter((e) => e.severity === "warning");
            expect(warnings).toHaveLength(1);
            expect(warnings[0].message).toContain("myapp::deploy");
        }
        finally {
            unlinkSync(mainFile);
            unlinkSync(libFile);
        }
    });
    it("does not warn when function is called via .preapprove()", () => {
        const warnings = warningsFrom(`
      def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
      node main() {
        const fn = deploy.preapprove()
        fn()
      }
    `);
        // fn() is a dynamic call to a variable, not a direct call to deploy,
        // so no warning is expected
        expect(warnings).toHaveLength(0);
    });
    it("does not warn when preapproved function is in tools array", () => {
        const warnings = warningsFrom(`
      def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
      node main() {
        llm("do it", { tools: [deploy.preapprove()] })
      }
    `);
        // deploy.preapprove() synthesizes as "any", so the function ref
        // is not extracted and no warning is generated
        expect(warnings).toHaveLength(0);
    });
    it("produces error when .preapprove() is called with arguments", () => {
        const file = path.join(os.tmpdir(), `tc-preapprove-${Date.now()}-${Math.random().toString(36).slice(2)}.agency`);
        const source = `
      def deploy() {
        interrupt myapp::deploy("Deploy?")
      }
      node main() {
        const fn = deploy.preapprove("bad")
      }
    `;
        writeFileSync(file, source);
        try {
            const absPath = path.resolve(file);
            const symbolTable = SymbolTable.build(absPath);
            const parseResult = parseAgency(source, {});
            if (!parseResult.success)
                throw new Error("Parse failed");
            const info = buildCompilationUnit(parseResult.result, symbolTable, absPath, source);
            const { errors } = typeCheck(parseResult.result, {}, info);
            const errs = errors.filter((e) => !e.severity || e.severity === "error");
            expect(errs.some((e) => e.message.includes(".preapprove()"))).toBe(true);
        }
        finally {
            unlinkSync(file);
        }
    });
});
