import assert from "node:assert/strict";
import stripAnsi from "strip-ansi";
import { describe, test } from "vitest";
import { initTheme } from "../../theme/theme.js";
import { ToolExecutionComponent } from "../tool-execution.js";

initTheme("dark", false);

function renderTool(
	toolName: string,
	args: Record<string, unknown>,
	result?: {
		content: Array<{ type: string; text?: string }>;
		isError: boolean;
		details?: Record<string, unknown>;
	},
	toolDefinition?: { label?: string },
	options: { startedAt?: number } = {},
): string {
	const component = new ToolExecutionComponent(
		toolName,
		args,
		options,
		toolDefinition as any,
		{ requestRender() {} } as any,
	);
	component.setExpanded(true);
	if (result) component.updateResult(result);
	return stripAnsi(component.render(120).join("\n"));
}

function renderToolCollapsed(
	toolName: string,
	args: Record<string, unknown>,
	result?: {
		content: Array<{ type: string; text?: string }>;
		isError: boolean;
		details?: Record<string, unknown>;
	},
): string {
	const component = new ToolExecutionComponent(toolName, args, {}, undefined, {
		requestRender() {},
	} as any);
	if (result) component.updateResult(result);
	return stripAnsi(component.render(120).join("\n"));
}

describe("ToolExecutionComponent", () => {
	test("renders capitalized adapter Bash tool names with bash output instead of generic args JSON", () => {
		const rendered = renderTool(
			"Bash",
			{ command: "pwd" },
			{ content: [{ type: "text", text: "/tmp/sf-pr-fix" }], isError: false },
		);

		assert.match(rendered, /\$ pwd/);
		assert.match(rendered, /\/tmp\/sf-pr-fix/);
		assert.doesNotMatch(rendered, /^\{\s*\}$/m);
	});

	test("renders capitalized adapter Read tool names with read output", () => {
		const rendered = renderTool(
			"Read",
			{ path: "/tmp/demo.txt" },
			{ content: [{ type: "text", text: "hello\nworld" }], isError: false },
		);

		assert.match(rendered, /read .*demo\.txt/);
		assert.match(rendered, /hello/);
		assert.match(rendered, /world/);
	});

	test("generic fallback strips mcp__<server>__ prefix and shows server·tool title", () => {
		const rendered = renderTool(
			"mcp__context7__resolve_library_id",
			{ name: "react" },
			{ content: [{ type: "text", text: "react@18.3.1" }], isError: false },
		);

		assert.match(rendered, /context7\u00b7resolve_library_id/);
		assert.doesNotMatch(rendered, /mcp__/);
		assert.match(rendered, /name="react"/);
		assert.match(rendered, /react@18\.3\.1/);
	});

	test("generic fallback renders compact key=value args for primitive args", () => {
		const rendered = renderTool("some_unknown_tool", {
			count: 3,
			enabled: true,
			label: "hello",
		});

		assert.match(rendered, /Some Unknown Tool/);
		assert.doesNotMatch(rendered, /some_unknown_tool/);
		assert.match(rendered, /count=3/);
		assert.match(rendered, /enabled=true/);
		assert.match(rendered, /label="hello"/);
		assert.doesNotMatch(rendered, /^\{$/m);
	});

	test("generic fallback truncates long output when collapsed", () => {
		const longOutput = Array.from(
			{ length: 25 },
			(_, i) => `line ${i + 1}`,
		).join("\n");
		const rendered = renderToolCollapsed(
			"mcp__demo__do_thing",
			{ ok: true },
			{ content: [{ type: "text", text: longOutput }], isError: false },
		);

		assert.match(rendered, /line 1\b/);
		assert.match(rendered, /line 10\b/);
		assert.doesNotMatch(rendered, /line 20\b/);
		assert.match(rendered, /\(15 more lines/);
	});

	test("generic fallback falls back to truncated JSON for complex args", () => {
		const rendered = renderTool("mcp__demo__nested", {
			payload: { nested: { deeply: ["a", "b", "c"] } },
			name: "x",
		});

		assert.match(rendered, /demo\u00b7nested/);
		// Multi-line JSON dump for the complex payload
		assert.match(rendered, /"payload"/);
		assert.match(rendered, /"nested"/);
	});

	test("custom tools without renderers use registered labels instead of raw ids", () => {
		const rendered = renderTool(
			"sf_plan_milestone",
			{ milestoneId: "M001" },
			undefined,
			{ label: "Plan Milestone" },
		);

		assert.match(rendered, /Tool Plan Milestone/);
		assert.match(rendered, /Plan Milestone/);
		assert.doesNotMatch(rendered, /sf_plan_milestone/);
	});

	test("tool frame header includes ISO minute timestamp", () => {
		const startedAt = new Date(2026, 4, 8, 20, 51, 0).getTime();
		const rendered = renderTool(
			"Read",
			{ path: "/tmp/demo.txt" },
			undefined,
			undefined,
			{ startedAt },
		);

		assert.match(rendered, /2026-05-08 20:51/);
	});
});
