import {
	bindExternalStoreMessage,
	type ExportedMessageRepository,
	type ThreadMessage,
} from "@assistant-ui/core";
import { ThreadListPrimitive } from "@assistant-ui/react";
import type { UIMessage } from "ai";
import {
	MoreHorizontalIcon,
	PencilIcon,
	PlusIcon,
	Trash2Icon,
} from "lucide-react";
import {
	type FC,
	type KeyboardEvent,
	useEffect,
	useRef,
	useState,
} from "react";
import { Button } from "@/components/ui/button";
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 />
			<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 Task
				</Button>
			</ThreadListPrimitive.New>
		</div>
	);
};

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);
	const [deletingSessionId, setDeletingSessionId] = useState<string | null>(
		null,
	);
	const [renamingSessionId, setRenamingSessionId] = useState<string | null>(
		null,
	);
	const [draftTitle, setDraftTitle] = useState("");
	const [openMenuSessionId, setOpenMenuSessionId] = useState<string | null>(
		null,
	);
	const renameInputRef = useRef<HTMLInputElement | null>(null);

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

	useEffect(() => {
		if (!renamingSessionId) return;
		window.requestAnimationFrame(() => {
			renameInputRef.current?.focus();
			renameInputRef.current?.select();
		});
	}, [renamingSessionId]);

	if (!sessions.length) return null;

	const saveTitle = async (sessionId: string, fallbackTitle: string) => {
		const normalized = draftTitle.trim();
		if (!normalized || normalized === fallbackTitle) {
			setRenamingSessionId(null);
			setDraftTitle("");
			return;
		}
		const response = await fetch(`/api/claw/sessions/${sessionId}`, {
			method: "PATCH",
			headers: { "content-type": "application/json" },
			body: JSON.stringify({ title: normalized }),
		});
		if (!response.ok) return;
		const payload = (await response.json()) as { title?: string };
		const nextTitle = payload.title || normalized;
		setSessions((current) =>
			current.map((item) =>
				item.session_id === sessionId ? { ...item, preview: nextTitle } : item,
			),
		);
		setRenamingSessionId(null);
		setDraftTitle("");
	};

	return (
		<div className="flex flex-col gap-1">
			{sessions.map((session) => {
				const active = activeSessionId === session.session_id;
				const loading = loadingSessionId === session.session_id;
				return (
					<div
						key={`claw-session-${session.session_id}-${session.modified_at}`}
						className="aui-thread-list-item group relative flex h-9 items-center gap-2 rounded-lg transition-colors hover:bg-muted data-[active=true]:bg-muted"
						data-active={active}
					>
						{renamingSessionId === session.session_id ? (
							<div className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center px-3">
								<input
									ref={renameInputRef}
									type="text"
									className="h-7 min-w-0 flex-1 rounded-md border bg-background px-2 text-sm outline-none focus-visible:ring-1 focus-visible:ring-ring"
									value={draftTitle}
									onChange={(event) => setDraftTitle(event.target.value)}
									onBlur={() => {
										saveTitle(
											session.session_id,
											session.preview || session.session_id,
										);
									}}
									onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
										if (event.key === "Enter") {
											event.preventDefault();
											event.currentTarget.blur();
										}
										if (event.key === "Escape") {
											event.preventDefault();
											setRenamingSessionId(null);
											setDraftTitle("");
										}
									}}
								/>
							</div>
						) : (
							<button
								type="button"
								className="aui-thread-list-item-trigger flex h-full min-w-0 flex-1 items-center truncate px-3 text-start text-sm"
								onClick={async () => {
									setOpenMenuSessionId(null);
									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="truncate">
									{loading
										? "回放中..."
										: session.preview || session.session_id}
								</span>
							</button>
						)}
						<button
							type="button"
							className="aui-thread-list-item-more mr-2 flex size-7 shrink-0 items-center justify-center rounded-md p-0 text-muted-foreground opacity-0 transition-opacity hover:bg-accent hover:text-foreground group-hover:opacity-100 data-[busy=true]:opacity-100 data-[open=true]:bg-accent data-[open=true]:opacity-100"
							data-busy={deletingSessionId === session.session_id}
							data-open={openMenuSessionId === session.session_id}
							aria-expanded={openMenuSessionId === session.session_id}
							aria-label="打开历史会话菜单"
							title="更多选项"
							onClick={() => {
								setOpenMenuSessionId((current) =>
									current === session.session_id ? null : session.session_id,
								);
							}}
						>
							<MoreHorizontalIcon className="size-4" />
						</button>
						{openMenuSessionId === session.session_id ? (
							<div className="aui-thread-list-item-more-content absolute top-8 right-1 z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md">
								<button
									type="button"
									className="aui-thread-list-item-more-item flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm outline-none hover:bg-accent focus:bg-accent"
									disabled={renamingSessionId === session.session_id}
									onClick={() => {
										setRenamingSessionId(session.session_id);
										setDraftTitle(session.preview || session.session_id);
										setOpenMenuSessionId(null);
									}}
								>
									<PencilIcon className="size-4" />
									重命名
								</button>
								<button
									type="button"
									className="aui-thread-list-item-more-item flex w-full cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-destructive outline-none hover:bg-accent focus:bg-accent"
									disabled={deletingSessionId === session.session_id}
									onClick={async () => {
										setDeletingSessionId(session.session_id);
										try {
											const response = await fetch(
												`/api/claw/sessions/${session.session_id}`,
												{ method: "DELETE" },
											);
											if (!response.ok) return;
											setSessions((current) =>
												current.filter(
													(item) => item.session_id !== session.session_id,
												),
											);
											setOpenMenuSessionId(null);
											if (activeSessionId === session.session_id) {
												window.localStorage.removeItem("claw.activeSessionId");
												setActiveSessionId(null);
												window.dispatchEvent(
													new Event("claw-active-session-cleared"),
												);
											}
										} finally {
											setDeletingSessionId(null);
										}
									}}
								>
									<Trash2Icon className="size-4" />
									删除
								</button>
							</div>
						) : null}
					</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"
	);
}

export function toReplayRepository(
	session: ClawStoredSession,
	fallbackSessionId: string,
): ExportedMessageRepository {
	const messages: ExportedMessageRepository["messages"] = [];
	const toolResults = collectToolResults(session.messages ?? []);
	const lastAssistantContentIndex = findLastAssistantContentIndex(
		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, content)
				: [];
		const shouldShowAssistantText =
			message.role !== "assistant" ||
			!toolParts.length ||
			index === lastAssistantContentIndex;
		const parts = (
			message.role === "assistant" && toolParts.length
				? shouldShowAssistantText
					? [...toolParts, { type: "text", text: content }]
					: toolParts
				: [{ 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 findLastAssistantContentIndex(messages: readonly ClawStoredMessage[]) {
	for (let index = messages.length - 1; index >= 0; index -= 1) {
		const message = messages[index];
		if (message?.role !== "assistant") continue;
		const content = cleanStoredContent(message.content ?? "");
		if (!content) continue;
		return index;
	}
	return -1;
}

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

function attachStageNote(input: unknown, stageNote?: string) {
	const note = stageNote?.trim();
	if (!note) return input;
	if (input && typeof input === "object" && !Array.isArray(input)) {
		return { ...input, __claw_stage_note: note };
	}
	return { value: input, __claw_stage_note: note };
}

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;
	}
}
