import {
	bindExternalStoreMessage,
	type ExportedMessageRepository,
	type ThreadMessage,
} from "@assistant-ui/core";
import {
	AuiIf,
	ThreadListItemMorePrimitive,
	ThreadListItemPrimitive,
	ThreadListPrimitive,
} from "@assistant-ui/react";
import type { UIMessage } from "ai";
import {
	ArchiveIcon,
	HistoryIcon,
	MoreHorizontalIcon,
	PlusIcon,
} from "lucide-react";
import { type FC, useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Skeleton } from "@/components/ui/skeleton";
import { useClawSessionReplay } from "@/lib/claw-session-replay";

type ClawSession = {
	session_id: string;
	turns: number;
	tool_calls: number;
	preview: string;
	modified_at: number;
};

type ClawStoredMessage = {
	role?: string;
	content?: string;
	name?: string;
	tool_call_id?: string;
	tool_calls?: ClawStoredToolCall[];
	metadata?: {
		elapsed_ms?: unknown;
	};
};

type ClawStoredSession = {
	session_id: string;
	messages?: ClawStoredMessage[];
};

type ClawStoredToolCall = {
	id?: string;
	name?: string;
	arguments?: unknown;
	function?: {
		name?: string;
		arguments?: unknown;
	};
};

export const ThreadList: FC = () => {
	return (
		<ThreadListPrimitive.Root className="aui-root aui-thread-list-root flex flex-col gap-1">
			<ThreadListNew />
			<AuiIf condition={({ threads }) => threads.isLoading}>
				<ThreadListSkeleton />
			</AuiIf>
			<AuiIf condition={({ threads }) => !threads.isLoading}>
				<ThreadListPrimitive.Items>
					{() => <ThreadListItem />}
				</ThreadListPrimitive.Items>
			</AuiIf>
			<ClawSessionList />
		</ThreadListPrimitive.Root>
	);
};

const ThreadListNew: FC = () => {
	return (
		<div
			onClickCapture={() => {
				window.localStorage.removeItem("claw.activeSessionId");
				window.dispatchEvent(new Event("claw-active-session-cleared"));
			}}
		>
			<ThreadListPrimitive.New asChild>
				<Button
					variant="outline"
					className="aui-thread-list-new h-9 w-full justify-start gap-2 rounded-lg px-3 text-sm hover:bg-muted data-active:bg-muted"
				>
					<PlusIcon className="size-4" />
					New Thread
				</Button>
			</ThreadListPrimitive.New>
		</div>
	);
};

const ThreadListSkeleton: FC = () => {
	const skeletonKeys = ["one", "two", "three", "four", "five"];

	return (
		<div className="flex flex-col gap-1">
			{skeletonKeys.map((key) => (
				<div
					key={key}
					role="status"
					aria-label="Loading threads"
					className="aui-thread-list-skeleton-wrapper flex h-9 items-center px-3"
				>
					<Skeleton className="aui-thread-list-skeleton h-4 w-full" />
				</div>
			))}
		</div>
	);
};

const ThreadListItem: FC = () => {
	return (
		<ThreadListItemPrimitive.Root className="aui-thread-list-item group flex h-9 items-center gap-2 rounded-lg transition-colors hover:bg-muted focus-visible:bg-muted focus-visible:outline-none data-active:bg-muted">
			<ThreadListItemPrimitive.Trigger className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center truncate px-3 text-start text-sm">
				<ThreadListItemPrimitive.Title fallback="New Chat" />
			</ThreadListItemPrimitive.Trigger>
			<ThreadListItemMore />
		</ThreadListItemPrimitive.Root>
	);
};

const ThreadListItemMore: FC = () => {
	return (
		<ThreadListItemMorePrimitive.Root>
			<ThreadListItemMorePrimitive.Trigger asChild>
				<Button
					variant="ghost"
					size="icon"
					className="aui-thread-list-item-more mr-2 size-7 p-0 opacity-0 transition-opacity group-hover:opacity-100 data-[state=open]:bg-accent data-[state=open]:opacity-100 group-data-active:opacity-100"
				>
					<MoreHorizontalIcon className="size-4" />
					<span className="sr-only">More options</span>
				</Button>
			</ThreadListItemMorePrimitive.Trigger>
			<ThreadListItemMorePrimitive.Content
				side="bottom"
				align="start"
				className="aui-thread-list-item-more-content z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
			>
				<ThreadListItemPrimitive.Archive asChild>
					<ThreadListItemMorePrimitive.Item className="aui-thread-list-item-more-item flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground">
						<ArchiveIcon className="size-4" />
						Archive
					</ThreadListItemMorePrimitive.Item>
				</ThreadListItemPrimitive.Archive>
			</ThreadListItemMorePrimitive.Content>
		</ThreadListItemMorePrimitive.Root>
	);
};

