import { describe, expect, it, vi } from "vitest";
import {
	type AvailableAgent,
	createSummonTool,
	type SummonCallbacks,
} from "../../src/tools/summon.js";

const noopCallbacks: SummonCallbacks = {
	spawn: async () => "agent-id-stub",
	getResult: async () => ({ status: "done", result: "" }),
};

describe("createSummonTool — description content", () => {
	it("lists the agent directories so the LLM knows where to look", () => {
		const tool = createSummonTool(
			"/tmp/work",
			noopCallbacks,
			[],
			[],
			["/home/u/.config/dispatch/agents", "/tmp/work/.dispatch/agents"],
		);
		expect(tool.description).toContain("/home/u/.config/dispatch/agents");
		expect(tool.description).toContain("/tmp/work/.dispatch/agents");
		expect(tool.description).toContain("read_file");
	});

	it("includes available agent slugs+names in the description", () => {
		const agents: AvailableAgent[] = [
			{
				slug: "programmer",
				name: "Programmer",
				description: "Implements code from a plan.",
				path: "/home/u/.config/dispatch/agents/programmer.toml",
			},
			{
				slug: "researcher",
				name: "Researcher",
				description: "Investigates topics.",
				path: "/home/u/.config/dispatch/agents/researcher.toml",
			},
		];
		const tool = createSummonTool(
			"/tmp/work",
			noopCallbacks,
			agents,
			[],
			["/home/u/.config/dispatch/agents"],
		);
		expect(tool.description).toContain("programmer");
		expect(tool.description).toContain("Programmer");
		expect(tool.description).toContain("Implements code from a plan");
		expect(tool.description).toContain("researcher");
		expect(tool.description).toContain("Investigates topics");
	});

	it("emits a 'no agents defined' notice when the catalog is empty", () => {
		const tool = createSummonTool(
			"/tmp/work",
			noopCallbacks,
			[],
			[],
			["/home/u/.config/dispatch/agents"],
		);
		expect(tool.description).toContain("No agent definitions are currently defined");
	});

	it("shows two groups when userAgentEnabled is true", () => {
		const subagents: AvailableAgent[] = [
			{
				slug: "programmer",
				name: "Programmer",
				description: "Codes things",
				path: "/agents/programmer.toml",
			},
		];
		const userAgents: AvailableAgent[] = [
			{
				slug: "default",
				name: "Default",
				description: "Default agent",
				path: "/agents/default.toml",
			},
		];
		const tool = createSummonTool(
			"/tmp/work",
			noopCallbacks,
			subagents,
			userAgents,
			["/agents"],
			true,
		);
		expect(tool.description).toContain("Subagents (spawned as child tabs):");
		expect(tool.description).toContain(
			"User agents (spawned as independent top-level tabs, requires top_level=true):",
		);
		expect(tool.description).toContain("programmer");
		expect(tool.description).toContain("default");
	});

	it("hides user agents group when userAgentEnabled is false", () => {
		const subagents: AvailableAgent[] = [
			{
				slug: "programmer",
				name: "Programmer",
				description: "Codes things",
				path: "/agents/programmer.toml",
			},
		];
		const userAgents: AvailableAgent[] = [
			{
				slug: "default",
				name: "Default",
				description: "Default agent",
				path: "/agents/default.toml",
			},
		];
		const tool = createSummonTool(
			"/tmp/work",
			noopCallbacks,
			subagents,
			userAgents,
			["/agents"],
			false,
		);
		expect(tool.description).toContain("Available agents:");
		expect(tool.description).not.toContain("User agents");
		// "default" appears in generic description text, so check for the slug listing format
		expect(tool.description).not.toContain("- default: Default");
	});
});

describe("createSummonTool — execute() argument forwarding", () => {
	it("forwards agent slug through to callbacks.spawn", async () => {
		const spawn = vi.fn(async () => "tab-xyz");
		const tool = createSummonTool(
			"/tmp/work",
			{ spawn, getResult: async () => ({ status: "done", result: "ok" }) },
			[],
			[],
		);
		await tool.execute({
			task: "do thing",
			agent: "programmer",
			background: true,
		});
		expect(spawn).toHaveBeenCalledTimes(1);
		const callArg = spawn.mock.calls[0]?.[0];
		expect(callArg).toMatchObject({
			task: "do thing",
			agentSlug: "programmer",
		});
	});

	it("returns spawned agent_id when background=true (no blocking on result)", async () => {
		const getResult = vi.fn(async () => ({ status: "done" as const, result: "should-not-see" }));
		const tool = createSummonTool("/tmp/work", { spawn: async () => "id-42", getResult }, [], []);
		const out = await tool.execute({ task: "x", agent: "test-agent", background: true });
		expect(out).toContain("id-42");
		// Background mode must not block on getResult
		expect(getResult).not.toHaveBeenCalled();
	});

	it("blocks on result and returns it when background=false (default)", async () => {
		const tool = createSummonTool(
			"/tmp/work",
			{
				spawn: async () => "id-1",
				getResult: async () => ({ status: "done", result: "child-output" }),
			},
			[],
			[],
		);
		const out = await tool.execute({ task: "x", agent: "test-agent" });
		// Foreground summons prefix the blocked result with `agent_id: <id>` so
		// the frontend's ToolCallDisplay regex can surface the "Open Tab" button
		// (see summon.ts). Assert both the prefix and the child output survive.
		expect(out).toContain("agent_id: id-1");
		expect(out).toBe("agent_id: id-1\n\nchild-output");
	});

	it("surfaces child errors when blocking", async () => {
		const tool = createSummonTool(
			"/tmp/work",
			{
				spawn: async () => "id-1",
				getResult: async () => ({ status: "error", error: "boom" }),
			},
			[],
			[],
		);
		const out = await tool.execute({ task: "x", agent: "test-agent" });
		expect(out).toContain("boom");
	});

	it("returns fire-and-forget message when top_level=true", async () => {
		const spawn = vi.fn(async () => "ua-tab-1");
		const getResult = vi.fn(async () => ({ status: "done" as const, result: "nope" }));
		const tool = createSummonTool(
			"/tmp/work",
			{ spawn, getResult },
			[],
			[],
			[],
			true, // userAgentEnabled
		);
		const out = await tool.execute({
			task: "do stuff",
			agent: "default",
			top_level: true,
		});
		expect(out).toContain("User agent spawned successfully");
		expect(out).toContain("ua-tab-1");
		expect(out).toContain("fire-and-forget");
		expect(getResult).not.toHaveBeenCalled();

		// Verify topLevel was forwarded to spawn
		const callArg = spawn.mock.calls[0]?.[0];
		expect(callArg).toMatchObject({ topLevel: true });
	});

	it("ignores top_level when userAgentEnabled is false", async () => {
		const spawn = vi.fn(async () => "tab-1");
		const getResult = vi.fn(async () => ({ status: "done" as const, result: "result" }));
		const tool = createSummonTool(
			"/tmp/work",
			{ spawn, getResult },
			[],
			[],
			[],
			false, // userAgentEnabled
		);
		const out = await tool.execute({
			task: "do stuff",
			agent: "default",
			top_level: true, // should be ignored
		});
		// Should behave as a normal foreground summon, not fire-and-forget
		expect(out).not.toContain("fire-and-forget");
		expect(getResult).toHaveBeenCalled();
	});
});
