"use client";

/**
 * XTerminal component
 *
 * Renders a real xterm.js terminal. The parent (TerminalPanel via useTerminalSessions)
 * owns the WebSocket connection and session lifecycle. This component receives the
 * sessionId and control functions as props and registers for output data specific
 * to its session.
 *
 * Handles resize, copy/paste, theme, exit overlay, and reconnect.
 */

import { useEffect, useRef, useCallback } from "react";
import type { ReactElement, CSSProperties } from "react";
import "@xterm/xterm/css/xterm.css";
import type { Terminal as XTerm } from "@xterm/xterm";
import type { FitAddon as XFitAddon } from "@xterm/addon-fit";
import type { SessionStatus } from "@/hooks/useTerminalSessions";

// ==========================================
// Types
// ==========================================

export interface XTerminalProps {
  /** Session identifier (provided by useTerminalSessions) */
  sessionId: string;
  /** Send keyboard input to this session */
  sendInput: (sessionId: string, data: string) => void;
  /** Notify the server of a terminal resize */
  resize: (sessionId: string, cols: number, rows: number) => void;
  /** Close this PTY session */
  closeSession: (sessionId: string) => void;
  /**
   * Register a callback to receive output for this session.
   * Returns an unsubscribe function.
   */
  registerOutputCallback: (sessionId: string, cb: (data: string) => void) => () => void;
  /** Whether the WebSocket is currently connected */
  isConnected: boolean;
  /** Current PTY process status */
  sessionStatus: SessionStatus;
  /** Exit code, populated when sessionStatus === 'exited' */
  exitCode?: number;
  /**
   * Called when the user clicks the restart button after the session has exited.
   * The parent is responsible for closing the old session and creating a new one.
   */
  onRestart?: () => void;
  /** Optional CSS class name for the outer container */
  className?: string;
  /** Optional inline styles for the outer container */
  style?: CSSProperties;
  /** Whether the terminal is visible (used to trigger re-fit on tab switch) */
  isVisible?: boolean;
}

// ==========================================
// Theme helpers
// ==========================================

/**
 * Read a CSS variable value from :root via computed styles.
 * Falls back to the provided default value if not available (e.g., during SSR).
 */
function getCssVar(varName: string, fallback: string): string {
  if (typeof document === "undefined") return fallback;
  const value = getComputedStyle(document.documentElement).getPropertyValue(varName).trim();
  return value.length > 0 ? value : fallback;
}

/**
 * Build an xterm.js ITheme object from the current design system CSS variables.
 */
function buildXtermTheme(): Record<string, string> {
  return {
    background: getCssVar("--bg-deep", "#080b12"),
    foreground: getCssVar("--text", "#eef3ff"),
    cursor: getCssVar("--success", "#14b8a6"),
    cursorAccent: getCssVar("--bg-deep", "#080b12"),
    selectionBackground: `${getCssVar("--primary", "#2f80ff")}40`,
    selectionForeground: getCssVar("--text", "#eef3ff"),
    selectionInactiveBackground: `${getCssVar("--muted", "#8f9db7")}30`,
    // Standard ANSI colors mapped to design system
    black: getCssVar("--bg-deep", "#080b12"),
    red: getCssVar("--danger", "#e5484d"),
    green: getCssVar("--success", "#14b8a6"),
    yellow: getCssVar("--warn", "#f59e0b"),
    blue: getCssVar("--primary", "#2f80ff"),
    magenta: getCssVar("--purple", "#8b5cf6"),
    cyan: "#06b6d4",
    white: getCssVar("--text-2", "#c5d0e6"),
    brightBlack: getCssVar("--muted", "#8f9db7"),
    brightRed: "#f06a6f",
    brightGreen: "#2dd4bf",
    brightYellow: "#fbbf24",
    brightBlue: "#56a0ff",
    brightMagenta: "#a78bfa",
    brightCyan: "#22d3ee",
    brightWhite: getCssVar("--text", "#eef3ff"),
  };
}

// ==========================================
// Component
// ==========================================

/**
 * XTerminal renders a real PTY terminal powered by xterm.js.
 * The parent provides the sessionId and control functions; this component
 * registers for output data and manages the xterm.js instance lifecycle.
 */
