"use client";

import {
	ExternalLinkIcon,
	FileIcon,
	Maximize2Icon,
	Minimize2Icon,
	RefreshCwIcon,
	XIcon,
} from "lucide-react";
import { useEffect, useMemo, useRef, useState } from "react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { Button } from "@/components/ui/button";
import {
	Sheet,
	SheetClose,
	SheetContent,
	SheetTitle,
} from "@/components/ui/sheet";
import { cn } from "@/lib/utils";

type TableSection = {
	title: string;
	source_path: string;
	kind: "csv" | "jsonl" | "text" | "markdown" | "missing" | "error";
	columns: string[];
	rows: string[][];
	total_rows: number;
	shown_rows: number;
	error?: string;
	body?: string;
};

type StepDetail = {
	step: string;
	run_dic: number | null;
	format:
		| "markdown"
		| "jsonl"
		| "json"
		| "csv"
		| "text"
		| "empty"
		| "tables";
	content?: string;
	sections?: TableSection[];
	source_path: string | null;
	fetched_at_ms: number;
	error?: string;
	tried_paths?: string[];
};

type Props = {
	sessionId: string | null;
	stepKey: string | null;
	runId?: string | null;
	stepTitle?: string;
	stepStatus?: string;
	open: boolean;
	onOpenChange: (open: boolean) => void;
};

