import * as React from "react";
import type {
	PipelineState,
	StatusEvent,
	WizardStateData,
} from "../api/client";
import { api, ChatWebSocket } from "../api/client";
import type { LogEntry } from "../components/ServerLogsPanel";
import type { Message } from "../types";
import { formatToolActivity } from "../utils/chatUtils";

const { useEffect, useRef, useState } = React;

/** Connectivity state of the WebSocket connection. */
export type WsConnectivity = "connecting" | "connected" | "reconnecting" | "failed";

type SetState<T> = React.Dispatch<React.SetStateAction<T>>;

interface UseChatWebSocketParams {
	setMessages: SetState<Message[]>;
	setLoading: SetState<boolean>;
	setClaudeSessionId: SetState<string | null>;
	queuedMessagesRef: React.MutableRefObject<{ id: string; text: string }[]>;
	setQueuedMessages: SetState<{ id: string; text: string }[]>;
	setPendingAutoSendBatch: SetState<string[] | null>;
}

interface ReconciliationEvent {
	id: string;
	storyId: string;
	status: string;
	message: string;
}

export interface UseChatWebSocketResult {
	wsRef: React.MutableRefObject<ChatWebSocket | null>;
	wsConnected: boolean;
	wsConnectivity: WsConnectivity;
	wsDisconnectedAt: Date | null;
	streamingContent: string;
	setStreamingContent: SetState<string>;
	streamingThinking: string;
	setStreamingThinking: SetState<string>;
	activityStatus: string | null;
	setActivityStatus: SetState<string | null>;
	permissionQueue: {
		requestId: string;
		toolName: string;
		toolInput: Record<string, unknown>;
	}[];
	setPermissionQueue: SetState<
		{
			requestId: string;
			toolName: string;
			toolInput: Record<string, unknown>;
		}[]
	>;
	pipeline: PipelineState;
	pipelineVersion: number;
	reconciliationActive: boolean;
	reconciliationEvents: ReconciliationEvent[];
	agentConfigVersion: number;
	agentStateVersion: number;
	needsOnboarding: boolean;
	setNeedsOnboarding: SetState<boolean>;
	wizardState: WizardStateData | null;
	setWizardState: SetState<WizardStateData | null>;
	sideQuestion: {
		question: string;
		response: string;
		loading: boolean;
	} | null;
	setSideQuestion: SetState<{
		question: string;
		response: string;
		loading: boolean;
	} | null>;
	serverLogs: LogEntry[];
	storyTokenCosts: Map<string, number>;
	/** Structured pipeline status events. Each entry preserves the full
	 * StatusEvent so future UI stories can render per-type icons or filters. */
	statusEvents: Array<{ receivedAt: string; event: StatusEvent }>;
}

