import * as React from "react";
import type { AgentConfigInfo } from "../api/agents";
import { agentsApi } from "../api/agents";
import type { AnthropicModelInfo, OAuthStatus } from "../api/client";
import { api } from "../api/client";
import { useChatHistory } from "../hooks/useChatHistory";
import { useChatSend } from "../hooks/useChatSend";
import { useChatWebSocket } from "../hooks/useChatWebSocket";
import { estimateTokens, getContextWindowSize } from "../utils/chatUtils";
import { ApiKeyDialog } from "./ApiKeyDialog";
import { BotConfigPage } from "./BotConfigPage";
import { SettingsPage } from "./SettingsPage";
import { ChatHeader } from "./ChatHeader";
import type { ChatInputHandle } from "./ChatInput";
import { ChatInput } from "./ChatInput";
import { ChatMessageList } from "./ChatMessageList";
import { ChatPipelinePanel } from "./ChatPipelinePanel";
import { HelpOverlay } from "./HelpOverlay";
import { PermissionDialog } from "./PermissionDialog";
import { ReconciliationBanner } from "./ReconciliationBanner";
import { SideQuestionOverlay } from "./SideQuestionOverlay";

const { useCallback, useEffect, useMemo, useRef, useState } = React;

const NARROW_BREAKPOINT = 900;

interface ChatProps {
	projectPath: string;
	onCloseProject: () => void;
	oauthStatus?: OAuthStatus | null;
}