export function StepDetailSheet({
	sessionId,
	stepKey,
	runId,
	stepTitle,
	stepStatus,
	open,
	onOpenChange,
}: Props) {
	const [detail, setDetail] = useState<StepDetail | null>(null);
	const [loading, setLoading] = useState(false);
	const [reloadCount, setReloadCount] = useState(0);
	const [width, setWidth] = useState(672);
	const [isFullscreen, setIsFullscreen] = useState(false);
	const dragRef = useRef<{ startX: number; startW: number } | null>(null);

	const onResizePointerDown = (e: React.PointerEvent<HTMLDivElement>) => {
		if (isFullscreen) return;
		e.currentTarget.setPointerCapture(e.pointerId);
		dragRef.current = { startX: e.clientX, startW: width };
	};
	const onResizePointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
		if (!dragRef.current) return;
		const dx = dragRef.current.startX - e.clientX;
		const max =
			typeof window !== "undefined"
				? Math.max(360, window.innerWidth - 32)
				: 1200;
		setWidth(Math.max(360, Math.min(max, dragRef.current.startW + dx)));
	};
	const onResizePointerUp = (e: React.PointerEvent<HTMLDivElement>) => {
		if (!dragRef.current) return;
		dragRef.current = null;
		e.currentTarget.releasePointerCapture(e.pointerId);
	};

	useEffect(() => {
		if (!open || !sessionId || !stepKey) return;
		let cancelled = false;
		setLoading(true);
		setDetail(null);
		const params = new URLSearchParams({
			session_id: sessionId,
			step: stepKey,
		});
		if (runId) params.set("run_id", runId);
		const url = `/api/claw/training/step-detail?${params.toString()}`;
		fetch(url, { cache: "no-store" })
			.then((res) => (res.ok ? res.json() : null))
			.then((payload) => {
				if (cancelled) return;
				setDetail(payload);
				setLoading(false);
			})
			.catch(() => {
				if (!cancelled) setLoading(false);
			});
		return () => {
			cancelled = true;
		};
	}, [open, sessionId, stepKey, runId, reloadCount]);

	return (
		<Sheet open={open} onOpenChange={onOpenChange}>
			<SheetContent
				side="right"
				hideClose
				onEscapeKeyDown={(e) => {
					if (isFullscreen) {
						e.preventDefault();
						setIsFullscreen(false);
					}
				}}
				className="flex max-w-none flex-col gap-0 p-0 sm:max-w-none"
				style={
					isFullscreen
						? { width: "100vw", maxWidth: "100vw" }
						: { width: `${width}px`, maxWidth: "none" }
				}
			>
				{!isFullscreen ? (
					<div
						onPointerDown={onResizePointerDown}
						onPointerMove={onResizePointerMove}
						onPointerUp={onResizePointerUp}
						onPointerCancel={onResizePointerUp}
						className="group absolute inset-y-0 left-0 z-50 w-1.5 cursor-ew-resize hover:bg-sky-500/40 active:bg-sky-500/60"
						title="拖动调整宽度"
						aria-label="拖动调整宽度"
					>
						<div className="-translate-y-1/2 absolute top-1/2 left-[2px] h-10 w-[2px] rounded-full bg-muted-foreground/30 group-hover:bg-sky-400" />
					</div>
				) : null}
				<header className="flex shrink-0 items-start justify-between gap-3 border-b px-5 py-3">
					<div className="min-w-0">
						<div className="flex items-center gap-2">
							<SheetTitle className="font-semibold text-base text-foreground">
								{stepTitle ?? stepKey ?? "—"}
							</SheetTitle>
							{stepStatus ? <StatusPill status={stepStatus} /> : null}
							{detail?.run_dic ? (
								<span className="rounded bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground">
									runDic={detail.run_dic}
								</span>
							) : null}
						</div>
						{detail?.source_path ? (
							<div className="mt-1 flex items-center gap-1 truncate font-mono text-[11px] text-muted-foreground">
								<FileIcon className="size-3 shrink-0" />
								<span className="truncate">{detail.source_path}</span>
							</div>
						) : null}
					</div>
					<div className="mt-0.5 flex shrink-0 items-center gap-1">
						<Button
							type="button"
							variant="ghost"
							size="icon"
							className="size-7"
							onClick={() => setIsFullscreen((v) => !v)}
							title={isFullscreen ? "退出全屏 (Esc)" : "全屏"}
							aria-label={isFullscreen ? "退出全屏" : "全屏"}
						>
							{isFullscreen ? (
								<Minimize2Icon className="size-3.5" />
							) : (
								<Maximize2Icon className="size-3.5" />
							)}
						</Button>
						<Button
							type="button"
							variant="ghost"
							size="icon"
							className="size-7"
							onClick={() => setReloadCount((c) => c + 1)}
							title="刷新"
							aria-label="刷新"
						>
							<RefreshCwIcon className="size-3.5" />
						</Button>
						<SheetClose
							className="inline-flex size-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2"
							aria-label="关闭"
						>
							<XIcon className="size-3.5" />
						</SheetClose>
					</div>
				</header>
				<div className="min-h-0 flex-1 overflow-y-auto px-5 py-4 text-sm">
					{loading ? (
						<p className="text-muted-foreground text-xs">加载中...</p>
					) : !detail ? (
						<p className="text-muted-foreground text-xs">无法获取详情</p>
					) : detail.format === "empty" ? (
						<EmptyView detail={detail} />
					) : detail.format === "tables" ? (
						<TablesView sections={detail.sections ?? []} />
					) : detail.format === "markdown" ? (
						<MarkdownView text={detail.content ?? ""} />
					) : detail.format === "json" ? (
						<JsonView text={detail.content ?? ""} />
					) : detail.format === "jsonl" ? (
						<JsonLinesView text={detail.content ?? ""} />
					) : (
						<RawView text={detail.content ?? ""} />
					)}
				</div>
				{detail?.source_path && !loading ? (
					<footer className="flex shrink-0 items-center justify-between border-t bg-muted/30 px-5 py-2 text-[11px] text-muted-foreground">
						<span>
							{detail.format} ·{" "}
							{detail.fetched_at_ms
								? new Date(detail.fetched_at_ms).toLocaleTimeString()
								: ""}
						</span>
						<button
							type="button"
							className="inline-flex items-center gap-1 hover:text-foreground"
							onClick={() => {
								if (detail.content) {
									navigator.clipboard?.writeText(detail.content);
								}
							}}
						>
							<ExternalLinkIcon className="size-3" />
							复制全文
						</button>
					</footer>
				) : null}
			</SheetContent>
		</Sheet>
	);
}

function EmptyView({ detail }: { detail: StepDetail }) {
	return (
		<div className="space-y-3">
			<p className="rounded-md border border-dashed border-muted-foreground/30 bg-muted/30 p-3 text-muted-foreground text-xs">
				{detail.error ?? "暂无产物。"}
			</p>
			{detail.tried_paths && detail.tried_paths.length > 0 ? (
				<div>
					<div className="mb-1 text-[10px] font-medium tracking-wider text-muted-foreground uppercase">
						尝试的路径
					</div>
					<ul className="space-y-1 font-mono text-[11px]">
						{detail.tried_paths.map((p) => (
							<li key={p} className="text-muted-foreground">
								{p}
							</li>
						))}
					</ul>
				</div>
			) : null}
		</div>
	);
}

type JsonValue =
	| string
	| number
	| boolean
	| null
	| JsonValue[]
	| { [key: string]: JsonValue };

