import type { CSSProperties } from 'react';
import { useEffect, useRef } from 'react';
import { Terminal as XTerm } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import '@xterm/xterm/css/xterm.css';
import { useMounted } from 'hooks/useMounted';

type TerminalTheme = {
  background?: string;
  foreground?: string;
  cursor?: string;
  selectionBackground?: string;
};

export type TerminalViewProps = {
  className?: string;
  style?: CSSProperties;
  wsPath?: string;
  fontSize?: number;
  fontFamily?: string;
  theme?: TerminalTheme;
  autoFocus?: boolean;
  onReady?: (term: XTerm) => void;
  onExit?: () => void;
  onDisconnect?: () => void;
};

const DEFAULT_THEME: Required<TerminalTheme> = {
  background: '#1a1a2e',
  foreground: '#e0e0e0',
  cursor: '#e0e0e0',
  selectionBackground: '#3a3a5e',
};

const buildWsUrl = (wsPath: string) => {
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  const token = localStorage.getItem('BEARER_TOKEN') ?? '';
  const separator = wsPath.includes('?') ? '&' : '?';
  return `${protocol}//${window.location.host}${wsPath}${separator}token=${encodeURIComponent(token)}`;
};

export const TerminalView = ({
  className,
  style,
  wsPath = '/api/terminal/ws',
  fontSize = 14,
  fontFamily = 'Menlo, Monaco, "Courier New", monospace',
  theme,
  autoFocus = true,
  onReady,
  onExit,
  onDisconnect,
}: TerminalViewProps) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const termRef = useRef<XTerm | null>(null);
  const wsRef = useRef<WebSocket | null>(null);
  const fitAddonRef = useRef<FitAddon | null>(null);
  const isMounted = useMounted();
  const onReadyRef = useRef<TerminalViewProps['onReady']>(onReady);
  const onExitRef = useRef<TerminalViewProps['onExit']>(onExit);
  const onDisconnectRef = useRef<TerminalViewProps['onDisconnect']>(onDisconnect);

  onReadyRef.current = onReady;
  onExitRef.current = onExit;
  onDisconnectRef.current = onDisconnect;

  const background = theme?.background ?? DEFAULT_THEME.background;
  const foreground = theme?.foreground ?? DEFAULT_THEME.foreground;
  const cursor = theme?.cursor ?? DEFAULT_THEME.cursor;
  const selectionBackground = theme?.selectionBackground ?? DEFAULT_THEME.selectionBackground;

  useEffect(() => {
    if (!isMounted) return;
    const container = containerRef.current;
    if (!container) return;

    let disposed = false;
    const initTimeout = setTimeout(() => {
      if (disposed) return;
      const term = new XTerm({
        cursorBlink: true,
        fontSize,
        fontFamily,
        theme: {
          background,
          foreground,
          cursor,
          selectionBackground,
        },
      });

      const fitAddon = new FitAddon();
      term.loadAddon(fitAddon);
      term.open(container);
      fitAddon.fit();
      if (autoFocus) term.focus();

      termRef.current = term;
      fitAddonRef.current = fitAddon;
      onReadyRef.current?.(term);

      const ws = new WebSocket(buildWsUrl(wsPath));
      wsRef.current = ws;

      const handleOpen = () => {
        ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
      };

      const handleMessage = (ev: MessageEvent) => {
        try {
          const msg = JSON.parse(ev.data as string);
          if (msg.type === 'output') {
            term.write(msg.data);
          } else if (msg.type === 'exit') {
            term.write('\r\n[Process exited]\r\n');
            onExitRef.current?.();
          }
        } catch {
          // ignore
        }
      };

      const handleClose = () => {
        term.write('\r\n[Disconnected]\r\n');
        onDisconnectRef.current?.();
      };

      ws.addEventListener('open', handleOpen);
      ws.addEventListener('message', handleMessage);
      ws.addEventListener('close', handleClose);

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

      const resizeObserver = new ResizeObserver(() => {
        fitAddon.fit();
        if (ws.readyState === WebSocket.OPEN) {
          ws.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
        }
      });
      resizeObserver.observe(container);

      const cleanup = () => {
        dataDisposable.dispose();
        resizeObserver.disconnect();
        ws.removeEventListener('open', handleOpen);
        ws.removeEventListener('message', handleMessage);
        ws.removeEventListener('close', handleClose);
      };

      (container as any).__terminalCleanup = cleanup;
    }, 0);

    return () => {
      disposed = true;
      clearTimeout(initTimeout);
      const cleanup = (container as any).__terminalCleanup as (() => void) | undefined;
      cleanup?.();
      delete (container as any).__terminalCleanup;
      wsRef.current?.close();
      wsRef.current = null;
      termRef.current?.dispose();
      termRef.current = null;
      fitAddonRef.current = null;
    };
  }, [
    isMounted,
    wsPath,
    fontSize,
    fontFamily,
    background,
    foreground,
    cursor,
    selectionBackground,
    autoFocus,
  ]);

  return (
    <div
      ref={containerRef}
      className={className}
      style={{ backgroundColor: background, ...style }}
    />
  );
};
