import { describe, it, expect } from "vitest";
import { __call, __callMethod } from "./call.js";
import { AgencyFunction } from "./agencyFunction.js";
function makeAgencyFn(fn, name = "testFn") {
    return new AgencyFunction({
        name,
        module: "test.agency",
        fn,
        params: [{ name: "x", hasDefault: false, defaultValue: undefined, variadic: false }],
        toolDefinition: null,
    });
}
describe("__call", () => {
    it("calls AgencyFunction via .invoke() with descriptor", async () => {
        const fn = makeAgencyFn(async (x) => ({ x }));
        const result = await __call(fn, { type: "positional", args: [42] });
        expect(result).toEqual({ x: 42 });
    });
    it("does not forward any trailing state arg to plain TS callees", async () => {
        const seen = [];
        const target = (...args) => {
            seen.push(args);
        };
        await __call(target, { type: "positional", args: [1, 2] });
        expect(seen[0]).toEqual([1, 2]);
    });
    it("rejects unknown third positional via the type system", async () => {
        const fn = makeAgencyFn(async (x) => x);
        // @ts-expect-error — third positional was stateExtras; removed in this PR.
        await __call(fn, { type: "positional", args: [1] }, { somethingCustom: true });
    });
    it("calls plain TS function by spreading positional args", async () => {
        const fn = (a, b) => a + b;
        const result = await __call(fn, { type: "positional", args: [3, 4] });
        expect(result).toBe(7);
    });
    it("throws on named args to a TS function", async () => {
        const fn = (a) => a;
        await expect(__call(fn, { type: "named", positionalArgs: [], namedArgs: { a: 1 } })).rejects.toThrow("Named arguments are not supported");
    });
    it("throws a trailing-block-specific error for `as { ... }` on a TS function", async () => {
        // A trailing `as x { ... }` block desugars to a "named" descriptor
        // carrying `blockArg` (with no other named args). The error must
        // name the actual problem (trailing block on JS function), not the
        // generic "named arguments not supported" which would confuse a
        // user who never wrote any named args.
        const fn = (cb) => cb();
        const blockArg = makeAgencyFn(async () => "block-ran");
        await expect(__call(fn, {
            type: "named",
            positionalArgs: [],
            namedArgs: {},
            blockArg,
        })).rejects.toThrow("Cannot pass a trailing block to non-Agency function");
    });
    it("throws on non-callable target", async () => {
        await expect(__call(42, { type: "positional", args: [] })).rejects.toThrow("Cannot call non-function value");
    });
});
describe("__callMethod", () => {
    it("calls AgencyFunction stored as object property via .invoke()", async () => {
        const fn = makeAgencyFn(async (x, state) => x * 2);
        const obj = { myFunc: fn };
        const result = await __callMethod(obj, "myFunc", { type: "positional", args: [5] });
        expect(result).toBe(10);
    });
    it("calls TS method preserving this binding", async () => {
        const s = new Set();
        await __callMethod(s, "add", { type: "positional", args: [42] });
        expect(s.has(42)).toBe(true);
    });
    it("calls AgencyFunction stored in array by index", async () => {
        const fn = makeAgencyFn(async (x, state) => x + 1);
        const arr = [fn];
        const result = await __callMethod(arr, 0, { type: "positional", args: [10] });
        expect(result).toBe(11);
    });
    it("short-circuits to undefined when optional and obj is null", async () => {
        const result = await __callMethod(null, "foo", { type: "positional", args: [] }, true);
        expect(result).toBeUndefined();
    });
    it("short-circuits to undefined when optional and obj is undefined", async () => {
        const result = await __callMethod(undefined, "foo", { type: "positional", args: [] }, true);
        expect(result).toBeUndefined();
    });
    it("calls normally when optional and obj is non-nullish", async () => {
        const obj = { greet: (name) => `hi ${name}` };
        const result = await __callMethod(obj, "greet", { type: "positional", args: ["Bob"] }, true);
        expect(result).toBe("hi Bob");
    });
    it("throws on named args to a TS method", async () => {
        const obj = { fn: (a) => a };
        await expect(__callMethod(obj, "fn", { type: "named", positionalArgs: [], namedArgs: { a: 1 } })).rejects.toThrow("Named arguments are not supported");
    });
    it("throws a trailing-block-specific error for `as { ... }` on a TS method", async () => {
        // Same regression as `__call` above, but reached through a method
        // dispatch. Without the dedicated check the user sees the generic
        // named-args error and has no idea why.
        const obj = { fn: (cb) => cb() };
        const blockArg = makeAgencyFn(async () => "block-ran");
        await expect(__callMethod(obj, "fn", {
            type: "named",
            positionalArgs: [],
            namedArgs: {},
            blockArg,
        })).rejects.toThrow("Cannot pass a trailing block to non-Agency function");
    });
    it("dispatches .preapprove() with no args", async () => {
        const fn = makeAgencyFn(async (x) => x);
        const result = await __callMethod(fn, "preapprove", { type: "positional", args: [] });
        expect(AgencyFunction.isAgencyFunction(result)).toBe(true);
        expect(result.isPreapproved).toBe(true);
    });
    it("throws when .preapprove() is called with positional args", async () => {
        const fn = makeAgencyFn(async (x) => x);
        await expect(__callMethod(fn, "preapprove", { type: "positional", args: ["bad"] })).rejects.toThrow(".preapprove() takes no arguments");
    });
    it("throws when .preapprove() is called with named args", async () => {
        const fn = makeAgencyFn(async (x) => x);
        await expect(__callMethod(fn, "preapprove", { type: "named", positionalArgs: [], namedArgs: { foo: 1 } })).rejects.toThrow(".preapprove() takes no arguments");
    });
});