function JsonView({ text }: { text: string }) {
	const parsed = useMemo<{ ok: true; value: JsonValue } | { ok: false }>(() => {
		try {
			return { ok: true, value: JSON.parse(text) as JsonValue };
		} catch {
			return { ok: false };
		}
	}, [text]);

	if (!parsed.ok) {
		return (
			<pre className="overflow-x-auto rounded-md border bg-muted/30 p-3 font-mono text-[11px] leading-relaxed">
				{text}
			</pre>
		);
	}

	const value = parsed.value;
	if (Array.isArray(value)) {
		return (
			<div className="space-y-3">
				<div className="text-[10px] text-muted-foreground">
					{value.length} 项
				</div>
				{value.map((item, idx) => (
					<details
						key={idx}
						open={idx === 0}
						className="rounded-lg border bg-card shadow-sm"
					>
						<summary className="cursor-pointer border-b px-4 py-2 font-mono text-[11px] text-muted-foreground hover:bg-muted/30">
							#{idx}
						</summary>
						<div className="px-4 py-3">
							<JsonValueView value={item} depth={0} />
						</div>
					</details>
				))}
			</div>
		);
	}
	if (value !== null && typeof value === "object") {
		return (
			<div className="rounded-lg border bg-card shadow-sm">
				<div className="space-y-3 px-4 py-3">
					<JsonObjectView value={value as Record<string, JsonValue>} depth={0} />
				</div>
			</div>
		);
	}
	return (
		<div className="rounded-md border bg-muted/20 px-3 py-2 font-mono text-[12px]">
			<JsonPrimitive value={value} />
		</div>
	);
}

function JsonObjectView({
	value,
	depth,
}: {
	value: Record<string, JsonValue>;
	depth: number;
}) {
	const keys = Object.keys(value);
	if (keys.length === 0) {
		return (
			<span className="font-mono text-[11px] text-muted-foreground">
				{`{}`}
			</span>
		);
	}
	return (
		<>
			{keys.map((k) => (
				<Field key={k} label={k}>
					<JsonValueView value={value[k]} depth={depth + 1} />
				</Field>
			))}
		</>
	);
}

function JsonValueView({
	value,
	depth,
}: {
	value: JsonValue;
	depth: number;
}) {
	if (value === null || typeof value !== "object") {
		return <JsonPrimitive value={value} />;
	}
	if (Array.isArray(value)) {
		if (value.length === 0) {
			return (
				<span className="font-mono text-[11px] text-muted-foreground">
					[]
				</span>
			);
		}
		const allPrimitive = value.every(
			(v) => v === null || typeof v !== "object",
		);
		if (allPrimitive) {
			return (
				<div className="flex flex-wrap gap-1.5">
					{value.map((v, i) => (
						<span
							key={i}
							className="inline-flex rounded border bg-muted/30 px-1.5 py-0.5 font-mono text-[11px]"
						>
							<JsonPrimitive value={v} />
						</span>
					))}
				</div>
			);
		}
		const allObjects = value.every(
			(v) => v !== null && typeof v === "object" && !Array.isArray(v),
		);
		if (allObjects) {
			const cols = Array.from(
				new Set(
					value.flatMap((v) =>
						Object.keys(v as Record<string, JsonValue>),
					),
				),
			);
			const flat = cols.length <= 8 && depth <= 1;
			if (flat) {
				return (
					<div className="overflow-x-auto rounded-md border">
						<table className="w-full border-collapse text-[12px]">
							<thead className="bg-muted/50">
								<tr>
									{cols.map((c) => (
										<th
											key={c}
											className="border-b px-2 py-1 text-left font-medium"
										>
											{c}
										</th>
									))}
								</tr>
							</thead>
							<tbody>
								{value.map((row, i) => (
									<tr
										key={i}
										className="border-b last:border-b-0 hover:bg-muted/20"
									>
										{cols.map((c) => {
											const cell = (row as Record<string, JsonValue>)[c];
											return (
												<td
													key={c}
													className="px-2 py-1 align-top font-mono text-[11px]"
												>
													{cell === undefined ? (
														<span className="text-muted-foreground">—</span>
													) : (
														<JsonValueView value={cell} depth={depth + 2} />
													)}
												</td>
											);
										})}
									</tr>
								))}
							</tbody>
						</table>
					</div>
				);
			}
		}
		return (
			<div className="space-y-1.5">
				{value.map((v, i) => (
					<details key={i} className="rounded-md border bg-muted/20">
						<summary className="cursor-pointer px-3 py-1 font-mono text-[10px] text-muted-foreground hover:bg-muted/40">
							#{i}
						</summary>
						<div className="space-y-2 px-3 pt-1 pb-2">
							<JsonValueView value={v} depth={depth + 1} />
						</div>
					</details>
				))}
			</div>
		);
	}
	const obj = value as Record<string, JsonValue>;
	if (depth >= 1) {
		return (
			<div className="space-y-2 border-l-2 border-muted pl-3">
				<JsonObjectView value={obj} depth={depth} />
			</div>
		);
	}
	return (
		<div className="space-y-2">
			<JsonObjectView value={obj} depth={depth} />
		</div>
	);
}

