import { API, watchWorkspace } from "api/api";

import { isApiError } from "api/errors";
import {
	chat,
	chatMessagesForInfiniteScroll,
	createChatMessage,
	deleteChatQueuedMessage,
	editChatMessage,
	interruptChat,
	promoteChatQueuedMessage,
} from "api/queries/chats";
import { deploymentSSHConfig } from "api/queries/deployment";
import { workspaceById, workspaceByIdKey } from "api/queries/workspaces";
import type * as TypesGen from "api/typesGenerated";
import type { ChatMessagePart } from "api/typesGenerated";
import { useProxy } from "contexts/ProxyContext";
import {
	getTerminalHref,
	getVSCodeHref,
	openAppInNewWindow,
} from "modules/apps/apps";
import { type FC, useEffect, useLayoutEffect, useRef, useState } from "react";
import {
	useInfiniteQuery,
	useMutation,
	useQuery,
	useQueryClient,
} from "react-query";
import { useNavigate, useOutletContext, useParams } from "react-router";
import { toast } from "sonner";
import type { UrlTransform } from "streamdown";
import { isMobileViewport } from "utils/mobile";
import { pageTitle } from "utils/page";
import { portForwardURL } from "utils/portForward";
import type { ChatMessageInputRef } from "./AgentChatInput";
import { useChatStore } from "./AgentDetail/ChatContext";
import { getParentChatID, getWorkspaceAgent } from "./AgentDetail/chatHelpers";
import { useWorkspaceCreationWatcher } from "./AgentDetail/useWorkspaceCreationWatcher";
import {
	AgentDetailLoadingView,
	AgentDetailNotFoundView,
	AgentDetailView,
} from "./AgentDetailView";
import type { AgentsOutletContext } from "./AgentsPage";
import {
	getModelCatalogStatusMessage,
	getModelSelectorPlaceholder,
	hasConfiguredModelsInCatalog,
} from "./modelOptions";
import { parsePullRequestUrl } from "./pullRequest";
import { formatUsageLimitMessage, isUsageLimitData } from "./usageLimitMessage";
import { useGitWatcher } from "./useGitWatcher";

/** localStorage key controlling whether the right panel is visible. */
export const RIGHT_PANEL_OPEN_KEY = "agents.right-panel-open";

const localHosts = new Set(["localhost", "127.0.0.1", "0.0.0.0"]);

const lastModelConfigIDStorageKey = "agents.last-model-config-id";
/** @internal Exported for testing. */
export const draftInputStorageKeyPrefix = "agents.draft-input.";

