import type { LogRecord } from "@dispatch/kernel";
import { describe, expect, it } from "vitest";
import { formatDuration, renderEasyView } from "./easy-view.js";

describe("renderEasyView", () => {
	it("returns empty string for empty records", () => {
		expect(renderEasyView([])).toBe("");
	});

	it("renders a single span with no children", () => {
		const records: LogRecord[] = [
			{
				kind: "span-open",
				spanId: "s1",
				name: "step",
				timestamp: 1000,
				extensionId: "ext",
				turnId: "t1",
			},
			{
				kind: "span-close",
				spanId: "s1",
				name: "step",
				timestamp: 2800,
				durationMs: 1800,
				status: "ok",
				extensionId: "ext",
				turnId: "t1",
			},
		];
		expect(renderEasyView(records)).toBe("- step 1.8s");
	});

	it("renders a span with nested log records", () => {
		const records: LogRecord[] = [
			{
				kind: "span-open",
				spanId: "s1",
				name: "step",
				timestamp: 1000,
				extensionId: "ext",
				turnId: "t1",
			},
			{
				kind: "log",
				level: "info",
				msg: "thinking...",
				timestamp: 1200,
				extensionId: "ext",
				turnId: "t1",
				parentSpanId: "s1",
			},
			{
				kind: "span-close",
				spanId: "s1",
				name: "step",
				timestamp: 2800,
				durationMs: 1800,
				status: "ok",
				extensionId: "ext",
				turnId: "t1",
			},
		];
		const expected = ["- step 1.8s", "  - thinking..."].join("\n");
		expect(renderEasyView(records)).toBe(expected);
	});

	it("renders nested spans with indentation", () => {
		const records: LogRecord[] = [
			{
				kind: "span-open",
				spanId: "parent",
				name: "step",
				timestamp: 1000,
				extensionId: "ext",
				turnId: "t1",
			},
			{
				kind: "span-open",
				spanId: "child",
				name: "tool.call",
				timestamp: 1100,
				extensionId: "ext",
				turnId: "t1",
				parentSpanId: "parent",
			},
			{
				kind: "log",
				level: "info",
				msg: "executing",
				timestamp: 1200,
				extensionId: "ext",
				turnId: "t1",
				parentSpanId: "child",
			},
			{
				kind: "span-close",
				spanId: "child",
				name: "tool.call",
				timestamp: 1500,
				durationMs: 400,
				status: "ok",
				extensionId: "ext",
				turnId: "t1",
				parentSpanId: "parent",
			},
			{
				kind: "span-close",
				spanId: "parent",
				name: "step",
				timestamp: 2800,
				durationMs: 1800,
				status: "ok",
				extensionId: "ext",
				turnId: "t1",
			},
		];
		const expected = ["- step 1.8s", "  - tool.call 400ms", "    - executing"].join("\n");
		expect(renderEasyView(records)).toBe(expected);
	});

	it("renders a span-open without matching span-close as (open)", () => {
		const records: LogRecord[] = [
			{
				kind: "span-open",
				spanId: "s-open",
				name: "step",
				timestamp: 1000,
				extensionId: "ext",
				turnId: "t1",
			},
		];
		expect(renderEasyView(records)).toBe("- step (open)");
	});

	it("renders root-level log records without a parent span", () => {
		const records: LogRecord[] = [
			{
				kind: "log",
				level: "info",
				msg: "top-level",
				timestamp: 500,
				extensionId: "ext",
				turnId: "t1",
			},
			{
				kind: "log",
				level: "warn",
				msg: "a warning",
				timestamp: 600,
				extensionId: "ext",
				turnId: "t1",
			},
		];
		const expected = ["- top-level", "- [warn] a warning"].join("\n");
		expect(renderEasyView(records)).toBe(expected);
	});

	it("skips span-close records without a matching span-open", () => {
		const records: LogRecord[] = [
			{
				kind: "span-close",
				spanId: "orphan",
				name: "orphan",
				timestamp: 1000,
				durationMs: 100,
				status: "ok",
				extensionId: "ext",
				turnId: "t1",
			},
			{
				kind: "log",
				level: "info",
				msg: "real",
				timestamp: 2000,
				extensionId: "ext",
				turnId: "t1",
			},
		];
		expect(renderEasyView(records)).toBe("- real");
	});

	it("renders span with ERR status", () => {
		const records: LogRecord[] = [
			{
				kind: "span-open",
				spanId: "s1",
				name: "failing-step",
				timestamp: 1000,
				extensionId: "ext",
				turnId: "t1",
			},
			{
				kind: "span-close",
				spanId: "s1",
				name: "failing-step",
				timestamp: 1100,
				durationMs: 100,
				status: "error",
				extensionId: "ext",
				turnId: "t1",
			},
		];
		expect(renderEasyView(records)).toBe("- failing-step 100ms ERR");
	});

	it("renders body hint when body is present", () => {
		const records: LogRecord[] = [
			{
				kind: "span-open",
				spanId: "s1",
				name: "prompt",
				timestamp: 1000,
				extensionId: "ext",
				turnId: "t1",
				body: "x".repeat(2100),
			},
			{
				kind: "span-close",
				spanId: "s1",
				name: "prompt",
				timestamp: 1100,
				durationMs: 100,
				status: "ok",
				extensionId: "ext",
				turnId: "t1",
			},
		];
		expect(renderEasyView(records)).toBe("- prompt 100ms [body 2.1k]");
	});

	it("renders log record body hint", () => {
		const records: LogRecord[] = [
			{
				kind: "log",
				level: "debug",
				msg: "payload",
				timestamp: 500,
				extensionId: "ext",
				turnId: "t1",
				body: "abc",
			},
		];
		expect(renderEasyView(records)).toBe("- [debug] payload [body 3b]");
	});

	it("renders attributes on span (up to 3)", () => {
		const records: LogRecord[] = [
			{
				kind: "span-open",
				spanId: "s1",
				name: "provider.request",
				timestamp: 1000,
				extensionId: "ext",
				turnId: "t1",
				attributes: { model: "gpt-4", method: "POST", status: 200 },
			},
			{
				kind: "span-close",
				spanId: "s1",
				name: "provider.request",
				timestamp: 1500,
				durationMs: 500,
				status: "ok",
				extensionId: "ext",
				turnId: "t1",
			},
		];
		const output = renderEasyView(records);
		expect(output).toContain("provider.request 500ms");
		expect(output).toContain('model="gpt-4"');
		expect(output).toContain('method="POST"');
		expect(output).toContain("status=200");
	});

	it("renders mixed records in correct order", () => {
		const records: LogRecord[] = [
			{
				kind: "span-open",
				spanId: "s1",
				name: "step",
				timestamp: 1000,
				extensionId: "ext",
				turnId: "t1",
			},
			{
				kind: "log",
				level: "info",
				msg: "inside step",
				timestamp: 1200,
				extensionId: "ext",
				turnId: "t1",
				parentSpanId: "s1",
			},
			{
				kind: "span-close",
				spanId: "s1",
				name: "step",
				timestamp: 2800,
				durationMs: 1800,
				status: "ok",
				extensionId: "ext",
				turnId: "t1",
			},
			{
				kind: "log",
				level: "info",
				msg: "after step",
				timestamp: 3000,
				extensionId: "ext",
				turnId: "t1",
			},
		];
		const expected = ["- step 1.8s", "  - inside step", "- after step"].join("\n");
		expect(renderEasyView(records)).toBe(expected);
	});

	it("deterministic output for same input", () => {
		const records: LogRecord[] = [
			{
				kind: "span-open",
				spanId: "s1",
				name: "step",
				timestamp: 1000,
				extensionId: "ext",
				turnId: "t1",
			},
			{
				kind: "span-close",
				spanId: "s1",
				name: "step",
				timestamp: 1800,
				durationMs: 800,
				status: "ok",
				extensionId: "ext",
				turnId: "t1",
			},
		];
		expect(renderEasyView(records)).toBe(renderEasyView(records));
	});
});

describe("formatDuration", () => {
	it("formats milliseconds under 1s", () => {
		expect(formatDuration(42)).toBe("42ms");
		expect(formatDuration(999)).toBe("999ms");
	});

	it("formats seconds", () => {
		expect(formatDuration(1000)).toBe("1.0s");
		expect(formatDuration(1500)).toBe("1.5s");
		expect(formatDuration(59999)).toBe("60.0s");
	});

	it("formats minutes", () => {
		expect(formatDuration(60000)).toBe("1m0s");
		expect(formatDuration(90000)).toBe("1m30s");
		expect(formatDuration(125000)).toBe("2m5s");
	});
});