function JsonPrimitive({ value }: { value: JsonValue }) {
	if (value === null) {
		return (
			<span className="font-mono text-[11px] text-muted-foreground italic">
				null
			</span>
		);
	}
	if (typeof value === "boolean") {
		return (
			<span
				className={cn(
					"font-mono text-[11px]",
					value
						? "text-emerald-600 dark:text-emerald-400"
						: "text-rose-600 dark:text-rose-400",
				)}
			>
				{String(value)}
			</span>
		);
	}
	if (typeof value === "number") {
		return (
			<span className="font-mono text-[12px] text-foreground">{value}</span>
		);
	}
	if (typeof value !== "string") return null;
	const str = value;
	if (str.includes("\n")) {
		return (
			<pre className="overflow-x-auto whitespace-pre-wrap break-words font-mono text-[11px] leading-relaxed">
				{str}
			</pre>
		);
	}
	return <span className="break-words text-foreground">{str}</span>;
}

type IterationEntry = {
	iteration?: number;
	runDic?: number;
	timestamp?: string;
	hypothesis?: unknown;
	intervention?: { type?: string; summary?: string } | string;
	prediction?: Record<string, number | string>;
	results?: Record<string, number | string>;
	verdict?: string;
	root_cause_findings?: Array<{
		pattern?: string;
		class?: string;
		cases?: number;
	}>;
	error_delta?: { persistent?: number; new?: number; fixed?: number };
	next_hypothesis?: unknown;
	[key: string]: unknown;
};

const KNOWN_KEYS = new Set([
	"iteration",
	"runDic",
	"timestamp",
	"hypothesis",
	"intervention",
	"prediction",
	"results",
	"verdict",
	"root_cause_findings",
	"error_delta",
	"next_hypothesis",
]);

function JsonLinesView({ text }: { text: string }) {
	const entries = useMemo(() => {
		return text
			.split("\n")
			.map((l) => l.trim())
			.filter(Boolean)
			.map((l) => {
				try {
					return { ok: true as const, data: JSON.parse(l) as IterationEntry };
				} catch {
					return { ok: false as const, raw: l };
				}
			});
	}, [text]);

	if (entries.length === 0) {
		return (
			<p className="text-muted-foreground text-xs">无记录。</p>
		);
	}

	return (
		<div className="space-y-3">
			<div className="text-[10px] text-muted-foreground">
				{entries.length} 条记录（最新在最后）
			</div>
			{entries.map((entry, idx) =>
				entry.ok ? (
					<IterationCard
						key={idx}
						entry={entry.data}
						defaultOpen={idx === entries.length - 1}
					/>
				) : (
					<div
						key={idx}
						className="rounded-md border border-destructive/40 bg-destructive/5 p-3 font-mono text-[11px] text-destructive"
					>
						行 #{idx + 1} 无法解析为 JSON
						<pre className="mt-1 whitespace-pre-wrap break-all text-muted-foreground">
							{entry.raw}
						</pre>
					</div>
				),
			)}
		</div>
	);
}

