import { FitAddon } from "@xterm/addon-fit";
import { Terminal } from "@xterm/xterm";
import { useEffect, useRef, useState } from "react";

import { Button } from "@/components/ui/button";
import {
  useShellStart,
  useShellStatus,
  useShellWebSocket,
} from "@/lib/api/process";

interface TerminalPanelProps {
  workspaceId: string | null;
}

export function TerminalPanel({ workspaceId }: TerminalPanelProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const terminalRef = useRef<Terminal | null>(null);
  const fitAddonRef = useRef<FitAddon | null>(null);
  const hasWelcomedRef = useRef(false);
  const isDeadRef = useRef(false);
  const [failed, setFailed] = useState(false);
  const [isDead, setIsDead] = useState(false);
  const { data: shellStatus } = useShellStatus(workspaceId);
  const shellRunning = shellStatus?.running === true;
  const { status, send, onData } = useShellWebSocket(workspaceId, shellRunning);
  const startShell = useShellStart();

  // Initialize xterm once and keep it alive across WS status changes.
  useEffect(() => {
    const container = containerRef.current;
    if (!container) return;

    let terminal: Terminal | undefined;
    let fitAddon: FitAddon | undefined;

    try {
      terminal = new Terminal({
        cursorBlink: true,
        convertEol: true,
        // Prevent Mac Option+key from emitting ESC. Without this, typing
        // Option+[ etc. leaks a literal 0x1b into the shell's stdin, which
        // can break downstream programs that don't interpret escape
        // sequences (e.g. Python's REPL reports "invalid non-printable
        // character U+001B").
        macOptionIsMeta: false,
        fontFamily:
          'Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
        fontSize: 12,
        theme: {
          background: "#171717",
          foreground: "#e5e5e5",
        },
      });
      fitAddon = new FitAddon();
      terminal.loadAddon(fitAddon);
      terminal.open(container);
      terminalRef.current = terminal;
      fitAddonRef.current = fitAddon;
    } catch {
      setFailed(true);
      terminal?.dispose();
      fitAddon?.dispose();
      return;
    }

    let rafId: number | undefined;
    const scheduleFit = () => {
      if (rafId !== undefined) return;
      rafId = requestAnimationFrame(() => {
        rafId = undefined;
        try {
          fitAddon?.fit();
        } catch {
          // fit() throws when the container has zero dimensions; ignore
          // and wait for the next ResizeObserver tick.
        }
      });
    };

    const resizeObserver = new ResizeObserver(scheduleFit);
    resizeObserver.observe(container);

    const handleResize = () => {
      scheduleFit();
    };

    window.addEventListener("resize", handleResize);

    return () => {
      if (rafId !== undefined) {
        cancelAnimationFrame(rafId);
      }
      resizeObserver.disconnect();
      window.removeEventListener("resize", handleResize);
      terminal?.dispose();
      fitAddon?.dispose();
      terminalRef.current = null;
      fitAddonRef.current = null;
    };
  }, []);

  // Reset per-workspace state and wire the active socket to xterm.
  useEffect(() => {
    const terminal = terminalRef.current;
    if (!terminal || !workspaceId) return;

    hasWelcomedRef.current = false;
    isDeadRef.current = false;
    setIsDead(false);
    // Clear the previous workspace's scrollback so the user does not see
    // shell output from another workspace in the new one.
    terminal.clear();
    terminal.reset();

    const removeInputHandler = terminal.onData((input) => {
      if (!isDeadRef.current) {
        send(input);
      }
    }).dispose;

    const unsubscribeData = onData((chunk) => {
      terminal.write(chunk);
      if (chunk.startsWith("[process exited")) {
        isDeadRef.current = true;
        setIsDead(true);
      }
    });

    return () => {
      removeInputHandler();
      unsubscribeData();
    };
  }, [workspaceId, send, onData]);

  // Print a one-time welcome banner the first time the socket opens.
  useEffect(() => {
    const terminal = terminalRef.current;
    if (!terminal || !workspaceId || status !== "open") return;
    if (hasWelcomedRef.current) return;
    hasWelcomedRef.current = true;
    terminal.write(`\r\n[connected to ${workspaceId}]\r\n$ `);
  }, [status, workspaceId]);

  if (!workspaceId) {
    return (
      <section className="flex h-full w-full items-center justify-center bg-background text-sm text-muted-foreground">
        Select a workspace to use the terminal.
      </section>
    );
  }

  if (failed) {
    return (
      <section className="flex h-full w-full items-center justify-center bg-background text-sm text-muted-foreground">
        Terminal failed to initialize
      </section>
    );
  }

  const statusHint = isDead
    ? "shell exited"
    : status === "error"
      ? "terminal disconnected"
      : status === "connecting"
        ? "connecting…"
        : null;

  const hint = shellRunning
    ? statusHint
    : "shell not running - start it to use the terminal";

  return (
    <section className="flex h-full w-full flex-col overflow-hidden bg-[#171717]">
      {hint && (
        <div className="flex h-6 shrink-0 items-center gap-2 px-2 text-[11px] text-muted-foreground">
          {hint}
          {!shellRunning && (
            <Button
              variant="ghost"
              size="sm"
              className="h-5 px-1.5 text-[11px]"
              onClick={() => workspaceId && startShell.mutate(workspaceId)}
              disabled={!workspaceId || startShell.isPending}
            >
              {startShell.isPending ? "starting…" : "Start shell"}
            </Button>
          )}
        </div>
      )}
      <div ref={containerRef} className="min-h-0 flex-1 p-2" />
    </section>
  );
}
