import React, { useCallback, useEffect, useRef, useState } from "react";
import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
import { SerializeAddon } from "xterm-addon-serialize";
import { WebLinksAddon } from "xterm-addon-web-links";
import "xterm/css/xterm.css";

import { useTerminalConnection } from "../hooks/use-terminal-connection";
import type {
	ServerControlMessage,
	TerminalConnectionState,
} from "../types/terminal";
import styles from "./features/terminal/TerminalComponent.module.css";

interface TerminalProps {
	instanceId: string;
	onClose?: () => void;
}

const STATUS_DOT_COLORS: Record<TerminalConnectionState["status"], string> = {
	connecting: "var(--warning)",
	connected: "var(--success)",
	reconnecting: "var(--warning)",
	disconnected: "var(--muted)",
};

function getStatusText(state: TerminalConnectionState): string {
	switch (state.status) {
		case "connecting":
			return "Connecting...";
		case "connected": {
			if (state.latency !== null && state.latency >= 100) {
				return `Slow (${state.latency}ms)`;
			}
			return "Connected";
		}
		case "reconnecting":
			return `Reconnecting${state.attempt > 0 ? ` (${state.attempt})` : ""}`;
		case "disconnected":
			return state.error || "Disconnected";
	}
}

export const TerminalComponent: React.FC<TerminalProps> = ({
	instanceId,
	onClose,
}) => {
	const terminalRef = useRef<HTMLDivElement>(null);
	const xtermRef = useRef<Terminal | null>(null);
	const fitAddonRef = useRef<FitAddon | null>(null);
	const serializeAddonRef = useRef<SerializeAddon | null>(null);
	const resizeObserverRef = useRef<ResizeObserver | null>(null);
	const [sessionEnded, setSessionEnded] = useState<{
		reason: string;
		message: string;
	} | null>(null);

	// Determine dark mode from document theme
	const isDarkMode =
		document.documentElement.getAttribute("data-theme") === "dark" ||
		(document.documentElement.getAttribute("data-theme") === null &&
			window.matchMedia("(prefers-color-scheme: dark)").matches);

	const handleData = useCallback((data: Uint8Array) => {
		// Data is already written by onLocalEcho or deduplication
		// This callback is mainly for external consumers
		void data;
	}, []);

	const handleLocalEcho = useCallback((data: string) => {
		xtermRef.current?.write(data);
	}, []);

	const serializeFn = useCallback((): string | null => {
		return serializeAddonRef.current?.serialize() ?? null;
	}, []);

	const handleRestoreScrollback = useCallback((content: string) => {
		xtermRef.current?.write(content);
		xtermRef.current?.write("\r\n\x1b[90m--- Reconnected ---\x1b[0m\r\n");
	}, []);

	const handleControl = useCallback((msg: ServerControlMessage) => {
		if (msg.type === "session_ended") {
			const messages: Record<string, string> = {
				process_exit: "The container process has exited.",
				container_stop: "The container was stopped.",
				timeout: "The session timed out due to inactivity.",
			};
			setSessionEnded({
				reason: msg.reason,
				message: messages[msg.reason] || "The session has ended.",
			});
		}
	}, []);

	const { state, sendInput, sendResize, reconnect } = useTerminalConnection({
		instanceId,
		onData: handleData,
		onControl: handleControl,
		onLocalEcho: handleLocalEcho,
		serializeFn,
		onRestoreScrollback: handleRestoreScrollback,
	});

	// Initialize xterm
	useEffect(() => {
		if (!terminalRef.current) return;

		const term = new Terminal({
			cursorBlink: true,
			fontSize: 14,
			fontFamily: 'Menlo, Monaco, "Courier New", monospace',
			theme: isDarkMode
				? {
						background: "#1e1e1e",
						foreground: "#d4d4d4",
						cursor: "#d4d4d4",
						selectionBackground: "#264f78",
						black: "#000000",
						red: "#cd3131",
						green: "#0dbc79",
						yellow: "#e5e510",
						blue: "#2472c8",
						magenta: "#bc3fbc",
						cyan: "#11a8cd",
						white: "#e5e5e5",
						brightBlack: "#666666",
						brightRed: "#f14c4c",
						brightGreen: "#23d18b",
						brightYellow: "#f5f543",
						brightBlue: "#3b8eea",
						brightMagenta: "#d670d6",
						brightCyan: "#29b8db",
						brightWhite: "#e5e5e5",
					}
				: {
						background: "#fafafa",
						foreground: "#333333",
						cursor: "#333333",
						selectionBackground: "#b4d7ff",
						black: "#000000",
						red: "#cd3131",
						green: "#0dbc79",
						yellow: "#e5e510",
						blue: "#2472c8",
						magenta: "#bc3fbc",
						cyan: "#11a8cd",
						white: "#e5e5e5",
						brightBlack: "#666666",
						brightRed: "#f14c4c",
						brightGreen: "#23d18b",
						brightYellow: "#f5f543",
						brightBlue: "#3b8eea",
						brightMagenta: "#d670d6",
						brightCyan: "#29b8db",
						brightWhite: "#e5e5e5",
					},
		});

		const fitAddon = new FitAddon();
		const serializeAddon = new SerializeAddon();

		term.loadAddon(fitAddon);
		term.loadAddon(serializeAddon);
		term.loadAddon(new WebLinksAddon());

		term.open(terminalRef.current);
		fitAddon.fit();

		xtermRef.current = term;
		fitAddonRef.current = fitAddon;
		serializeAddonRef.current = serializeAddon;

		// Handle terminal input
		const disposable = term.onData((data) => {
			sendInput(data);
		});

		// Resize observer for container-level resize detection
		let resizeTimeout: ReturnType<typeof setTimeout> | null = null;
		let lastWidth = 0;
		let lastHeight = 0;
		const resizeObserver = new ResizeObserver((entries) => {
			if (resizeTimeout) {
				clearTimeout(resizeTimeout);
			}
			const entry = entries[0];
			if (!entry) return;
			const { width, height } = entry.contentRect;
			resizeTimeout = setTimeout(() => {
				resizeTimeout = null;
				// Guard against internal xterm DOM changes that don't affect container size
				if (
					Math.abs(width - lastWidth) < 1 &&
					Math.abs(height - lastHeight) < 1
				) {
					return;
				}
				lastWidth = width;
				lastHeight = height;
				const prevCols = term.cols;
				const prevRows = term.rows;
				fitAddon.fit();
				if (term.cols !== prevCols || term.rows !== prevRows) {
					sendResize(term.cols, term.rows);
				}
			}, 100);
		});
		resizeObserver.observe(terminalRef.current);
		resizeObserverRef.current = resizeObserver;

		return () => {
			if (resizeTimeout) {
				clearTimeout(resizeTimeout);
			}
			disposable.dispose();
			resizeObserver.disconnect();
			term.dispose();
			xtermRef.current = null;
			fitAddonRef.current = null;
			serializeAddonRef.current = null;
		};
	}, [instanceId, isDarkMode, sendInput, sendResize]);

	// Send initial terminal size once connected (and on reconnect)
	useEffect(() => {
		if (state.status === "connected" && xtermRef.current) {
			const { cols, rows } = xtermRef.current;
			sendResize(cols, rows);
		}
	}, [state.status, sendResize]);

	return (
		<div className={styles.terminalWrapper}>
			<div className={styles.terminalHeader}>
				<div className={styles.terminalStatus}>
					<span
						className={styles.statusDot}
						style={{
							backgroundColor: STATUS_DOT_COLORS[state.status],
						}}
						aria-label={`Terminal status: ${state.status}`}
						title={
							state.latency !== null
								? `Latency: ${state.latency}ms`
								: getStatusText(state)
						}
					/>
					<span className={styles.statusText}>{getStatusText(state)}</span>
				</div>
				<div className={styles.terminalActions}>
					{state.status === "disconnected" && (
						<button
							className="secondary-button small"
							onClick={reconnect}
							type="button"
						>
							Reconnect
						</button>
					)}
					{onClose && (
						<button className={styles.terminalClose} onClick={onClose} type="button">
							Close
						</button>
					)}
				</div>
			</div>

			{sessionEnded && (
				<div className={styles.terminalOverlay}>
					<div className={styles.terminalOverlayContent}>
						<h3>Session Ended</h3>
						<p>{sessionEnded.message}</p>
						<div className={styles.terminalOverlayActions}>
							<button
								className="primary-button small"
								onClick={() => {
									setSessionEnded(null);
									reconnect();
								}}
								type="button"
							>
								Reconnect
							</button>
							{onClose && (
								<button
									className="secondary-button small"
									onClick={onClose}
									type="button"
								>
									Go Back
								</button>
							)}
						</div>
					</div>
				</div>
			)}

			{state.status === "reconnecting" && (
				<div className={styles.terminalReconnectBanner}>
					<span className={styles.spinner} />
					{state.error}
				</div>
			)}

			<div ref={terminalRef} className={styles.terminalContainer} />
		</div>
	);
};