function IterationCard({
	entry,
	defaultOpen,
}: {
	entry: IterationEntry;
	defaultOpen: boolean;
}) {
	const intervention =
		typeof entry.intervention === "string"
			? { summary: entry.intervention }
			: (entry.intervention ?? {});
	const otherKeys = Object.keys(entry).filter((k) => !KNOWN_KEYS.has(k));

	return (
		<details
			open={defaultOpen}
			className="group rounded-lg border bg-card shadow-sm"
		>
			<summary className="flex cursor-pointer items-center gap-2 border-b px-4 py-2.5 hover:bg-muted/30">
				<span className="rounded bg-primary/10 px-2 py-0.5 font-mono text-[11px] font-semibold text-primary">
					R{entry.iteration ?? "?"}
				</span>
				{entry.runDic != null ? (
					<span className="rounded bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground">
						runDic={entry.runDic}
					</span>
				) : null}
				{entry.verdict ? <VerdictPill verdict={entry.verdict} /> : null}
				<span className="ml-auto truncate font-mono text-[10px] text-muted-foreground">
					{entry.timestamp ?? ""}
				</span>
			</summary>
			<div className="space-y-3 px-4 py-3 text-[13px]">
				{entry.hypothesis ? (
					<Field label="假设" tone="primary">
						<RichText value={entry.hypothesis} />
					</Field>
				) : null}
				{intervention.summary || intervention.type ? (
					<Field label="干预">
						<div className="flex flex-wrap items-center gap-2">
							{intervention.type ? (
								<span className="rounded bg-amber-500/15 px-2 py-0.5 font-mono text-[11px] text-amber-700 dark:text-amber-300">
									{intervention.type}
								</span>
							) : null}
							{intervention.summary ? (
								<span className="text-foreground">{intervention.summary}</span>
							) : null}
						</div>
					</Field>
				) : null}
				{entry.prediction || entry.results ? (
					<MetricsCompare
						prediction={entry.prediction}
						results={entry.results}
					/>
				) : null}
				{entry.error_delta ? (
					<Field label="错误增量">
						<div className="flex flex-wrap gap-2">
							<DeltaPill
								label="persistent"
								value={entry.error_delta.persistent}
								tone="muted"
							/>
							<DeltaPill
								label="new"
								value={entry.error_delta.new}
								tone="rose"
							/>
							<DeltaPill
								label="fixed"
								value={entry.error_delta.fixed}
								tone="emerald"
							/>
						</div>
					</Field>
				) : null}
				{entry.root_cause_findings && entry.root_cause_findings.length > 0 ? (
					<Field label="根因发现">
						<div className="overflow-x-auto rounded-md border">
							<table className="w-full border-collapse text-[12px]">
								<thead className="bg-muted/50">
									<tr>
										<th className="border-b px-2 py-1 text-left font-medium">
											pattern
										</th>
										<th className="border-b px-2 py-1 text-left font-medium">
											class
										</th>
										<th className="border-b px-2 py-1 text-right font-medium">
											cases
										</th>
									</tr>
								</thead>
								<tbody>
									{entry.root_cause_findings.map((f, i) => (
										<tr
											key={i}
											className="border-b last:border-b-0 hover:bg-muted/20"
										>
											<td className="px-2 py-1">{f.pattern ?? "—"}</td>
											<td className="px-2 py-1">
												{f.class ? (
													<span className="rounded bg-muted px-1.5 py-0.5 font-mono text-[10px]">
														{f.class}
													</span>
												) : (
													"—"
												)}
											</td>
											<td className="px-2 py-1 text-right font-mono">
												{f.cases ?? "—"}
											</td>
										</tr>
									))}
								</tbody>
							</table>
						</div>
					</Field>
				) : null}
				{entry.next_hypothesis ? (
					<Field label="下一步假设" tone="accent">
						<RichText value={entry.next_hypothesis} />
					</Field>
				) : null}
				{otherKeys.length > 0 ? (
					<details className="rounded-md border bg-muted/20">
						<summary className="cursor-pointer px-3 py-1.5 text-[11px] text-muted-foreground hover:bg-muted/40">
							其他字段（{otherKeys.length}）
						</summary>
						<pre className="overflow-x-auto px-3 pt-0 pb-2 font-mono text-[11px] leading-relaxed">
							{JSON.stringify(
								Object.fromEntries(otherKeys.map((k) => [k, entry[k]])),
								null,
								2,
							)}
						</pre>
					</details>
				) : null}
			</div>
		</details>
	);
}

function RichText({ value }: { value: unknown }) {
	if (value == null) return null;
	if (typeof value === "string" || typeof value === "number") {
		return <>{String(value)}</>;
	}
	return (
		<pre className="overflow-x-auto whitespace-pre-wrap break-words font-mono text-[11px] leading-relaxed">
			{JSON.stringify(value, null, 2)}
		</pre>
	);
}

function Field({
	label,
	children,
	tone,
}: {
	label: string;
	children: React.ReactNode;
	tone?: "primary" | "accent";
}) {
	return (
		<div>
			<div className="mb-1 text-[10px] font-medium uppercase tracking-wider text-muted-foreground">
				{label}
			</div>
			<div
				className={cn(
					"rounded-md border bg-muted/20 px-3 py-2 leading-relaxed",
					tone === "primary" && "border-primary/30 bg-primary/5",
					tone === "accent" && "border-amber-500/30 bg-amber-500/5",
				)}
			>
				{children}
			</div>
		</div>
	);
}

function VerdictPill({ verdict }: { verdict: string }) {
	const map: Record<string, string> = {
		pass: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
		partial: "bg-amber-500/15 text-amber-700 dark:text-amber-300",
		miss: "bg-rose-500/15 text-rose-700 dark:text-rose-300",
		replication: "bg-sky-500/15 text-sky-700 dark:text-sky-300",
	};
	return (
		<span
			className={cn(
				"rounded-full px-2 py-0.5 font-medium text-[10px] tracking-wider",
				map[verdict.toLowerCase()] ?? "bg-muted text-muted-foreground",
			)}
		>
			{verdict.toUpperCase()}
		</span>
	);
}