const ClawSessionList: FC = () => {
	const { replaySession } = useClawSessionReplay();
	const [sessions, setSessions] = useState<ClawSession[]>([]);
	const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
	const [loadingSessionId, setLoadingSessionId] = useState<string | null>(null);

	useEffect(() => {
		setActiveSessionId(window.localStorage.getItem("claw.activeSessionId"));
		const clearActiveSession = () => setActiveSessionId(null);
		window.addEventListener("claw-active-session-cleared", clearActiveSession);
		fetch("/api/claw/sessions")
			.then((res) => (res.ok ? res.json() : []))
			.then((payload) => {
				if (Array.isArray(payload))
					setSessions(dedupeSessions(payload).slice(0, 8));
			})
			.catch(() => setSessions([]));
		return () => {
			window.removeEventListener(
				"claw-active-session-cleared",
				clearActiveSession,
			);
		};
	}, []);

	if (!sessions.length) return null;

	return (
		<div className="mt-4 border-t pt-3">
			<div className="mb-2 flex items-center justify-between gap-2 px-3">
				<div className="flex items-center gap-2 font-medium text-muted-foreground text-xs">
					<HistoryIcon className="size-3.5" />
					Claw 历史会话
				</div>
				{activeSessionId ? (
					<button
						type="button"
						className="text-muted-foreground text-xs hover:text-foreground"
						onClick={() => {
							window.localStorage.removeItem("claw.activeSessionId");
							setActiveSessionId(null);
						}}
					>
						取消续接
					</button>
				) : null}
			</div>
			<div className="flex flex-col gap-1">
				{sessions.map((session) => {
					const active = activeSessionId === session.session_id;
					return (
						<button
							key={`claw-session-${session.session_id}-${session.modified_at}`}
							type="button"
							className="rounded-lg px-3 py-2 text-left text-sm transition-colors hover:bg-muted data-[active=true]:bg-muted"
							data-active={active}
							onClick={async () => {
								window.localStorage.setItem(
									"claw.activeSessionId",
									session.session_id,
								);
								setActiveSessionId(session.session_id);
								setLoadingSessionId(session.session_id);
								try {
									const response = await fetch(
										`/api/claw/sessions/${session.session_id}`,
									);
									if (!response.ok) return;
									const payload = (await response.json()) as ClawStoredSession;
									replaySession(
										session.session_id,
										toReplayRepository(payload, session.session_id),
									);
								} finally {
									setLoadingSessionId(null);
								}
							}}
						>
							<span className="block truncate">
								{session.preview || session.session_id}
							</span>
							<span className="mt-0.5 block truncate text-muted-foreground text-xs">
								{active ? "下一条消息将续接此会话 · " : ""}
								{loadingSessionId === session.session_id ? "回放中 · " : ""}
								{session.turns} 轮 · {session.tool_calls} 次工具
							</span>
						</button>
					);
				})}
			</div>
		</div>
	);
};

function dedupeSessions(payload: unknown[]) {
	const byId = new Map<string, ClawSession>();
	for (const item of payload) {
		if (!isClawSession(item)) continue;
		const previous = byId.get(item.session_id);
		if (!previous || item.modified_at >= previous.modified_at) {
			byId.set(item.session_id, item);
		}
	}
	return [...byId.values()].sort((a, b) => b.modified_at - a.modified_at);
}

function isClawSession(value: unknown): value is ClawSession {
	if (!value || typeof value !== "object") return false;
	const item = value as Partial<ClawSession>;
	return (
		typeof item.session_id === "string" && typeof item.modified_at === "number"
	);
}