export function Chat({
	projectPath,
	onCloseProject,
	oauthStatus = null,
}: ChatProps) {
	const { messages, setMessages, clearMessages } = useChatHistory(projectPath);
	const [loading, setLoading] = useState(false);
	const [model, setModel] = useState("claude-code-pty");
	const [enableTools, setEnableTools] = useState(true);
	const [availableModels, setAvailableModels] = useState<string[]>([]);
	const [claudeModels, setClaudeModels] = useState<string[]>([]);
	const [claudeContextWindowMap, setClaudeContextWindowMap] = useState<
		Map<string, number>
	>(new Map());
	const [hasAnthropicKey, setHasAnthropicKey] = useState(false);
	const [claudeSessionId, setClaudeSessionId] = useState<string | null>(() => {
		try {
			return (
				localStorage.getItem(`storykit-claude-session-id:${projectPath}`) ??
				null
			);
		} catch {
			return null;
		}
	});
	const [isNarrowScreen, setIsNarrowScreen] = useState(
		window.innerWidth < NARROW_BREAKPOINT,
	);
	const [agentRoster, setAgentRoster] = useState<AgentConfigInfo[]>([]);
	const [selectedWorkItemId, setSelectedWorkItemId] = useState<string | null>(
		null,
	);
	const [showHelp, setShowHelp] = useState(false);
	const [view, setView] = useState<"chat" | "bot-config" | "settings">("chat");
	const [queuedMessages, setQueuedMessages] = useState<
		{ id: string; text: string }[]
	>([]);
	const [pendingAutoSendBatch, setPendingAutoSendBatch] = useState<
		string[] | null
	>(null);

	const chatInputRef = useRef<ChatInputHandle>(null);
	const scrollContainerRef = useRef<HTMLDivElement>(null);
	const messagesEndRef = useRef<HTMLDivElement>(null);
	const shouldAutoScrollRef = useRef(true);
	const lastScrollTopRef = useRef(0);
	const userScrolledUpRef = useRef(false);
	const queuedMessagesRef = useRef<{ id: string; text: string }[]>([]);
	const queueIdCounterRef = useRef(0);
	const onboardingTriggeredRef = useRef(false);

	const {
		wsRef,
		wsConnected,
		wsConnectivity,
		wsDisconnectedAt,
		streamingContent,
		setStreamingContent,
		streamingThinking,
		setStreamingThinking,
		activityStatus,
		setActivityStatus,
		permissionQueue,
		setPermissionQueue,
		pipeline,
		pipelineVersion,
		reconciliationActive,
		reconciliationEvents,
		agentConfigVersion,
		agentStateVersion,
		needsOnboarding,
		setNeedsOnboarding,
		wizardState,
		setWizardState,
		sideQuestion,
		setSideQuestion,
		serverLogs,
		storyTokenCosts,
		statusEvents,
	} = useChatWebSocket({
		setMessages,
		setLoading,
		setClaudeSessionId,
		queuedMessagesRef,
		setQueuedMessages,
		setPendingAutoSendBatch,
	});

	const {
		sendMessage,
		sendMessageBatch,
		cancelGeneration,
		handleSaveApiKey,
		clearSession,
		showApiKeyDialog,
		setShowApiKeyDialog,
		apiKeyInput,
		setApiKeyInput,
	} = useChatSend({
		messages,
		loading,
		model,
		enableTools,
		claudeSessionId,
		streamingContent,
		setClaudeSessionId,
		setMessages,
		setLoading,
		setStreamingContent,
		setStreamingThinking,
		setActivityStatus,
		setSideQuestion,
		chatInputRef,
		wsRef,
		queuedMessagesRef,
		setQueuedMessages,
		queueIdCounterRef,
		clearMessages,
		projectPath,
	});

	const busyAgentNames = useMemo(() => {
		const busy = new Set<string>();
		const allItems = [
			...pipeline.backlog,
			...pipeline.current,
			...pipeline.qa,
			...pipeline.merge,
		];
		for (const item of allItems) {
			if (
				item.agent &&
				(item.agent.status === "running" || item.agent.status === "pending")
			) {
				busy.add(item.agent.agent_name);
			}
		}
		return busy;
	}, [pipeline]);

	const contextUsage = useMemo(() => {
		let totalTokens = 200;
		for (const msg of messages) {
			totalTokens += estimateTokens(msg.content);
			if (msg.tool_calls) {
				totalTokens += estimateTokens(JSON.stringify(msg.tool_calls));
			}
		}
		if (streamingContent) {
			totalTokens += estimateTokens(streamingContent);
		}
		const contextWindow = getContextWindowSize(model, claudeContextWindowMap);
		return {
			used: totalTokens,
			total: contextWindow,
			percentage: Math.round((totalTokens / contextWindow) * 100),
		};
	}, [messages, streamingContent, model, claudeContextWindowMap]);

	useEffect(() => {
		try {
			if (claudeSessionId !== null) {
				localStorage.setItem(
					`storykit-claude-session-id:${projectPath}`,
					claudeSessionId,
				);
			} else {
				localStorage.removeItem(`storykit-claude-session-id:${projectPath}`);
			}
		} catch {
			// Ignore — quota or security errors.
		}
	}, [claudeSessionId, projectPath]);

	useEffect(() => {
		api
			.getOllamaModels()
			.then(async (models) => {
				if (models.length > 0) {
					const sortedModels = models.sort((a, b) =>
						a.toLowerCase().localeCompare(b.toLowerCase()),
					);
					setAvailableModels(sortedModels);
					try {
						const savedModel = await api.getModelPreference();
						if (savedModel) setModel(savedModel);
					} catch (e) {
						console.error(e);
					}
				}
			})
			.catch((err) => console.error(err));

		api
			.getAnthropicApiKeyExists()
			.then((exists) => {
				setHasAnthropicKey(exists);
				if (!exists) return;
				return api.getAnthropicModels().then((models: AnthropicModelInfo[]) => {
					if (models.length > 0) {
						const sorted = models.sort((a, b) =>
							a.id.toLowerCase().localeCompare(b.id.toLowerCase()),
						);
						setClaudeModels(sorted.map((m) => m.id));
						setClaudeContextWindowMap(
							new Map(sorted.map((m) => [m.id, m.context_window])),
						);
					} else {
						setClaudeModels([]);
						setClaudeContextWindowMap(new Map());
					}
				});
			})
			.catch((err) => {
				console.error(err);
				setHasAnthropicKey(false);
				setClaudeModels([]);
			});
	}, []);

	useEffect(() => {
		const handleResize = () =>
			setIsNarrowScreen(window.innerWidth < NARROW_BREAKPOINT);
		window.addEventListener("resize", handleResize);
		return () => window.removeEventListener("resize", handleResize);
	}, []);

	useEffect(() => {
		agentsApi
			.getAgentConfig()
			.then(setAgentRoster)
			.catch(() => {
				// Silently ignore — roster unavailable.
			});
	}, [agentConfigVersion]);

	const scrollToBottom = useCallback(() => {
		const el = scrollContainerRef.current;
		if (el) {
			el.scrollTop = el.scrollHeight;
			lastScrollTopRef.current = el.scrollTop;
		}
	}, []);

	const handleScroll = () => {
		const el = scrollContainerRef.current;
		if (!el) return;
		const currentScrollTop = el.scrollTop;
		const isAtBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 5;
		if (currentScrollTop < lastScrollTopRef.current) {
			userScrolledUpRef.current = true;
			shouldAutoScrollRef.current = false;
		}
		if (isAtBottom) {
			userScrolledUpRef.current = false;
			shouldAutoScrollRef.current = true;
		}
		lastScrollTopRef.current = currentScrollTop;
	};

	const autoScrollKey =
		messages.length + streamingContent.length + streamingThinking.length;

	useEffect(() => {
		if (shouldAutoScrollRef.current && !userScrolledUpRef.current) {
			scrollToBottom();
		}
	}, [autoScrollKey, scrollToBottom]);

	useEffect(() => {
		if (pendingAutoSendBatch && pendingAutoSendBatch.length > 0) {
			const batch = pendingAutoSendBatch;
			setPendingAutoSendBatch(null);
			sendMessageBatch(batch);
		}
	}, [pendingAutoSendBatch, sendMessageBatch]);

	const handleStartAgent = useCallback(
		async (storyId: string, agentName?: string) => {
			try {
				await agentsApi.startAgent(storyId, agentName);
			} catch (err) {
				console.error("Failed to start agent:", err);
			}
		},
		[],
	);

	const handleStopAgent = useCallback((storyId: string, agentName: string) => {
		agentsApi.stopAgent(storyId, agentName).catch((err: unknown) => {
			console.error("Failed to stop agent:", err);
		});
	}, []);

	const handleDeleteItem = useCallback(
		(item: import("../api/client").PipelineStageItem) => {
			api.deleteStory(item.story_id).catch((err: unknown) => {
				console.error("Failed to delete story:", err);
			});
		},
		[],
	);

	const handleRemoveQueuedMessage = useCallback((id: string) => {
		queuedMessagesRef.current = queuedMessagesRef.current.filter(
			(item) => item.id !== id,
		);
		setQueuedMessages([...queuedMessagesRef.current]);
	}, []);

	const handlePermissionResponse = (approved: boolean, alwaysAllow = false) => {
		const current = permissionQueue[0];
		if (!current) return;
		wsRef.current?.sendPermissionResponse(
			current.requestId,
			approved,
			alwaysAllow,
		);
		setPermissionQueue((prev) => prev.slice(1));
	};

	return (
		<div
			className="chat-container"
			style={{
				display: "flex",
				flexDirection: "column",
				height: "100%",
				backgroundColor: "#171717",
				color: "#ececec",
			}}
		>
			<ChatHeader
				projectPath={projectPath}
				onCloseProject={onCloseProject}
				contextUsage={contextUsage}
				onClearSession={clearSession}
				model={model}
				availableModels={availableModels}
				claudeModels={claudeModels}
				hasAnthropicKey={hasAnthropicKey}
				onModelChange={(newModel) => {
					setModel(newModel);
					api.setModelPreference(newModel).catch(console.error);
				}}
				enableTools={enableTools}
				onToggleTools={setEnableTools}
				wsConnected={wsConnected}
				wsConnectivity={wsConnectivity}
				wsDisconnectedAt={wsDisconnectedAt}
				oauthStatus={oauthStatus}
				onShowBotConfig={() => setView("bot-config")}
				onShowSettings={() => setView("settings")}
			/>

			{view === "bot-config" && (
				<BotConfigPage onBack={() => setView("chat")} />
			)}

			{view === "settings" && <SettingsPage onBack={() => setView("chat")} />}

			<div
				data-testid="chat-content-area"
				style={{
					display: view === "chat" ? "flex" : "none",
					flex: 1,
					minHeight: 0,
					flexDirection: isNarrowScreen ? "column" : "row",
				}}
			>
				<div
					data-testid="chat-left-column"
					style={{
						display: "flex",
						flexDirection: "column",
						flex: "0 0 60%",
						minHeight: 0,
						overflow: "hidden",
					}}
				>
					<ChatMessageList
						messages={messages}
						loading={loading}
						streamingContent={streamingContent}
						streamingThinking={streamingThinking}
						activityStatus={activityStatus}
						wizardState={wizardState}
						setWizardState={setWizardState}
						needsOnboarding={needsOnboarding}
						setNeedsOnboarding={setNeedsOnboarding}
						sendMessage={sendMessage}
						scrollContainerRef={scrollContainerRef}
						messagesEndRef={messagesEndRef}
						onScroll={handleScroll}
						onboardingTriggeredRef={onboardingTriggeredRef}
					/>

					{reconciliationActive && (
						<ReconciliationBanner reconciliationEvents={reconciliationEvents} />
					)}

					<ChatInput
						ref={chatInputRef}
						loading={loading}
						queuedMessages={queuedMessages}
						onSubmit={sendMessage}
						onCancel={cancelGeneration}
						onRemoveQueuedMessage={handleRemoveQueuedMessage}
					/>
				</div>

				<ChatPipelinePanel
					isNarrowScreen={isNarrowScreen}
					pipeline={pipeline}
					pipelineVersion={pipelineVersion}
					agentConfigVersion={agentConfigVersion}
					agentStateVersion={agentStateVersion}
					storyTokenCosts={storyTokenCosts}
					agentRoster={agentRoster}
					busyAgentNames={busyAgentNames}
					selectedWorkItemId={selectedWorkItemId}
					serverLogs={serverLogs}
					statusEvents={statusEvents}
					onSelectWorkItem={setSelectedWorkItemId}
					onCloseWorkItem={() => setSelectedWorkItemId(null)}
					onStartAgent={handleStartAgent}
					onStopAgent={handleStopAgent}
					onDeleteItem={handleDeleteItem}
				/>
			</div>

			{showApiKeyDialog && (
				<ApiKeyDialog
					apiKeyInput={apiKeyInput}
					onApiKeyChange={setApiKeyInput}
					onSave={handleSaveApiKey}
					onCancel={() => {
						setShowApiKeyDialog(false);
						setApiKeyInput("");
					}}
				/>
			)}

			{permissionQueue.length > 0 && (
				<PermissionDialog
					permissionQueue={permissionQueue}
					onResponse={handlePermissionResponse}
				/>
			)}

			{showHelp && <HelpOverlay onDismiss={() => setShowHelp(false)} />}

			{sideQuestion && (
				<SideQuestionOverlay
					question={sideQuestion.question}
					response={sideQuestion.response}
					loading={sideQuestion.loading}
					onDismiss={() => setSideQuestion(null)}
				/>
			)}
		</div>
	);
}