function DeltaPill({
	label,
	value,
	tone,
}: {
	label: string;
	value: number | undefined;
	tone: "rose" | "emerald" | "muted";
}) {
	if (value == null) return null;
	const toneMap: Record<typeof tone, string> = {
		rose: "bg-rose-500/15 text-rose-700 dark:text-rose-300",
		emerald: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
		muted: "bg-muted text-muted-foreground",
	};
	return (
		<span
			className={cn(
				"inline-flex items-center gap-1 rounded-full px-2 py-0.5 font-mono text-[11px]",
				toneMap[tone],
			)}
		>
			<span className="opacity-70">{label}</span>
			<span className="font-semibold">{value}</span>
		</span>
	);
}

function MetricsCompare({
	prediction,
	results,
}: {
	prediction?: Record<string, number | string>;
	results?: Record<string, number | string>;
}) {
	const keys = useMemo(() => {
		const set = new Set<string>();
		Object.keys(prediction ?? {}).forEach((k) => set.add(k));
		Object.keys(results ?? {}).forEach((k) => set.add(k));
		return Array.from(set);
	}, [prediction, results]);

	if (keys.length === 0) return null;

	return (
		<Field label="指标">
			<div className="overflow-x-auto rounded-md border">
				<table className="w-full border-collapse text-[12px]">
					<thead className="bg-muted/50">
						<tr>
							<th className="border-b px-2 py-1 text-left font-medium">
								metric
							</th>
							<th className="border-b px-2 py-1 text-right font-medium">
								prediction
							</th>
							<th className="border-b px-2 py-1 text-right font-medium">
								result
							</th>
							<th className="border-b px-2 py-1 text-right font-medium">
								Δ
							</th>
						</tr>
					</thead>
					<tbody>
						{keys.map((k) => {
							const p = prediction?.[k];
							const r = results?.[k];
							const pn = typeof p === "number" ? p : Number(p);
							const rn = typeof r === "number" ? r : Number(r);
							const delta =
								Number.isFinite(pn) && Number.isFinite(rn)
									? rn - pn
									: null;
							const deltaTone =
								delta == null
									? ""
									: delta > 0
										? "text-emerald-600 dark:text-emerald-400"
										: delta < 0
											? "text-rose-600 dark:text-rose-400"
											: "text-muted-foreground";
							return (
								<tr
									key={k}
									className="border-b last:border-b-0 hover:bg-muted/20"
								>
									<td className="px-2 py-1 font-mono text-[11px]">{k}</td>
									<td className="px-2 py-1 text-right font-mono">
										{p ?? "—"}
									</td>
									<td className="px-2 py-1 text-right font-mono font-semibold">
										{r ?? "—"}
									</td>
									<td
										className={cn(
											"px-2 py-1 text-right font-mono",
											deltaTone,
										)}
									>
										{delta == null
											? "—"
											: `${delta > 0 ? "+" : ""}${delta.toFixed(2)}`}
									</td>
								</tr>
							);
						})}
					</tbody>
				</table>
			</div>
		</Field>
	);
}

const PROSE_CLASSES = cn(
	"prose prose-sm max-w-none dark:prose-invert",
	"prose-headings:scroll-m-20 prose-headings:font-semibold",
	"prose-h1:mt-4 prose-h1:mb-2 prose-h1:text-lg",
	"prose-h2:mt-4 prose-h2:mb-2 prose-h2:text-base prose-h2:border-b prose-h2:border-border prose-h2:pb-1",
	"prose-h3:mt-3 prose-h3:mb-1.5 prose-h3:text-sm",
	"prose-p:my-2 prose-p:leading-relaxed",
	"prose-a:text-primary prose-a:no-underline hover:prose-a:underline",
	"prose-strong:text-foreground prose-strong:font-semibold",
	"prose-blockquote:my-2 prose-blockquote:border-l-4 prose-blockquote:border-muted-foreground/30 prose-blockquote:pl-3 prose-blockquote:text-muted-foreground prose-blockquote:not-italic",
	"prose-ul:my-2 prose-ol:my-2 prose-li:my-0.5",
	"prose-hr:my-3 prose-hr:border-border",
	"prose-table:my-3 prose-table:text-xs",
	"prose-thead:bg-muted prose-th:px-2 prose-th:py-1 prose-th:font-semibold prose-th:text-left",
	"prose-td:px-2 prose-td:py-1 prose-tr:border-b prose-tr:border-border",
	"prose-code:rounded prose-code:bg-muted prose-code:px-1.5 prose-code:py-0.5 prose-code:text-[0.85em] prose-code:before:content-none prose-code:after:content-none",
	"prose-pre:my-2 prose-pre:rounded-md prose-pre:border prose-pre:border-border prose-pre:bg-muted/40 prose-pre:p-3 prose-pre:text-[12px] prose-pre:text-foreground",
);

