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 TerminalConnectionState = 'connected' | 'disconnected' | 'reconnecting';

export type TerminalViewProps = {
  className?: string;
  style?: CSSProperties;
  wsPath?: string;
  sessionId?: string;
  cwd?: string;
  command?: string;
  initialInput?: string;
  fontSize?: number;
  fontFamily?: string;
  theme?: TerminalTheme;
  autoFocus?: boolean;
  onReady?: (term: XTerm) => void;
  onExit?: () => void;
  onCommandDone?: (exitCode: number, output: string) => void;
  onDisconnect?: () => void;
  onPanelRefresh?: () => void;
  onConnectionChange?: (state: TerminalConnectionState) => void;
};

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

const buildWsUrl = (
  wsPath: string,
  sessionId?: string,
  cwd?: string,
  command?: string,
  cols?: number,
  rows?: number,
) => {
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
  const token = localStorage.getItem('BEARER_TOKEN') ?? '';
  const separator = wsPath.includes('?') ? '&' : '?';
  let url = `${protocol}//${window.location.host}${wsPath}${separator}token=${encodeURIComponent(token)}`;
  if (sessionId) url += `&sessionId=${encodeURIComponent(sessionId)}`;
  if (cwd) url += `&cwd=${encodeURIComponent(cwd)}`;
  if (command) url += `&command=${encodeURIComponent(command)}`;
  if (cols) url += `&cols=${cols}`;
  if (rows) url += `&rows=${rows}`;
  return url;
};