export function useChatWebSocket({
	setMessages,
	setLoading,
	setClaudeSessionId,
	queuedMessagesRef,
	setQueuedMessages,
	setPendingAutoSendBatch,
}: UseChatWebSocketParams): UseChatWebSocketResult {
	const wsRef = useRef<ChatWebSocket | null>(null);
	const [wsConnected, setWsConnected] = useState(false);
	const [wsConnectivity, setWsConnectivity] = useState<WsConnectivity>("connecting");
	const [wsDisconnectedAt, setWsDisconnectedAt] = useState<Date | null>(null);
	const failedTimerRef = useRef<number | undefined>(undefined);
	const [streamingContent, setStreamingContent] = useState("");
	const [streamingThinking, setStreamingThinking] = useState("");
	const [activityStatus, setActivityStatus] = useState<string | null>(null);
	const [permissionQueue, setPermissionQueue] = useState<
		{
			requestId: string;
			toolName: string;
			toolInput: Record<string, unknown>;
		}[]
	>([]);
	const [pipeline, setPipeline] = useState<PipelineState>({
		backlog: [],
		current: [],
		qa: [],
		merge: [],
		done: [],
		deterministic_merges_in_flight: [],
	});
	const [pipelineVersion, setPipelineVersion] = useState(0);
	const [reconciliationActive, setReconciliationActive] = useState(false);
	const [reconciliationEvents, setReconciliationEvents] = useState<
		ReconciliationEvent[]
	>([]);
	const reconciliationEventIdRef = useRef(0);
	const [agentConfigVersion, setAgentConfigVersion] = useState(0);
	const [agentStateVersion, setAgentStateVersion] = useState(0);
	const [needsOnboarding, setNeedsOnboarding] = useState(false);
	const [wizardState, setWizardState] = useState<WizardStateData | null>(null);
	const [sideQuestion, setSideQuestion] = useState<{
		question: string;
		response: string;
		loading: boolean;
	} | null>(null);
	const [serverLogs, setServerLogs] = useState<LogEntry[]>([]);
	const [storyTokenCosts, setStoryTokenCosts] = useState<Map<string, number>>(
		new Map(),
	);
	const [statusEvents, setStatusEvents] = useState<
		Array<{ receivedAt: string; event: StatusEvent }>
	>([]);

	useEffect(() => {
		const ws = new ChatWebSocket();
		wsRef.current = ws;

		ws.connect({
			onToken: (content) => {
				setStreamingContent((prev: string) => prev + content);
			},
			onThinkingToken: (content) => {
				setStreamingThinking((prev: string) => prev + content);
			},
			onUpdate: (history) => {
				setMessages(history);
				setStreamingContent("");
				setStreamingThinking("");
				const last = history[history.length - 1];
				if (last?.role === "assistant" && !last.tool_calls) {
					setLoading(false);
					setActivityStatus(null);
					if (queuedMessagesRef.current.length > 0) {
						const batch = queuedMessagesRef.current.map((item) => item.text);
						queuedMessagesRef.current = [];
						setQueuedMessages([]);
						setPendingAutoSendBatch(batch);
					}
				}
			},
			onSessionId: (sessionId) => {
				setClaudeSessionId(sessionId);
			},
			onError: (message) => {
				console.error("WebSocket error:", message);
				setLoading(false);
				setActivityStatus(null);
				if (queuedMessagesRef.current.length > 0) {
					const batch = queuedMessagesRef.current.map((item) => item.text);
					queuedMessagesRef.current = [];
					setQueuedMessages([]);
					setPendingAutoSendBatch(batch);
				}
			},
			onPipelineState: (state) => {
				setPipeline(state);
				setPipelineVersion((v) => v + 1);
				const allItems = [
					...state.backlog,
					...state.current,
					...state.qa,
					...state.merge,
					...state.done,
				];
				for (const item of allItems) {
					api
						.getTokenCost(item.story_id)
						.then((cost) => {
							if (cost.total_cost_usd > 0) {
								setStoryTokenCosts((prev) => {
									const next = new Map(prev);
									next.set(item.story_id, cost.total_cost_usd);
									return next;
								});
							}
						})
						.catch(() => {
							// Silently ignore — cost data may not exist yet.
						});
				}
			},
			onPermissionRequest: (requestId, toolName, toolInput) => {
				setPermissionQueue((prev) => [
					...prev,
					{ requestId, toolName, toolInput },
				]);
			},
			onActivity: (toolName) => {
				setActivityStatus(formatToolActivity(toolName));
			},
			onReconciliationProgress: (storyId, status, message) => {
				if (status === "done") {
					setReconciliationActive(false);
				} else {
					setReconciliationActive(true);
					setReconciliationEvents((prev) => {
						const id = String(reconciliationEventIdRef.current++);
						const next = [...prev, { id, storyId, status, message }];
						// Keep only the last 8 events to avoid the banner growing too tall.
						return next.slice(-8);
					});
				}
			},
			onAgentConfigChanged: () => {
				setAgentConfigVersion((v) => v + 1);
			},
			onAgentStateChanged: () => {
				setAgentStateVersion((v) => v + 1);
			},
			onOnboardingStatus: (onboarding: boolean) => {
				setNeedsOnboarding(onboarding);
			},
			onWizardState: (state: WizardStateData) => {
				setWizardState(state);
			},
			onSideQuestionToken: (content) => {
				setSideQuestion((prev) =>
					prev ? { ...prev, response: prev.response + content } : prev,
				);
			},
			onSideQuestionDone: (response) => {
				setSideQuestion((prev) =>
					prev ? { ...prev, response, loading: false } : prev,
				);
			},
			onLogEntry: (timestamp, level, message) => {
				setServerLogs((prev) => [...prev, { timestamp, level, message }]);
			},
			onStatusUpdate: (event) => {
				// Preserve the structured event and receive timestamp so future stories
				// can render per-type icons, banners, or filters without format changes.
				setStatusEvents((prev) => [
					...prev,
					{ receivedAt: new Date().toISOString(), event },
				]);
			},
			onConnected: () => {
				setWsConnected(true);
				setWsConnectivity("connected");
				setWsDisconnectedAt(null);
				window.clearTimeout(failedTimerRef.current);
				failedTimerRef.current = undefined;
			},
			onDisconnected: () => {
				setWsConnectivity("reconnecting");
				setWsDisconnectedAt(new Date());
				window.clearTimeout(failedTimerRef.current);
				failedTimerRef.current = window.setTimeout(() => {
					setWsConnectivity("failed");
				}, 30_000);
			},
		});

		return () => {
			ws.close();
			wsRef.current = null;
			window.clearTimeout(failedTimerRef.current);
			failedTimerRef.current = undefined;
		};
	}, []);

	return {
		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,
	};
}