type MarkdownSection = { title: string | null; body: string };

const HIDDEN_MD_SECTION_PATTERNS: RegExp[] = [
	/^(?:\d+(?:\.\d+)*\s+)?根因归类/,
	/^(?:\d+(?:\.\d+)*\s+)?下一轮假设候选/,
	/^(?:\d+(?:\.\d+)*\s+)?error_registry\s+baseline/i,
];

function isHiddenHeading(title: string): boolean {
	const trimmed = title.trim();
	return HIDDEN_MD_SECTION_PATTERNS.some((re) => re.test(trimmed));
}

function stripHiddenSections(text: string): string {
	const lines = text.split("\n");
	const out: string[] = [];
	let skipUntilLevel: number | null = null;
	for (const line of lines) {
		const m = line.match(/^(#{1,6})\s+(.+?)\s*$/);
		if (m) {
			const level = m[1].length;
			const title = m[2];
			if (skipUntilLevel !== null && level <= skipUntilLevel) {
				skipUntilLevel = null;
			}
			if (skipUntilLevel === null && isHiddenHeading(title)) {
				skipUntilLevel = level;
				continue;
			}
		}
		if (skipUntilLevel !== null) continue;
		out.push(line);
	}
	return out.join("\n").replace(/\n{3,}/g, "\n\n").trimEnd();
}

function splitMarkdownSections(text: string): MarkdownSection[] {
	const sections: MarkdownSection[] = [];
	let current: MarkdownSection = { title: null, body: "" };
	const lines = text.split("\n");
	for (const line of lines) {
		const m = line.match(/^##\s+(.+?)\s*$/);
		if (m) {
			if (current.title !== null || current.body.trim().length > 0) {
				sections.push(current);
			}
			current = { title: m[1], body: "" };
		} else {
			current.body += (current.body ? "\n" : "") + line;
		}
	}
	if (current.title !== null || current.body.trim().length > 0) {
		sections.push(current);
	}
	return sections;
}

function MarkdownView({ text }: { text: string }) {
	const sections = useMemo(() => {
		const stripped = stripHiddenSections(text);
		return splitMarkdownSections(stripped).filter(
			(s) => s.title === null || !isHiddenHeading(s.title),
		);
	}, [text]);

	if (sections.length === 0) {
		return (
			<p className="text-muted-foreground text-xs">空内容。</p>
		);
	}

	return (
		<div className="space-y-3">
			{sections.map((section, idx) =>
				section.title === null ? (
					<div
						key={idx}
						className="rounded-lg border bg-card px-4 py-3 shadow-sm"
					>
						<article className={PROSE_CLASSES}>
							<ReactMarkdown remarkPlugins={[remarkGfm]}>
								{section.body}
							</ReactMarkdown>
						</article>
					</div>
				) : (
					<details
						key={idx}
						open={idx <= 1}
						className="group rounded-lg border bg-card shadow-sm"
					>
						<summary className="flex cursor-pointer items-center gap-2 border-b px-4 py-2.5 hover:bg-muted/30">
							<span className="font-semibold text-foreground text-sm">
								{section.title}
							</span>
							<span className="ml-auto text-[10px] text-muted-foreground transition-transform group-open:rotate-180">
								▾
							</span>
						</summary>
						<div className="px-4 py-3">
							<article className={PROSE_CLASSES}>
								<ReactMarkdown remarkPlugins={[remarkGfm]}>
									{section.body}
								</ReactMarkdown>
							</article>
						</div>
					</details>
				),
			)}
		</div>
	);
}

function TablesView({ sections }: { sections: TableSection[] }) {
	if (sections.length === 0) {
		return <p className="text-muted-foreground text-xs">空内容。</p>;
	}
	return (
		<div className="space-y-3">
			{sections.map((section, idx) => (
				<TableSectionCard
					key={`${section.title}-${idx}`}
					section={section}
					defaultOpen={idx <= 1}
				/>
			))}
		</div>
	);
}

function TableSectionCard({
	section,
	defaultOpen,
}: {
	section: TableSection;
	defaultOpen: boolean;
}) {
	const isMissing = section.kind === "missing" || section.kind === "error";
	const isMarkdown = section.kind === "markdown";
	const truncated =
		section.shown_rows > 0 && section.shown_rows < section.total_rows;
	const noteParts: string[] = [];
	if (section.total_rows > 0) {
		noteParts.push(`共 ${section.total_rows} 行`);
		if (truncated) {
			noteParts.push(`仅显示前 ${section.shown_rows} 行`);
		}
	}
	if (section.kind === "csv") noteParts.push("CSV");
	else if (section.kind === "jsonl") noteParts.push("JSONL");
	else if (section.kind === "text") noteParts.push("TEXT");
	else if (section.kind === "markdown") noteParts.push("MARKDOWN");

	return (
		<details
			open={defaultOpen && !isMissing}
			className="group rounded-lg border bg-card shadow-sm"
		>
			<summary className="flex cursor-pointer items-center gap-2 border-b px-4 py-2.5 hover:bg-muted/30">
				<span className="font-semibold text-foreground text-sm">
					{section.title}
				</span>
				{noteParts.length > 0 ? (
					<span className="rounded bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground">
						{noteParts.join(" · ")}
					</span>
				) : null}
				<span className="ml-auto text-[10px] text-muted-foreground transition-transform group-open:rotate-180">
					▾
				</span>
			</summary>
			<div className="space-y-2 px-4 py-3">
				<div className="flex items-center gap-1 truncate font-mono text-[11px] text-muted-foreground">
					<FileIcon className="size-3 shrink-0" />
					<span className="truncate">{section.source_path}</span>
				</div>
				{isMissing ? (
					<p className="rounded-md border border-dashed border-muted-foreground/30 bg-muted/30 px-3 py-2 text-muted-foreground text-xs">
						{section.error ?? "产物文件不存在或读取失败"}
					</p>
				) : isMarkdown ? (
					<MarkdownView text={section.body ?? ""} />
				) : section.rows.length === 0 ? (
					<p className="text-muted-foreground text-xs">空表。</p>
				) : (
					<DataTable columns={section.columns} rows={section.rows} />
				)}
			</div>
		</details>
	);
}

function DataTable({
	columns,
	rows,
}: {
	columns: string[];
	rows: string[][];
}) {
	return (
		<div className="max-h-[60vh] overflow-auto rounded-md border">
			<table className="w-full border-collapse text-[12px]">
				<thead className="sticky top-0 z-10 bg-muted/80 backdrop-blur supports-[backdrop-filter]:bg-muted/60">
					<tr>
						<th className="border-b border-r px-2 py-1 text-left font-medium text-[10px] text-muted-foreground tracking-wider w-10">
							#
						</th>
						{columns.map((c) => (
							<th
								key={c}
								className="border-b border-r last:border-r-0 px-2 py-1 text-left font-medium whitespace-nowrap"
							>
								{c}
							</th>
						))}
					</tr>
				</thead>
				<tbody>
					{rows.map((row, rIdx) => (
						<tr
							key={rIdx}
							className="border-b last:border-b-0 hover:bg-muted/20"
						>
							<td className="border-r px-2 py-1 align-top font-mono text-[10px] text-muted-foreground tabular-nums">
								{rIdx + 1}
							</td>
							{columns.map((c, cIdx) => {
								const cell = row[cIdx] ?? "";
								return (
									<td
										key={c}
										className="border-r last:border-r-0 px-2 py-1 align-top font-mono text-[11px]"
									>
										{cell === "" ? (
											<span className="text-muted-foreground">—</span>
										) : (
											<TableCell text={cell} />
										)}
									</td>
								);
							})}
						</tr>
					))}
				</tbody>
			</table>
		</div>
	);
}

function TableCell({ text }: { text: string }) {
	if (text.includes("\n")) {
		return (
			<pre className="whitespace-pre-wrap break-words font-mono text-[11px] leading-snug">
				{text}
			</pre>
		);
	}
	return <span className="break-words whitespace-pre-wrap">{text}</span>;
}

function RawView({ text }: { text: string }) {
	return (
		<pre className="overflow-x-auto whitespace-pre-wrap break-all font-mono text-[11px] leading-relaxed">
			{text}
		</pre>
	);
}

function StatusPill({ status }: { status: string }) {
	const map: Record<string, string> = {
		complete: "bg-emerald-500/15 text-emerald-700 dark:text-emerald-300",
		running: "bg-sky-500/20 text-sky-700 dark:text-sky-200",
		pending: "bg-muted text-muted-foreground",
		failed: "bg-rose-500/15 text-rose-700 dark:text-rose-300",
		cancelled: "bg-muted text-muted-foreground",
	};
	return (
		<span
			className={cn(
				"rounded-full px-2 py-0.5 text-[10px] font-medium tracking-wider",
				map[status] ?? "bg-muted text-muted-foreground",
			)}
		>
			{status.toUpperCase()}
		</span>
	);
}