export function XTerminal({
  sessionId,
  sendInput,
  resize,
  closeSession: _closeSession,
  registerOutputCallback,
  isConnected,
  sessionStatus,
  exitCode,
  onRestart,
  className = "",
  style,
  isVisible = true,
}: XTerminalProps): ReactElement {
  const containerRef = useRef<HTMLDivElement>(null);
  const terminalRef = useRef<XTerm | null>(null);
  const fitAddonRef = useRef<XFitAddon | null>(null);
  const resizeObserverRef = useRef<ResizeObserver | null>(null);
  const isTerminalMountedRef = useRef(false);

  const hasExited = sessionStatus === "exited";

  // ==========================================
  // Fit helper
  // ==========================================

  const fitAndResize = useCallback((): void => {
    const fitAddon = fitAddonRef.current;
    const terminal = terminalRef.current;
    if (!fitAddon || !terminal) return;

    try {
      fitAddon.fit();
      resize(sessionId, terminal.cols, terminal.rows);
    } catch {
      // Ignore fit errors (e.g., when container has zero dimensions)
    }
  }, [resize, sessionId]);

  // ==========================================
  // Mount xterm.js terminal (client-only)
  // ==========================================

  useEffect(() => {
    if (!containerRef.current || isTerminalMountedRef.current) return;

    let cancelled = false;

    const mountTerminal = async (): Promise<void> => {
      // Dynamic imports ensure DOM-dependent xterm.js modules are never loaded server-side
      const [{ Terminal }, { FitAddon }, { WebLinksAddon }] = await Promise.all([
        import("@xterm/xterm"),
        import("@xterm/addon-fit"),
        import("@xterm/addon-web-links"),
      ]);

      if (cancelled || !containerRef.current) return;

      const theme = buildXtermTheme();

      const terminal = new Terminal({
        fontFamily: "var(--mono, 'Fira Code', 'Cascadia Code', monospace)",
        fontSize: 13,
        lineHeight: 1.4,
        cursorBlink: true,
        cursorStyle: "block",
        scrollback: 10000,
        theme,
        allowTransparency: false,
        convertEol: true,
        // Accessibility
        screenReaderMode: false,
      });

      const fitAddon = new FitAddon();
      const webLinksAddon = new WebLinksAddon();

      terminal.loadAddon(fitAddon);
      terminal.loadAddon(webLinksAddon);

      terminal.open(containerRef.current);

      terminalRef.current = terminal;
      fitAddonRef.current = fitAddon;
      isTerminalMountedRef.current = true;

      // Initial fit
      try {
        fitAddon.fit();
      } catch {
        // Container might not have dimensions yet
      }

      // Set up ResizeObserver for automatic re-fitting
      const observer = new ResizeObserver(() => {
        fitAndResize();
      });
      observer.observe(containerRef.current);
      resizeObserverRef.current = observer;
    };

    void mountTerminal();

    return (): void => {
      cancelled = true;
    };
    // Intentionally empty dep array — mount once only
  }, []);

  // ==========================================
  // Register output callback for this session
  // ==========================================

  useEffect(() => {
    const unregister = registerOutputCallback(sessionId, (data: string) => {
      terminalRef.current?.write(data);
    });
    return unregister;
  }, [sessionId, registerOutputCallback]);

  // ==========================================
  // Re-fit when visibility changes
  // ==========================================

  useEffect(() => {
    if (isVisible) {
      // Small delay allows CSS transitions to complete before fitting
      const id = setTimeout(fitAndResize, 50);
      return (): void => {
        clearTimeout(id);
      };
    }
    return undefined;
  }, [isVisible, fitAndResize]);

  // ==========================================
  // Wire terminal input → sendInput
  // ==========================================

  useEffect(() => {
    const terminal = terminalRef.current;
    if (!terminal) return;

    const disposable = terminal.onData((data: string): void => {
      sendInput(sessionId, data);
    });

    return (): void => {
      disposable.dispose();
    };
  }, [sendInput, sessionId]);

  // ==========================================
  // Update xterm theme when data-theme attribute changes
  // ==========================================

  useEffect(() => {
    const observer = new MutationObserver(() => {
      const terminal = terminalRef.current;
      if (terminal) {
        terminal.options.theme = buildXtermTheme();
      }
    });

    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["data-theme"],
    });

    return (): void => {
      observer.disconnect();
    };
  }, []);

  // ==========================================
  // Cleanup on unmount
  // ==========================================

  useEffect(() => {
    return (): void => {
      // Cleanup ResizeObserver
      resizeObserverRef.current?.disconnect();
      resizeObserverRef.current = null;

      // Dispose xterm terminal
      terminalRef.current?.dispose();
      terminalRef.current = null;
      fitAddonRef.current = null;
      isTerminalMountedRef.current = false;
    };
  }, []);

  // ==========================================
  // Restart handler
  // ==========================================

  const handleRestart = useCallback((): void => {
    const terminal = terminalRef.current;
    if (terminal) {
      terminal.clear();
    }
    // Notify parent to close old session and create a new one
    onRestart?.();
  }, [onRestart]);

  // ==========================================
  // Render
  // ==========================================

  const containerStyle: CSSProperties = {
    flex: 1,
    overflow: "hidden",
    position: "relative",
    backgroundColor: "var(--bg-deep)",
    ...style,
  };

  return (
    <div
      className={className}
      style={containerStyle}
      role="region"
      aria-label="Terminal"
      data-testid="xterminal-container"
      data-session-id={sessionId}
    >
      {/* Status bar — show when not connected and not exited */}
      {!isConnected && !hasExited && (
        <div
          style={{
            position: "absolute",
            top: 0,
            left: 0,
            right: 0,
            padding: "4px 12px",
            fontSize: "0.75rem",
            fontFamily: "var(--mono)",
            color: "var(--warn)",
            backgroundColor: "var(--bg-deep)",
            zIndex: 10,
            borderBottom: "1px solid var(--border)",
          }}
        >
          Connecting to terminal...
        </div>
      )}

      {/* Exit overlay */}
      {hasExited && (
        <div
          style={{
            position: "absolute",
            bottom: 8,
            left: 0,
            right: 0,
            display: "flex",
            justifyContent: "center",
            zIndex: 10,
          }}
        >
          <button
            style={{
              padding: "4px 12px",
              borderRadius: "4px",
              fontSize: "0.75rem",
              fontFamily: "var(--mono)",
              color: "var(--text)",
              backgroundColor: "var(--surface)",
              border: "1px solid var(--border)",
              cursor: "pointer",
            }}
            onClick={handleRestart}
          >
            Restart terminal{exitCode !== undefined ? ` (exit ${exitCode.toString()})` : ""}
          </button>
        </div>
      )}

      {/* xterm.js render target */}
      <div
        ref={containerRef}
        style={{
          width: "100%",
          height: "100%",
          padding: "4px 8px",
          boxSizing: "border-box",
        }}
        data-testid="xterm-viewport"
      />
    </div>
  );
}
