"use client";

import type {
	MessageState,
	ThreadAssistantMessagePart,
	ToolCallMessagePart,
	ToolCallMessagePartStatus,
} from "@assistant-ui/react";
import { useAuiState } from "@assistant-ui/react";
import {
	BrainIcon,
	CheckCircle2Icon,
	ChevronDownIcon,
	ClockIcon,
	FileTextIcon,
	PanelRightCloseIcon,
	WrenchIcon,
	XCircleIcon,
} from "lucide-react";
import {
	createContext,
	type ReactNode,
	useCallback,
	useContext,
	useEffect,
	useMemo,
	useRef,
	useState,
} from "react";
import { Button } from "@/components/ui/button";
import {
	Collapsible,
	CollapsibleContent,
	CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { cn } from "@/lib/utils";

type ActivityContextValue = {
	open: boolean;
	selectedId: string | null;
	openItem: (id: string) => void;
	close: () => void;
};

const ActivityContext = createContext<ActivityContextValue | null>(null);

export function ActivityProvider({ children }: { children: ReactNode }) {
	const [open, setOpen] = useState(false);
	const [selectedId, setSelectedId] = useState<string | null>(null);
	const openItem = useCallback((id: string) => {
		setSelectedId(id);
		setOpen(true);
	}, []);
	const close = useCallback(() => setOpen(false), []);
	const value = useMemo(
		() => ({
			open,
			selectedId,
			openItem,
			close,
		}),
		[open, selectedId, openItem, close],
	);

	return (
		<ActivityContext.Provider value={value}>
			{children}
		</ActivityContext.Provider>
	);
}

export function useActivityPanel() {
	const value = useContext(ActivityContext);
	if (!value) {
		throw new Error("useActivityPanel must be used within ActivityProvider");
	}
	return value;
}

type ActivityItem = {
	id: string;
	kind: "reasoning" | "tool";
	title: string;
	summary: string;
	status: ToolCallMessagePartStatus["type"];
	argsText?: string;
	result?: unknown;
	resultSummary?: string;
	rawResult?: string;
	fileLinks?: string[];
};

export function ActivityPanel() {
	const { open, selectedId, openItem, close } = useActivityPanel();
	const messages = useAuiState((s) => s.thread.messages);
	const items = useMemo(() => collectActivityItems(messages), [messages]);
	const selectedMessageId = selectedId ? activityMessageId(selectedId) : null;
	const visibleItems = selectedMessageId
		? items.filter((item) => activityMessageId(item.id) === selectedMessageId)
		: items;
	const prevCountRef = useRef(0);

	useEffect(() => {
		if (items.length > prevCountRef.current) {
			openItem(items.at(-1)?.id ?? items[0]?.id ?? "");
		}
		prevCountRef.current = items.length;
	}, [items, openItem]);

	if (!open) return null;

	return (
		<aside className="fixed inset-y-0 right-0 z-30 flex w-[min(23rem,calc(100vw-1rem))] flex-col border-l bg-background shadow-xl lg:static lg:z-auto lg:h-full lg:w-92 lg:shrink-0 lg:shadow-none">
			<header className="flex h-16 shrink-0 items-center justify-between border-b px-4">
				<div className="min-w-0">
					<h2 className="font-semibold text-base">活动</h2>
					<p className="text-muted-foreground text-xs">
						{visibleItems.length > 0
							? `${visibleItems.length} 个步骤`
							: "暂无链路"}
					</p>
				</div>
				<Button
					type="button"
					variant="ghost"
					size="icon"
					className="size-8"
					onClick={close}
					aria-label="关闭活动面板"
				>
					<PanelRightCloseIcon className="size-4" />
				</Button>
			</header>
			<div className="min-h-0 flex-1 overflow-y-auto px-4 py-4">
				{visibleItems.length === 0 ? (
					<p className="text-muted-foreground text-sm">
						当前这轮对话还没有工具链路。
					</p>
				) : (
					<div className="flex flex-col gap-2">
						{visibleItems.map((item) => (
							<ActivityPanelItem
								key={item.id}
								item={item}
								open={selectedId === item.id}
								onOpenChange={(nextOpen) => {
									if (nextOpen) openItem(item.id);
								}}
							/>
						))}
					</div>
				)}
			</div>
		</aside>
	);
}

function activityMessageId(activityId: string) {
	return activityId.split(":", 1)[0] ?? activityId;
}

function ActivityPanelItem({
	item,
	open,
	onOpenChange,
}: {
	item: ActivityItem;
	open: boolean;
	onOpenChange: (open: boolean) => void;
}) {
	const Icon = item.kind === "tool" ? WrenchIcon : BrainIcon;

	return (
		<Collapsible
			open={open}
			onOpenChange={onOpenChange}
			className="rounded-lg border bg-card text-card-foreground"
		>
			<CollapsibleTrigger className="flex w-full items-start gap-3 px-3 py-3 text-left">
				<ActivityStatusIcon status={item.status} />
				<div className="min-w-0 flex-1">
					<div className="flex items-center gap-2">
						<Icon className="size-3.5 shrink-0 text-muted-foreground" />
						<span className="truncate font-medium text-sm">{item.title}</span>
					</div>
					<p className="mt-1 line-clamp-2 text-muted-foreground text-xs">
						{item.summary}
					</p>
				</div>
				<ChevronDownIcon
					className={cn(
						"mt-0.5 size-4 shrink-0 text-muted-foreground transition-transform",
						!open && "-rotate-90",
					)}
				/>
			</CollapsibleTrigger>
			<CollapsibleContent>
				<div className="flex flex-col gap-3 border-t px-3 py-3 text-xs">
					{item.argsText ? (
						<ActivityPre title="输入参数" value={item.argsText} />
					) : null}
					{item.resultSummary ? (
						<ActivityResultSummary value={item.resultSummary} />
					) : null}
					{item.fileLinks?.length ? (
						<ActivityFileLinks paths={item.fileLinks} />
					) : null}
					{item.rawResult ? (
						<Collapsible>
							<CollapsibleTrigger className="flex items-center gap-1 font-medium text-muted-foreground transition-colors hover:text-foreground">
								<ChevronDownIcon className="size-3" />
								原始结果
							</CollapsibleTrigger>
							<CollapsibleContent>
								<ActivityPre title="" value={item.rawResult} className="mt-2" />
							</CollapsibleContent>
						</Collapsible>
					) : null}
					{item.kind === "reasoning" ? (
						<p className="text-muted-foreground">
							这里展示的是思考状态摘要，不展示模型完整隐藏思考过程。
						</p>
					) : null}
				</div>
			</CollapsibleContent>
		</Collapsible>
	);
}

function ActivityStatusIcon({ status }: { status: ActivityItem["status"] }) {
	if (status === "running") {
		return <ClockIcon className="mt-0.5 size-4 shrink-0 animate-pulse" />;
	}
	if (status === "incomplete") {
		return <XCircleIcon className="mt-0.5 size-4 shrink-0 text-destructive" />;
	}
	return (
		<CheckCircle2Icon className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
	);
}

function ActivityPre({
	title,
	value,
	className,
}: {
	title: string;
	value: string;
	className?: string;
}) {
	return (
		<div className={className}>
			{title ? (
				<p className="mb-1 font-medium text-muted-foreground">{title}</p>
			) : null}
			<pre className="max-h-80 overflow-auto rounded-md bg-muted px-3 py-2 whitespace-pre-wrap">
				{value}
			</pre>
		</div>
	);
}

function ActivityResultSummary({ value }: { value: string }) {
	return (
		<div className="rounded-md border bg-muted/30 px-3 py-2">
			<div className="mb-1 flex items-center gap-1.5 font-medium text-muted-foreground">
				<FileTextIcon className="size-3.5" />
				结果摘要
			</div>
			<p className="whitespace-pre-wrap">{value}</p>
		</div>
	);
}

function collectActivityItems(messages: readonly MessageState[]) {
	const items: ActivityItem[] = [];

	for (const message of messages) {
		if (message.role !== "assistant") continue;
		const parts = message.content as readonly ThreadAssistantMessagePart[];
		for (const [index, part] of parts.entries()) {
			const id = `${message.id}:${index}`;
			if (part.type === "reasoning") {
				const status = getPartStatus(message, part);
				items.push({
					id,
					kind: "reasoning",
					title: status === "running" ? "思考中" : "思考完成",
					summary:
						part.text.trim() ||
						(status === "running"
							? "模型正在整理下一步行动。"
							: "思考并执行完成。"),
					status,
				});
			}
			if (part.type === "tool-call") {
				items.push({
					id,
					kind: "tool",
					title: part.toolName,
					summary: summarizeTool(message, part),
					status: getPartStatus(message, part),
					argsText: part.argsText,
					result: decodeJsonString(part.result),
					resultSummary: summarizeToolResult(decodeJsonString(part.result)),
					rawResult:
						part.result === undefined
							? undefined
							: formatValue(decodeJsonString(part.result)),
					fileLinks: extractLocalPaths(
						[
							part.argsText,
							part.result === undefined
								? undefined
								: formatValue(decodeJsonString(part.result)),
						].filter(Boolean) as string[],
					),
				});
			}
		}
	}

	return items;
}

function ActivityFileLinks({ paths }: { paths: string[] }) {
	return (
		<div className="rounded-md border bg-muted/30 px-3 py-2">
			<div className="mb-1 flex items-center gap-1.5 font-medium text-muted-foreground">
				<FileTextIcon className="size-3.5" />
				相关文件
			</div>
			<div className="flex flex-col gap-1">
				{paths.map((filePath) => (
					<a
						key={filePath}
						href={`/api/claw/files?path=${encodeURIComponent(filePath)}`}
						className="truncate text-primary hover:underline"
						target="_blank"
						rel="noreferrer"
						title={filePath}
					>
						{basename(filePath)}
					</a>
				))}
			</div>
		</div>
	);
}

function getPartStatus(
	message: Extract<MessageState, { role: "assistant" }>,
	part: ThreadAssistantMessagePart,
): ToolCallMessagePartStatus["type"] {
	if (message.status.type === "incomplete") return "incomplete";
	if (part.type === "tool-call" && part.result === undefined) return "running";
	if (message.status.type === "running") return "running";
	return "complete";
}

function summarizeTool(
	message: Extract<MessageState, { role: "assistant" }>,
	part: ToolCallMessagePart,
) {
	const status = getPartStatus(message, part);
	if (status === "running") return "调用工具中";
	if (status === "incomplete") return "工具调用未完成";
	if (part.result === undefined) return "已提交工具参数";
	return "工具调用完成";
}

function decodeJsonString(value: unknown): unknown {
	if (typeof value !== "string") return value;
	try {
		return JSON.parse(value);
	} catch {
		return value;
	}
}

function formatValue(value: unknown) {
	const decoded = decodeJsonString(value);
	return typeof decoded === "string"
		? decoded
		: JSON.stringify(decoded, null, 2);
}

function summarizeToolResult(value: unknown) {
	const decoded = decodeJsonString(value);
	if (decoded === undefined) return undefined;
	if (typeof decoded === "string") return trimText(decoded, 600);
	if (!decoded || typeof decoded !== "object") return String(decoded);

	const record = decoded as Record<string, unknown>;
	const parts: string[] = [];
	if (typeof record.tool === "string") parts.push(`工具: ${record.tool}`);
	if (typeof record.ok === "boolean")
		parts.push(record.ok ? "状态: 成功" : "状态: 失败");
	if (typeof record.error === "string") parts.push(`错误: ${record.error}`);
	if (typeof record.content === "string")
		parts.push(trimText(record.content, 600));
	if (!parts.length) parts.push(trimText(JSON.stringify(record, null, 2), 600));
	return parts.join("\n");
}

function trimText(text: string, maxLength: number) {
	const normalized = text.trim();
	if (normalized.length <= maxLength) return normalized;
	return `${normalized.slice(0, maxLength)}...`;
}

function extractLocalPaths(values: string[]) {
	const paths = new Set<string>();
	for (const value of values) {
		for (const match of value.matchAll(/\/Users\/[^\s"',)]+/g)) {
			paths.add(match[0]);
		}
	}
	return [...paths];
}

function basename(filePath: string) {
	return filePath.split("/").filter(Boolean).at(-1) ?? filePath;
}
