import { FitAddon } from "@xterm/addon-fit";
import { WebLinksAddon } from "@xterm/addon-web-links";
import { Terminal } from "@xterm/xterm";
import { useEffect, useRef, useState } from "react";
import { wsUrl } from "../../api/base";
import styles from "./WebTerminal.module.css";
import "@xterm/xterm/css/xterm.css";

type ConnectionState = "idle" | "connecting" | "connected" | "error";

function normalizeTerminalInput(data: string, platform?: string): string {
	if (platform !== "windows") return data;
	return data.replace(/\r(?!\n)/g, "\r\n");
}

function terminalFontSize(width: number): number {
	if (width < 360) return 11;
	if (width < 480) return 12;
	return 13;
}

export function WebTerminal({
	active,
	platform,
}: {
	active: boolean;
	platform?: string;
}) {
	const shellRef = useRef<HTMLDivElement>(null);
	const containerRef = useRef<HTMLDivElement>(null);
	const [connectionState, setConnectionState] = useState<ConnectionState>("idle");
	const [sessionKey, setSessionKey] = useState(0);

	useEffect(() => {
		if (!active) {
			setConnectionState("idle");
			return;
		}

		const container = containerRef.current;
		const shell = shellRef.current;
		if (!container || !shell) return;

		let disposed = false;
		let resizeTimer: number | undefined;

		const term = new Terminal({
			cursorBlink: true,
			fontSize: terminalFontSize(container.clientWidth),
			lineHeight: 1.2,
			letterSpacing: 0,
			fontFamily: '"LXGW WenKai Mono", "PingFang SC", "Microsoft YaHei UI", ui-monospace, monospace',
			scrollback: 5000,
			allowProposedApi: true,
			theme: {
				background: "#0d1117",
				foreground: "#c9d1d9",
				cursor: "#58a6ff",
				selectionBackground: "#264f78",
			},
		});
		const fitAddon = new FitAddon();
		const webLinksAddon = new WebLinksAddon();
		term.loadAddon(fitAddon);
		term.loadAddon(webLinksAddon);
		term.open(container);

		setConnectionState("connecting");

		const ws = new WebSocket(wsUrl("/api/terminal/ws"));
		ws.binaryType = "arraybuffer";

		const sendResize = () => {
			if (ws.readyState !== WebSocket.OPEN) return;
			ws.send(JSON.stringify({ type: "resize", cols: term.cols, rows: term.rows }));
		};

		const fit = () => {
			if (disposed) return;
			const width = container.clientWidth;
			const height = container.clientHeight;
			if (width < 24 || height < 24) return;

			const nextFontSize = terminalFontSize(width);
			if (term.options.fontSize !== nextFontSize) {
				term.options.fontSize = nextFontSize;
			}

			try {
				fitAddon.fit();
			} catch {
				return;
			}
			sendResize();
		};

		const scheduleFit = () => {
			window.clearTimeout(resizeTimer);
			resizeTimer = window.setTimeout(() => {
				requestAnimationFrame(fit);
			}, 32);
		};

		const resizeObserver = new ResizeObserver(scheduleFit);
		resizeObserver.observe(shell);
		resizeObserver.observe(container);
		window.addEventListener("resize", scheduleFit);

		ws.onopen = () => {
			if (disposed) return;
			setConnectionState("connected");
			scheduleFit();
			window.setTimeout(scheduleFit, 120);
			term.focus();
		};

		ws.onmessage = (event) => {
			if (disposed) return;
			if (typeof event.data === "string") {
				try {
					const payload = JSON.parse(event.data) as { type?: string; error?: string };
					if (payload.type === "error" && payload.error) {
						setConnectionState("error");
						term.writeln(`\r\n\x1b[31m${payload.error}\x1b[0m`);
					}
				} catch {
					term.write(event.data);
				}
				return;
			}
			term.write(new Uint8Array(event.data));
		};

		ws.onerror = () => {
			if (disposed) return;
			setConnectionState("error");
		};

		ws.onclose = () => {
			if (disposed) return;
			setConnectionState("idle");
			term.writeln("\r\n\x1b[90m[连接已断开]\x1b[0m");
		};

		const dataDisposable = term.onData((data) => {
			if (ws.readyState === WebSocket.OPEN) {
				ws.send(JSON.stringify({ type: "input", data: normalizeTerminalInput(data, platform) }));
			}
		});

		const resizeDisposable = term.onResize(() => {
			sendResize();
		});

		scheduleFit();

		return () => {
			disposed = true;
			window.clearTimeout(resizeTimer);
			window.removeEventListener("resize", scheduleFit);
			dataDisposable.dispose();
			resizeDisposable.dispose();
			resizeObserver.disconnect();
			if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
				ws.close();
			}
			term.dispose();
		};
	}, [active, platform, sessionKey]);

	const statusLabel =
		connectionState === "connecting"
			? "连接中…"
			: connectionState === "connected"
				? "已连接"
				: connectionState === "error"
					? "连接失败"
					: "未连接";

	return (
		<div ref={shellRef} className={styles.shell}>
			<div className={styles.toolbar}>
				<div className={styles.toolbarTop}>
					<div className={styles.statusLine}>
						<span className={`${styles.statusDot} ${styles[connectionState]}`} aria-hidden />
						<span className={styles.statusText}>{statusLabel}</span>
					</div>
					<button
						type="button"
						className={styles.reconnectBtn}
						disabled={!active || connectionState === "connecting"}
						onClick={() => setSessionKey((key) => key + 1)}
					>
						重新连接
					</button>
				</div>
			</div>
			<div ref={containerRef} className={styles.terminalHost} tabIndex={0} aria-label="Web 终端" />
		</div>
	);
}