export const TerminalView = ({
  className,
  style,
  wsPath = '/api/terminal/ws',
  sessionId,
  cwd,
  command,
  initialInput,
  fontSize = 14,
  fontFamily = 'Menlo, Monaco, "Courier New", monospace',
  theme,
  autoFocus = true,
  onReady,
  onExit,
  onCommandDone,
  onDisconnect,
  onPanelRefresh,
  onConnectionChange,
}: TerminalViewProps) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const termRef = useRef<XTerm | null>(null);
  const wsRef = useRef<WebSocket | null>(null);
  const isMounted = useMounted();
  const onReadyRef = useRef<TerminalViewProps['onReady']>(onReady);
  const onExitRef = useRef<TerminalViewProps['onExit']>(onExit);
  const onCommandDoneRef = useRef<TerminalViewProps['onCommandDone']>(onCommandDone);
  const onDisconnectRef = useRef<TerminalViewProps['onDisconnect']>(onDisconnect);
  const onPanelRefreshRef = useRef<TerminalViewProps['onPanelRefresh']>(onPanelRefresh);
  const onConnectionChangeRef = useRef<TerminalViewProps['onConnectionChange']>(onConnectionChange);
  const commandRef = useRef(command);
  const initialInputRef = useRef(initialInput);

  onReadyRef.current = onReady;
  onExitRef.current = onExit;
  onCommandDoneRef.current = onCommandDone;
  onDisconnectRef.current = onDisconnect;
  onPanelRefreshRef.current = onPanelRefresh;
  onConnectionChangeRef.current = onConnectionChange;
  commandRef.current = command;
  initialInputRef.current = initialInput;

  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;

    // Step 2: Create at 80x24, then fit after layout settles.
    const term = new XTerm({
      cursorBlink: true,
      cols: 80,
      rows: 24,
      fontSize,
      fontFamily,
      theme: {
        background,
        foreground,
        cursor,
        selectionBackground,
      },
    });

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

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

    // Reconnect state
    let reconnectAttempts = 0;
    let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
    let processExited = false;
    const MAX_RECONNECT_ATTEMPTS = 5;
    const RECONNECT_DELAYS = [1000, 2000, 3000, 5000, 5000];

    let commandSent = false;
    let commandDone = false;
    let initialInputSent = false;
    let commandOutput = '';
    const EXIT_MARKER = '__OFFICER_EXIT_';
    // eslint-disable-next-line no-control-regex
    const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07/g, '');

    const connect = () => {
      if (disposed) return;

      fitAddon.fit();
      const cols = term.cols;
      const rows = term.rows;

      const ws = new WebSocket(buildWsUrl(wsPath, sessionId, cwd, command, cols, rows));
      wsRef.current = ws;

      const cleanupWs = () => {
        ws.removeEventListener('open', handleOpen);
        ws.removeEventListener('message', handleMessage);
        ws.removeEventListener('close', handleClose);
        ws.close();
      };

      const handleOpen = () => {
        reconnectAttempts = 0;
        onConnectionChangeRef.current?.('connected');
        ws.send(JSON.stringify({ type: 'resize', cols, rows }));
      };

      const handleMessage = (ev: MessageEvent) => {
        try {
          const msg = JSON.parse(ev.data as string);
          if (msg.type === 'output') {
            term.write(msg.data);
            if (commandRef.current && !commandSent) {
              commandSent = true;
              setTimeout(() => {
                if (ws.readyState === WebSocket.OPEN) {
                  const wrapped = onCommandDoneRef.current
                    ? `${commandRef.current}; echo "${EXIT_MARKER}$?__"`
                    : commandRef.current;
                  ws.send(JSON.stringify({ type: 'input', data: wrapped + '\r' }));
                }
              }, 100);
            }
            if (!commandRef.current && initialInputRef.current && !initialInputSent) {
              initialInputSent = true;
              setTimeout(() => {
                if (ws.readyState === WebSocket.OPEN) {
                  ws.send(JSON.stringify({ type: 'input', data: initialInputRef.current + '\r' }));
                }
              }, 500);
            }
            if (commandSent && !commandDone && onCommandDoneRef.current) {
              commandOutput += msg.data as string;
              const markerMatch = stripAnsi(commandOutput).match(/__OFFICER_EXIT_(\d+)__/);
              if (markerMatch) {
                const exitCode = Number(markerMatch[1]);
                const raw = stripAnsi(commandOutput).slice(0, markerMatch.index);
                const lines = raw
                  .split(/\r?\n/)
                  .map((l) => l.trim())
                  .filter(Boolean);
                const cmdLine = lines.findIndex((l) => l.includes(commandRef.current!.slice(0, 20)));
                const output = lines
                  .slice(cmdLine >= 0 ? cmdLine + 1 : 0)
                  .join('\n')
                  .trim();
                commandDone = true;
                onCommandDoneRef.current(exitCode, output);
              }
            }
          } else if (msg.type === 'exit') {
            processExited = true;
            term.write('\r\n[Process exited]\r\n');
            onExitRef.current?.();
          } else if (msg.type === 'detached') {
            term.write('\r\n[Session taken over]\r\n');
          } else if (msg.type === 'panel-refresh') {
            onPanelRefreshRef.current?.();
          }
        } catch {
          // ignore
        }
      };

      const handleClose = () => {
        cleanupWs();
        if (disposed || processExited) {
          onConnectionChangeRef.current?.('disconnected');
          term.write('\r\n[Disconnected]\r\n');
          onDisconnectRef.current?.();
          return;
        }
        if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
          const delay = RECONNECT_DELAYS[reconnectAttempts] ?? 5000;
          reconnectAttempts++;
          onConnectionChangeRef.current?.('reconnecting');
          term.write(`\r\n[Reconnecting (${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...]\r\n`);
          reconnectTimer = setTimeout(connect, delay);
        } else {
          onConnectionChangeRef.current?.('disconnected');
          term.write('\r\n[Disconnected]\r\n');
          onDisconnectRef.current?.();
        }
      };

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

      (container as any).__terminalCleanup = () => {
        cleanupWs();
      };
    };

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

    // Reconnect when tab becomes visible again
    const handleVisibilityChange = () => {
      if (document.visibilityState === 'visible' && !disposed && !processExited) {
        if (!wsRef.current || wsRef.current.readyState === WebSocket.CLOSED) {
          reconnectAttempts = 0;
          connect();
        }
      }
    };
    document.addEventListener('visibilitychange', handleVisibilityChange);

    // Wait for layout to fully settle (double rAF), then fit + connect
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        connect();
      });
    });

    const originalCleanup = () => {
      dataDisposable.dispose();
      document.removeEventListener('visibilitychange', handleVisibilityChange);
      if (reconnectTimer) clearTimeout(reconnectTimer);
      const wsCleanup = (container as any).__terminalCleanup as (() => void) | undefined;
      wsCleanup?.();
    };

    (container as any).__terminalFullCleanup = originalCleanup;

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

  return (
    <div className={className} style={{ backgroundColor: background, overflow: 'hidden', ...style }}>
      <div ref={containerRef} style={{ width: '100%', height: '100%' }} />
    </div>
  );
};