/** @internal Exported for testing. */
export function useConversationEditingState(deps: {
	chatID: string | undefined;
	onSend: (
		message: string,
		fileIds?: string[],
		editedMessageID?: number,
	) => Promise<void>;
	onDeleteQueuedMessage: (id: number) => Promise<void>;
	chatInputRef: React.RefObject<ChatMessageInputRef | null>;
	inputValueRef: React.RefObject<string>;
}) {
	const { chatID, onSend, onDeleteQueuedMessage, chatInputRef, inputValueRef } =
		deps;
	const draftStorageKey = chatID
		? `${draftInputStorageKeyPrefix}${chatID}`
		: null;
	const [editorInitialValue, setEditorInitialValue] = useState(() => {
		if (typeof window === "undefined" || !draftStorageKey) {
			return "";
		}
		return localStorage.getItem(draftStorageKey) ?? "";
	});

	// Sync the ref with the initial draft value so callers that
	// read inputValueRef.current see the persisted draft. Uses a
	// layout effect so the value is available before paint.
	const initialSyncDone = useRef(false);
	useLayoutEffect(() => {
		if (!initialSyncDone.current && editorInitialValue) {
			initialSyncDone.current = true;
			(inputValueRef as React.MutableRefObject<string>).current =
				editorInitialValue;
		}
	}, [editorInitialValue, inputValueRef]);

	// -- History editing state --
	const [editingMessageId, setEditingMessageId] = useState<number | null>(null);
	const [draftBeforeHistoryEdit, setDraftBeforeHistoryEdit] = useState<
		string | null
	>(null);
	const [editingFileBlocks, setEditingFileBlocks] = useState<
		readonly ChatMessagePart[]
	>([]);

	const handleEditUserMessage = (
		messageId: number,
		text: string,
		fileBlocks?: readonly ChatMessagePart[],
	) => {
		setDraftBeforeHistoryEdit((prev) =>
			editingMessageId !== null ? prev : inputValueRef.current,
		);
		setEditingMessageId(messageId);
		setEditorInitialValue(text);
		inputValueRef.current = text;
		setEditingFileBlocks(fileBlocks ?? []);
	};

	const handleCancelHistoryEdit = () => {
		setEditorInitialValue(draftBeforeHistoryEdit ?? "");
		inputValueRef.current = draftBeforeHistoryEdit ?? "";
		setEditingMessageId(null);
		setDraftBeforeHistoryEdit(null);
		setEditingFileBlocks([]);
		chatInputRef.current?.clear();
		if (draftBeforeHistoryEdit) {
			chatInputRef.current?.insertText(draftBeforeHistoryEdit);
		}
	};

	// -- Queue editing state --
	const [editingQueuedMessageID, setEditingQueuedMessageID] = useState<
		number | null
	>(null);
	const [draftBeforeQueueEdit, setDraftBeforeQueueEdit] = useState<
		string | null
	>(null);

	const handleStartQueueEdit = (
		id: number,
		text: string,
		fileBlocks: readonly ChatMessagePart[],
	) => {
		setDraftBeforeQueueEdit((prev) =>
			editingQueuedMessageID === null ? inputValueRef.current : prev,
		);
		setEditingQueuedMessageID(id);
		setEditorInitialValue(text);
		inputValueRef.current = text;
		setEditingFileBlocks(fileBlocks);
	};

	const handleCancelQueueEdit = () => {
		setEditorInitialValue(draftBeforeQueueEdit ?? "");
		inputValueRef.current = draftBeforeQueueEdit ?? "";
		setEditingQueuedMessageID(null);
		setDraftBeforeQueueEdit(null);
		setEditingFileBlocks([]);
	};

	// Wraps the parent onSend to clear local input/editing state
	// and handle queue-edit deletion.
	const handleSendFromInput = async (message: string, fileIds?: string[]) => {
		const editedMessageID =
			editingMessageId !== null ? editingMessageId : undefined;
		const queueEditID = editingQueuedMessageID;

		await onSend(message, fileIds, editedMessageID);
		// Clear input and editing state on success.
		chatInputRef.current?.clear();
		if (!isMobileViewport()) {
			chatInputRef.current?.focus();
		}
		inputValueRef.current = "";
		if (typeof window !== "undefined" && draftStorageKey) {
			localStorage.removeItem(draftStorageKey);
		}
		if (editingMessageId !== null) {
			setEditingMessageId(null);
			setDraftBeforeHistoryEdit(null);
			setEditingFileBlocks([]);
		}
		if (queueEditID !== null) {
			setEditingQueuedMessageID(null);
			setDraftBeforeQueueEdit(null);
			setEditingFileBlocks([]);
			void onDeleteQueuedMessage(queueEditID);
		}
	};

	const handleContentChange = (content: string) => {
		inputValueRef.current = content;
		if (typeof window !== "undefined" && draftStorageKey) {
			if (content) {
				localStorage.setItem(draftStorageKey, content);
			} else {
				localStorage.removeItem(draftStorageKey);
			}
		}
	};

	return {
		inputValueRef,
		chatInputRef,
		editorInitialValue,
		editingMessageId,
		editingFileBlocks,
		handleEditUserMessage,
		handleCancelHistoryEdit,
		editingQueuedMessageID,
		handleStartQueueEdit,
		handleCancelQueueEdit,
		handleSendFromInput,
		handleContentChange,
	};
}

