import { useEffect, useRef } from 'react';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { WebLinksAddon } from 'xterm-addon-web-links';
import 'xterm/css/xterm.css';
import { api } from '@/api/client';
import { sendToTerminal, terminalsRegistry } from '@/lib/events';

interface Props {
  sessionId: string;
  paneId: string;
  label: string;
}

// Minimal dark theme. xterm.js renders against its own canvas; CSS variables
// don't reach it, so we hardcode. Matches the obsidian-dark base in spirit.
const XTERM_THEME = {
  background: '#0b0f14',
  foreground: '#d6deeb',
  cursor: '#82aaff',
  selectionBackground: '#1d3b53',
  black: '#011627',
  red: '#ef5350',
  green: '#22da6e',
  yellow: '#c5e478',
  blue: '#82aaff',
  magenta: '#c792ea',
  cyan: '#7fdbca',
  white: '#d6deeb',
  brightBlack: '#575656',
  brightRed: '#ef5350',
  brightGreen: '#22da6e',
  brightYellow: '#ffeb95',
  brightBlue: '#82aaff',
  brightMagenta: '#c792ea',
  brightCyan: '#7fdbca',
  brightWhite: '#ffffff',
};

export function TerminalPane({ sessionId, paneId, label }: Props) {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const wsRef = useRef<WebSocket | null>(null);

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

    let disposed = false;
    let resizeDebounceTimer: ReturnType<typeof setTimeout> | null = null;

    const term = new Terminal({
      fontFamily: '"JetBrains Mono Variable", "JetBrains Mono", ui-monospace, monospace',
      fontSize: 13,
      lineHeight: 1.2,
      cursorBlink: true,
      scrollback: 10_000,
      theme: XTERM_THEME,
      allowProposedApi: true,
    });
    const fit = new FitAddon();
    term.loadAddon(fit);
    term.loadAddon(new WebLinksAddon());
    term.open(container);
    try {
      fit.fit();
    } catch {
      /* container not yet sized */
    }

    // POST start kicks the tmux window into existence before the WS upgrade.
    // It's idempotent so a refresh just no-ops. Failures fall through to the
    // WS handler which will also call ensureWindow.
    api.terminals.start(sessionId, paneId).catch(() => {
      /* surfaced by WS error if it matters */
    });

    const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
    const initialCols = term.cols;
    const initialRows = term.rows;
    const wsUrl =
      `${proto}//${window.location.host}/ws/term/sessions/${sessionId}/panes/${paneId}` +
      `?cols=${initialCols}&rows=${initialRows}`;
    const ws = new WebSocket(wsUrl);
    ws.binaryType = 'arraybuffer';
    wsRef.current = ws;

    ws.onmessage = (e) => {
      if (typeof e.data === 'string') {
        // Control frame from server (e.g. {"type":"exit","code":0}).
        try {
          const parsed = JSON.parse(e.data) as { type?: string; code?: number };
          if (parsed.type === 'exit') {
            term.write(`\r\n\x1b[2m[process exited with code ${parsed.code ?? 0}]\x1b[0m\r\n`);
            return;
          }
        } catch {
          /* not JSON — fall through and write as text */
        }
        term.write(e.data);
      } else {
        term.write(new Uint8Array(e.data));
      }
    };

    ws.onclose = () => {
      if (disposed) return;
      term.write('\r\n\x1b[2m[disconnected]\x1b[0m\r\n');
    };

    term.onData((data) => {
      if (ws.readyState === WebSocket.OPEN) {
        ws.send(data);
      }
    });

    const fireResize = () => {
      try {
        fit.fit();
      } catch {
        return;
      }
      const cols = term.cols;
      const rows = term.rows;
      api.terminals.resize(sessionId, paneId, cols, rows).catch(() => {
        /* transient — next resize will catch up */
      });
    };

    const ro = new ResizeObserver(() => {
      if (resizeDebounceTimer !== null) clearTimeout(resizeDebounceTimer);
      resizeDebounceTimer = setTimeout(fireResize, 100);
    });
    ro.observe(container);

    const unregister = terminalsRegistry.register(paneId, label);

    const unsubscribe = sendToTerminal.subscribe(({ pane_id, text }) => {
      if (pane_id !== paneId) return;
      if (ws.readyState !== WebSocket.OPEN) return;
      const payload = text.endsWith('\n') ? text : `${text}\n`;
      ws.send(payload);
    });

    return () => {
      disposed = true;
      unsubscribe();
      unregister();
      if (resizeDebounceTimer !== null) clearTimeout(resizeDebounceTimer);
      ro.disconnect();
      try {
        ws.close();
      } catch {
        /* ignore */
      }
      wsRef.current = null;
      term.dispose();
    };
  }, [sessionId, paneId, label]);

  return (
    <div
      ref={containerRef}
      className="w-full h-full bg-[#0b0f14] overflow-hidden"
      data-testid="terminal-pane"
    />
  );
}