function toReplayRepository(
	session: ClawStoredSession,
	fallbackSessionId: string,
): ExportedMessageRepository {
	const messages: ExportedMessageRepository["messages"] = [];
	const toolResults = collectToolResults(session.messages ?? []);
	let previousId: string | null = null;
	for (const [index, message] of (session.messages ?? []).entries()) {
		if (message.role === "tool") continue;
		if (message.role !== "user" && message.role !== "assistant") continue;
		const content = cleanStoredContent(message.content ?? "");
		if (!content) continue;
		if (content.trimStart().startsWith("<system-reminder>")) continue;
		const id = `${fallbackSessionId}-${index}`;
		const toolParts =
			message.role === "assistant"
				? toToolParts(message.tool_calls ?? [], toolResults)
				: [];
		const elapsedMs =
			typeof message.metadata?.elapsed_ms === "number"
				? message.metadata.elapsed_ms
				: undefined;
		const reasoningParts =
			message.role === "assistant" &&
			(toolParts.length > 0 || elapsedMs !== undefined)
				? [
						{
							type: "reasoning",
							text: formatHistoricalReasoning(toolParts.length, elapsedMs),
						},
					]
				: [];
		const parts = (
			message.role === "assistant" &&
			(reasoningParts.length || toolParts.length)
				? [...reasoningParts, ...toolParts, { type: "text", text: content }]
				: [{ type: "text", text: content }]
		) as UIMessage["parts"];
		const uiMessage: UIMessage = {
			id,
			role: message.role,
			parts,
			...(message.role === "assistant"
				? { metadata: { sessionId: session.session_id ?? fallbackSessionId } }
				: {}),
		} as UIMessage;
		const threadMessage = {
			id,
			role: message.role,
			createdAt: new Date(),
			content: toThreadMessageContent(parts),
			...(message.role === "assistant"
				? {
						status: { type: "complete", reason: "stop" },
						metadata: {
							unstable_state: null,
							unstable_annotations: [],
							unstable_data: [],
							steps: [],
							custom: { sessionId: session.session_id ?? fallbackSessionId },
						},
					}
				: { metadata: { custom: {} } }),
		} as ThreadMessage;
		bindExternalStoreMessage(threadMessage, uiMessage);
		messages.push({ message: threadMessage, parentId: previousId });
		previousId = id;
	}
	return {
		headId: previousId,
		messages,
	};
}

function cleanStoredContent(content: string) {
	const markers = ["\n\n[当前会话目录]", "\n[当前会话目录]"];
	for (const marker of markers) {
		if (content.includes(marker)) return content.split(marker, 1)[0].trim();
	}
	return content.trim();
}

function collectToolResults(messages: readonly ClawStoredMessage[]) {
	const results = new Map<string, string>();
	for (const message of messages) {
		if (message.role === "tool" && message.tool_call_id) {
			results.set(message.tool_call_id, message.content ?? "");
		}
	}
	return results;
}

function toToolParts(
	toolCalls: readonly ClawStoredToolCall[],
	toolResults: Map<string, string>,
) {
	return toolCalls.flatMap((call) => {
		const toolCallId = call.id;
		const toolName = call.function?.name ?? call.name;
		if (!toolCallId || !toolName) return [];
		const input = parseToolInput(call.function?.arguments ?? call.arguments);
		const output = toolResults.get(toolCallId);
		return [
			{
				type: "dynamic-tool",
				toolName,
				toolCallId,
				state: output === undefined ? "input-available" : "output-available",
				input,
				...(output === undefined ? {} : { output }),
			},
		];
	});
}

function formatHistoricalReasoning(toolCount: number, elapsedMs?: number) {
	const duration =
		elapsedMs === undefined ? "" : `，用时 ${formatDuration(elapsedMs)}`;
	if (toolCount <= 0) return `历史记录：思考完成${duration}。`;
	return `历史记录：本轮调用了 ${toolCount} 个工具${duration}。`;
}

function formatDuration(ms: number) {
	if (ms < 1000) return `${ms}ms`;
	return `${(ms / 1000).toFixed(1)}s`;
}

function toThreadMessageContent(parts: UIMessage["parts"]) {
	return parts.map((part) => {
		if (part.type === "text" || part.type === "reasoning") return part;
		if (part.type === "dynamic-tool") {
			return {
				type: "tool-call",
				toolName: part.toolName,
				toolCallId: part.toolCallId,
				args: part.input ?? {},
				argsText: JSON.stringify(part.input ?? {}),
				...(part.state === "output-available" ? { result: part.output } : {}),
			};
		}
		return part;
	});
}

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