const AgentDetail: FC = () => {
	const navigate = useNavigate();
	const { agentId } = useParams<{ agentId: string }>();
	const outletContext = useOutletContext<AgentsOutletContext>();
	const queryClient = useQueryClient();
	const [selectedModel, setSelectedModel] = useState("");
	const [pendingEditMessageId, setPendingEditMessageId] = useState<
		number | null
	>(null);
	const {
		chatErrorReasons,
		setChatErrorReason,
		clearChatErrorReason,
		requestArchiveAgent,
		requestArchiveAndDeleteWorkspace,
		requestUnarchiveAgent,
		onOpenAnalytics,
		isSidebarCollapsed,
		onToggleSidebarCollapsed,
		modelOptions,
		modelConfigIDByModelID,
		modelIDByConfigID,
		modelConfigs,
		modelCatalog,
		isModelCatalogLoading,
		modelCatalogError,
		desktopEnabled,
	} = outletContext;
	const scrollContainerRef = useRef<HTMLDivElement | null>(null);
	const chatInputRef = useRef<ChatMessageInputRef | null>(null);
	const inputValueRef = useRef(
		typeof window !== "undefined" && agentId
			? (localStorage.getItem(`${draftInputStorageKeyPrefix}${agentId}`) ?? "")
			: "",
	);

	// Right panel open/closed state is owned here so the loading
	// skeleton and the loaded view share the same layout, preventing
	// a horizontal shift when data arrives.
	const [showSidebarPanel, setShowSidebarPanel] = useState(() => {
		if (typeof window === "undefined") return false;
		return localStorage.getItem(RIGHT_PANEL_OPEN_KEY) === "true";
	});
	const handleSetShowSidebarPanel = (
		next: boolean | ((prev: boolean) => boolean),
	) => {
		setShowSidebarPanel((prev) => {
			const value = typeof next === "function" ? next(prev) : next;
			if (typeof window !== "undefined") {
				localStorage.setItem(RIGHT_PANEL_OPEN_KEY, String(value));
			}
			return value;
		});
	};

	const chatQuery = useQuery({
		...chat(agentId ?? ""),
		enabled: Boolean(agentId),
	});
	const chatMessagesQuery = useInfiniteQuery({
		...chatMessagesForInfiniteScroll(agentId ?? ""),
		enabled: Boolean(agentId),
	});
	const parentChatID = getParentChatID(chatQuery.data);
	const parentChatQuery = useQuery({
		...chat(parentChatID ?? ""),
		enabled: Boolean(parentChatID),
	});
	const workspaceId = chatQuery.data?.workspace_id;
	const workspaceQuery = useQuery({
		...workspaceById(workspaceId ?? ""),
		enabled: Boolean(workspaceId),
	});

	// Subscribe to live workspace updates so that agent status changes
	// (e.g. connected/disconnected) are reflected without a page refresh.
	useEffect(() => {
		if (!workspaceId) {
			return;
		}
		const socket = watchWorkspace(workspaceId);
		socket.addEventListener("message", (event) => {
			if (event.parseError) {
				return;
			}
			if (event.parsedMessage.type === "data") {
				const next = event.parsedMessage.data as TypesGen.Workspace;
				queryClient.setQueryData<TypesGen.Workspace | undefined>(
					workspaceByIdKey(workspaceId),
					(prev) => {
						// Return the same reference when nothing the UI
						// reads has changed. This prevents react-query
						// from notifying subscribers and avoids a full
						// AgentDetail re-render on every heartbeat.
						if (
							prev &&
							prev.latest_build.status === next.latest_build.status &&
							prev.latest_build.resources === next.latest_build.resources &&
							prev.name === next.name &&
							prev.owner_name === next.owner_name
						) {
							return prev;
						}
						return next;
					},
				);
			}
		});
		return () => socket.close();
	}, [workspaceId, queryClient]);
	const sshConfigQuery = useQuery(deploymentSSHConfig());
	const workspace = workspaceQuery.data;
	const workspaceAgent = getWorkspaceAgent(workspace, undefined);
	const { proxy } = useProxy();

	// Extract the primitive fields used by the transform so the
	// compiler can see the real dependencies and avoid invalidating
	// the closure when the workspace object reference changes but
	// the relevant fields haven't.
	const proxyHost = proxy.preferredWildcardHostname;
	const agentName = workspaceAgent?.name;
	const wsName = workspace?.name;
	const wsOwner = workspace?.owner_name;

	const urlTransform: UrlTransform = (url) => {
		if (!proxyHost || !agentName || !wsName || !wsOwner) {
			return url;
		}
		try {
			const parsed = new URL(url);
			if (!localHosts.has(parsed.hostname)) {
				return url;
			}
			return portForwardURL(
				proxyHost,
				Number.parseInt(parsed.port, 10),
				agentName,
				wsName,
				wsOwner,
				"http",
				parsed.pathname,
				parsed.search,
			);
		} catch {
			return url;
		}
	};

	const chatRecord = chatQuery.data;
	// Flatten paginated messages into chronological order.
	// Pages arrive newest-first per page, and pages[0] is the
	// most recent page.
	const chatMessagesList = (() => {
		const pages = chatMessagesQuery.data?.pages;
		if (!pages || pages.length === 0) return undefined;
		// Collect all messages, then sort chronologically by ID.
		const all = pages.flatMap((p) => p.messages);
		// Sort ascending by ID for chronological order.
		all.sort((a, b) => a.id - b.id);
		return all;
	})();

	// Queued messages are only in the first page (most recent).
	const chatQueuedMessages = chatMessagesQuery.data?.pages[0]?.queued_messages;

	// Build a synthetic ChatMessagesResponse from the flattened
	// data for backward compat with useChatStore.
	const chatMessagesData: TypesGen.ChatMessagesResponse | undefined =
		chatMessagesList
			? {
					messages: chatMessagesList,
					queued_messages: chatQueuedMessages ?? [],
					has_more: chatMessagesQuery.data?.pages.at(-1)?.has_more ?? false,
				}
			: undefined;
	const isArchived = chatRecord?.archived ?? false;
	const chatLastModelConfigID = chatRecord?.last_model_config_id;

	const sendMutation = useMutation(
		createChatMessage(queryClient, agentId ?? ""),
	);
	const editMutation = useMutation(editChatMessage(queryClient, agentId ?? ""));
	const interruptMutation = useMutation(
		interruptChat(queryClient, agentId ?? ""),
	);
	const deleteQueuedMutation = useMutation(
		deleteChatQueuedMessage(queryClient, agentId ?? ""),
	);
	const promoteQueuedMutation = useMutation(
		promoteChatQueuedMessage(queryClient, agentId ?? ""),
	);

	const { store, clearStreamError } = useChatStore({
		chatID: agentId,
		chatMessages: chatMessagesList,
		chatRecord,
		chatMessagesData,
		chatQueuedMessages,
		setChatErrorReason,
		clearChatErrorReason,
	});

	// Git watcher: runs regardless of sidebar visibility, but only
	// connects when the workspace agent is in the "connected" state
	// to avoid an infinite reconnect loop against a missing agent.
	const gitWatcher = useGitWatcher({
		chatId: agentId,
		agentStatus: workspaceAgent?.status,
	});

	// Detect workspace creation so the sidebar can resolve the
	// workspace and display agent/git info.
	useWorkspaceCreationWatcher({
		store,
		chatID: agentId,
	});

	const handleCommit = (repoRoot: string) => {
		const commitPrompt = `Commit and push the working changes in ${repoRoot}. If there are unstaged files, commit them too.`;
		const current = inputValueRef.current;
		if (current.includes(commitPrompt)) {
			return;
		}
		const prefix = current.trim() ? "\n\n" : "";
		chatInputRef.current?.insertText(prefix + commitPrompt);
		chatInputRef.current?.focus();
	};

	// Prefer the explicit PR number from the API, and only fall back to URL
	// parsing when older metadata does not provide it.
	const parsedPrNumber = Number(
		parsePullRequestUrl(chatQuery.data?.diff_status?.url)?.number,
	);
	const prNumber =
		chatQuery.data?.diff_status?.pr_number ?? (parsedPrNumber || undefined);
	// Compute an effective selected model by validating the user's
	// explicit choice against the current model options, falling
	// back to the chat's last model or the first available option.
	const effectiveSelectedModel = (() => {
		if (
			selectedModel &&
			modelOptions.some((model) => model.id === selectedModel)
		) {
			return selectedModel;
		}
		if (chatLastModelConfigID) {
			const fromChat = modelIDByConfigID.get(chatLastModelConfigID);
			if (fromChat && modelOptions.some((model) => model.id === fromChat)) {
				return fromChat;
			}
		}
		return modelOptions[0]?.id ?? "";
	})();

	const compressionThreshold = chatLastModelConfigID
		? modelConfigs.find((c) => c.id === chatLastModelConfigID)
				?.compression_threshold
		: undefined;
	const hasModelOptions = modelOptions.length > 0;
	const hasConfiguredModels = hasConfiguredModelsInCatalog(modelCatalog);
	const modelSelectorPlaceholder = getModelSelectorPlaceholder(
		modelOptions,
		isModelCatalogLoading,
		hasConfiguredModels,
	);
	const modelCatalogStatusMessage = getModelCatalogStatusMessage(
		modelCatalog,
		modelOptions,
		isModelCatalogLoading,
		Boolean(modelCatalogError),
	);
	const inputStatusText = hasModelOptions
		? null
		: hasConfiguredModels
			? "Models are configured but unavailable. Ask an admin."
			: "No models configured. Ask an admin.";
	const isSubmissionPending =
		sendMutation.isPending ||
		editMutation.isPending ||
		interruptMutation.isPending;
	const isInputDisabled = !hasModelOptions || isArchived;

	const handleUsageLimitError = (error: unknown): void => {
		if (!agentId) {
			return;
		}
		if (
			isApiError(error) &&
			error.response?.status === 409 &&
			isUsageLimitData(error.response.data)
		) {
			setChatErrorReason(agentId, {
				kind: "usage-limit",
				message: formatUsageLimitMessage(error.response.data),
			});
		} else if (isApiError(error)) {
			setChatErrorReason(agentId, {
				kind: "generic",
				message: error.message || "An unexpected error occurred.",
			});
		}
	};

	const handleSend = async (
		message: string,
		fileIds?: string[],
		editedMessageID?: number,
	) => {
		const chatInputHandle = (
			editing.chatInputRef as React.RefObject<ChatMessageInputRef | null>
		)?.current;

		// Walk the Lexical tree in document order so file-reference
		// parts appear at the correct position relative to the
		// surrounding text the user typed.
		const editorParts = chatInputHandle?.getContentParts() ?? [];
		const hasFileReferences = editorParts.some(
			(p) => p.type === "file-reference",
		);
		const hasContent =
			message.trim() || (fileIds && fileIds.length > 0) || hasFileReferences;
		if (!hasContent || isSubmissionPending || !agentId || !hasModelOptions) {
			return;
		}

		const content: TypesGen.ChatInputPart[] = [];

		// Emit parts in document order — text segments and
		// file-reference chips are interleaved as they appear in
		// the editor.
		for (const part of editorParts) {
			if (part.type === "text") {
				const trimmed = part.text.trim();
				if (trimmed) {
					content.push({ type: "text", text: part.text });
				}
			} else {
				const r = part.reference;
				content.push({
					type: "file-reference",
					file_name: r.fileName,
					start_line: r.startLine,
					end_line: r.endLine,
					content: r.content,
				});
			}
		}

		// Add pre-uploaded file references.
		if (fileIds && fileIds.length > 0) {
			for (const fileId of fileIds) {
				content.push({ type: "file", file_id: fileId });
			}
		}
		if (editedMessageID !== undefined) {
			const request: TypesGen.EditChatMessageRequest = { content };
			clearChatErrorReason(agentId);
			clearStreamError();
			setPendingEditMessageId(editedMessageID);
			if (scrollContainerRef.current) {
				scrollContainerRef.current.scrollTop = 0;
			}
			store.clearStreamState();
			try {
				await editMutation.mutateAsync({
					messageId: editedMessageID,
					req: request,
				});
				setPendingEditMessageId(null);
			} catch (error) {
				setPendingEditMessageId(null);
				handleUsageLimitError(error);
				throw error;
			}
			return;
		}
		const selectedModelConfigID =
			(effectiveSelectedModel &&
				modelConfigIDByModelID.get(effectiveSelectedModel)) ||
			undefined;
		const request: TypesGen.CreateChatMessageRequest = {
			content,
			model_config_id: selectedModelConfigID,
		};
		clearChatErrorReason(agentId);
		clearStreamError();
		if (scrollContainerRef.current) {
			scrollContainerRef.current.scrollTop = 0;
		}

		// No optimistic rendering — the message will appear in the
		// timeline when the server confirms via the POST response or
		// via the SSE stream.
		store.clearStreamState();
		let response: Awaited<ReturnType<typeof sendMutation.mutateAsync>>;
		try {
			response = await sendMutation.mutateAsync(request);
		} catch (error) {
			handleUsageLimitError(error);
			throw error;
		}
		// When the server accepts the message immediately (not
		// queued), insert it into the store so it appears in the
		// timeline without waiting for the SSE stream.
		if (!response.queued && response.message) {
			store.upsertDurableMessage(response.message);
		}
		if (typeof window !== "undefined") {
			if (selectedModelConfigID) {
				localStorage.setItem(
					lastModelConfigIDStorageKey,
					selectedModelConfigID,
				);
			} else {
				localStorage.removeItem(lastModelConfigIDStorageKey);
			}
		}
	};

	const handleInterrupt = () => {
		if (!agentId || interruptMutation.isPending) {
			return;
		}
		void interruptMutation.mutateAsync();
	};

	const handleDeleteQueuedMessage = async (id: number) => {
		const previousQueuedMessages = store.getSnapshot().queuedMessages;
		store.setQueuedMessages(
			previousQueuedMessages.filter((message) => message.id !== id),
		);
		try {
			await deleteQueuedMutation.mutateAsync(id);
		} catch (error) {
			store.setQueuedMessages(previousQueuedMessages);
			throw error;
		}
	};

	const handlePromoteQueuedMessage = async (id: number) => {
		const previousSnapshot = store.getSnapshot();
		const previousQueuedMessages = previousSnapshot.queuedMessages;
		const previousChatStatus = previousSnapshot.chatStatus;
		store.setQueuedMessages(
			previousQueuedMessages.filter((message) => message.id !== id),
		);
		store.clearStreamState();
		if (agentId) {
			clearChatErrorReason(agentId);
		}
		store.clearStreamError();
		store.setChatStatus("pending");
		try {
			const promotedMessage = await promoteQueuedMutation.mutateAsync(id);
			// Insert the promoted message into the store immediately
			// so it appears in the timeline without waiting for the
			// WebSocket to deliver it.
			store.upsertDurableMessage(promotedMessage);
		} catch (error) {
			store.setQueuedMessages(previousQueuedMessages);
			store.setChatStatus(previousChatStatus);
			handleUsageLimitError(error);
			throw error;
		}
	};

	const editing = useConversationEditingState({
		chatID: agentId,
		onSend: handleSend,
		onDeleteQueuedMessage: handleDeleteQueuedMessage,
		chatInputRef,
		inputValueRef,
	});

	const chatTitle = chatQuery.data?.title;

	const titleElement = (
		<title>
			{chatTitle ? pageTitle(chatTitle, "Agents") : pageTitle("Agents")}
		</title>
	);

	const parentChat = parentChatQuery.data;
	const workspaceRoute = workspace
		? `/@${workspace.owner_name}/${workspace.name}`
		: null;
	const canOpenWorkspace = Boolean(workspaceRoute);
	const canOpenEditors = Boolean(workspace && workspaceAgent);
	const terminalHref =
		workspace && workspaceAgent
			? getTerminalHref({
					username: workspace.owner_name,
					workspace: workspace.name,
					agent: workspaceAgent.name,
				})
			: null;
	const sshCommand =
		workspace && workspaceAgent && sshConfigQuery.data?.hostname_suffix
			? `ssh ${workspaceAgent.name}.${workspace.name}.${workspace.owner_name}.${sshConfigQuery.data.hostname_suffix}`
			: undefined;

	const generateKeyMutation = useMutation({
		mutationFn: () => API.getApiKey(),
	});

	const handleOpenInEditor = (editor: "cursor" | "vscode") => {
		if (!workspace || !workspaceAgent) {
			return;
		}

		generateKeyMutation.mutate(undefined, {
			onSuccess: ({ key }) => {
				location.href = getVSCodeHref(editor, {
					owner: workspace.owner_name,
					workspace: workspace.name,
					token: key,
					agent: workspaceAgent.name,
					folder: workspaceAgent.expanded_directory,
					chatId: agentId,
				});
			},
			onError: () => {
				toast.error(
					editor === "cursor"
						? "Failed to open in Cursor."
						: "Failed to open in VS Code.",
				);
			},
		});
	};

	const handleViewWorkspace = () => {
		if (!workspaceRoute) {
			return;
		}
		window.open(workspaceRoute, "_blank");
	};

	const handleOpenTerminal = () => {
		if (!terminalHref) {
			return;
		}
		openAppInNewWindow(terminalHref);
	};

	const handleArchiveAgentAction = () => {
		if (!agentId || isArchived) {
			return;
		}
		requestArchiveAgent(agentId);
	};

	const handleArchiveAndDeleteWorkspaceAction = () => {
		if (!agentId || isArchived || !workspaceId) {
			return;
		}
		requestArchiveAndDeleteWorkspace(agentId, workspaceId);
	};

	const handleUnarchiveAgentAction = () => {
		if (!agentId || !isArchived) {
			return;
		}
		requestUnarchiveAgent(agentId);
	};

	if (chatQuery.isLoading || chatMessagesQuery.isLoading) {
		return (
			<AgentDetailLoadingView
				titleElement={titleElement}
				isInputDisabled={isInputDisabled}
				effectiveSelectedModel={effectiveSelectedModel}
				setSelectedModel={setSelectedModel}
				modelOptions={modelOptions}
				modelSelectorPlaceholder={modelSelectorPlaceholder}
				hasModelOptions={hasModelOptions}
				inputStatusText={inputStatusText}
				modelCatalogStatusMessage={modelCatalogStatusMessage}
				isSidebarCollapsed={isSidebarCollapsed}
				onToggleSidebarCollapsed={onToggleSidebarCollapsed}
				showRightPanel={showSidebarPanel}
			/>
		);
	}

	if (!chatQuery.data || !chatMessagesQuery.data?.pages?.length || !agentId) {
		return (
			<AgentDetailNotFoundView
				titleElement={titleElement}
				isSidebarCollapsed={isSidebarCollapsed}
				onToggleSidebarCollapsed={onToggleSidebarCollapsed}
			/>
		);
	}

	return (
		<AgentDetailView
			agentId={agentId}
			chatTitle={chatTitle}
			parentChat={parentChat}
			chatErrorReasons={chatErrorReasons}
			chatRecord={chatRecord}
			isArchived={isArchived}
			hasWorkspace={Boolean(workspaceId)}
			store={store}
			editing={editing}
			pendingEditMessageId={pendingEditMessageId}
			effectiveSelectedModel={effectiveSelectedModel}
			setSelectedModel={setSelectedModel}
			modelOptions={modelOptions}
			modelSelectorPlaceholder={modelSelectorPlaceholder}
			hasModelOptions={hasModelOptions}
			inputStatusText={inputStatusText}
			modelCatalogStatusMessage={modelCatalogStatusMessage}
			compressionThreshold={compressionThreshold}
			isInputDisabled={isInputDisabled}
			isSubmissionPending={isSubmissionPending}
			isInterruptPending={interruptMutation.isPending}
			isSidebarCollapsed={isSidebarCollapsed}
			onToggleSidebarCollapsed={onToggleSidebarCollapsed}
			onOpenAnalytics={onOpenAnalytics}
			showSidebarPanel={showSidebarPanel}
			onSetShowSidebarPanel={handleSetShowSidebarPanel}
			prNumber={prNumber}
			diffStatusData={chatQuery.data?.diff_status}
			gitWatcher={gitWatcher}
			canOpenEditors={canOpenEditors}
			canOpenWorkspace={canOpenWorkspace}
			sshCommand={sshCommand}
			handleOpenInEditor={handleOpenInEditor}
			handleViewWorkspace={handleViewWorkspace}
			handleOpenTerminal={handleOpenTerminal}
			handleCommit={handleCommit}
			onNavigateToChat={(chatId) => navigate(`/agents/${chatId}`)}
			handleInterrupt={handleInterrupt}
			handleDeleteQueuedMessage={handleDeleteQueuedMessage}
			handlePromoteQueuedMessage={handlePromoteQueuedMessage}
			handleArchiveAgentAction={handleArchiveAgentAction}
			handleUnarchiveAgentAction={handleUnarchiveAgentAction}
			handleArchiveAndDeleteWorkspaceAction={
				handleArchiveAndDeleteWorkspaceAction
			}
			urlTransform={urlTransform}
			scrollContainerRef={scrollContainerRef}
			hasMoreMessages={chatMessagesQuery.hasNextPage ?? false}
			isFetchingMoreMessages={chatMessagesQuery.isFetchingNextPage}
			onFetchMoreMessages={chatMessagesQuery.fetchNextPage}
			desktopChatId={desktopEnabled ? agentId : undefined}
		/>
	);
};

export default AgentDetail;
