import { useEffect, useRef, useState } from "react";
import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import "@xterm/xterm/css/xterm.css";
import type { TerminalCard } from "../types";
import { useViewport } from "../viewport";
import { useDragHandle } from "../useDragHandle";
import { ResizeHandles } from "./ResizeHandles";
import { pty, base64ToBytes, stringToBase64 } from "../../ipc";

const BASE_FONT_SIZE = 13;

interface Props {
  card: TerminalCard;
  onUpdate: (patch: Partial<TerminalCard>) => void;
}

export function TerminalCardView({ card, onUpdate }: Props) {
  const vp = useViewport();
  const drag = useDragHandle(card, (p) => onUpdate(p));
  const containerRef = useRef<HTMLDivElement>(null);
  const termRef = useRef<Terminal | null>(null);
  const fitRef = useRef<FitAddon | null>(null);
  const ptyIdRef = useRef<string>(card.ptyId);
  const [ready, setReady] = useState(false);

  useEffect(() => {
    let unlistenData: (() => void) | undefined;
    let unlistenExit: (() => void) | undefined;
    let disposed = false;

    const term = new Terminal({
      fontSize: BASE_FONT_SIZE * vp.scale,
      fontFamily: 'Menlo, "Cascadia Code", Consolas, monospace',
      theme: {
        background: "#1d1d23",
        foreground: "#e8e8ec",
        cursor: "#e8e8ec",
      },
      cursorBlink: true,
      allowProposedApi: true,
    });
    const fit = new FitAddon();
    term.loadAddon(fit);
    term.open(containerRef.current!);
    fit.fit();
    termRef.current = term;
    fitRef.current = fit;

    (async () => {
      let id = ptyIdRef.current;
      if (!id) {
        id = await pty.spawn({ cols: term.cols, rows: term.rows });
        if (disposed) {
          await pty.kill(id);
          return;
        }
        ptyIdRef.current = id;
        onUpdate({ ptyId: id });
      }

      unlistenData = await pty.onData((e) => {
        if (e.id !== id) return;
        term.write(base64ToBytes(e.data));
      });
      unlistenExit = await pty.onExit((e) => {
        if (e.id !== id) return;
        term.write("\r\n\x1b[2m[process exited]\x1b[0m\r\n");
      });

      term.onData((data) => {
        pty.write(id!, stringToBase64(data)).catch((err) => console.error("pty_write", err));
      });
      term.onResize(({ cols, rows }) => {
        pty.resize(id!, cols, rows).catch(() => {});
      });

      setReady(true);
    })();

    return () => {
      disposed = true;
      unlistenData?.();
      unlistenExit?.();
      term.dispose();
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Update xterm font size when canvas zoom changes so text stays crisp.
  useEffect(() => {
    if (!ready) return;
    const term = termRef.current;
    const fit = fitRef.current;
    if (!term || !fit) return;
    term.options.fontSize = BASE_FONT_SIZE * vp.scale;
    fit.fit();
  }, [vp.scale, ready]);

  // Refit on card resize.
  useEffect(() => {
    if (!ready) return;
    fitRef.current?.fit();
  }, [card.width, card.height, ready]);

  return (
    <div
      className="card terminal-card"
      style={
        {
          left: vp.x + card.x * vp.scale,
          top: vp.y + card.y * vp.scale,
          width: card.width * vp.scale,
          height: card.height * vp.scale,
          zIndex: card.z,
          "--scale": vp.scale,
        } as React.CSSProperties
      }
    >
      <div className="card-header" {...drag}>
        terminal
      </div>
      <div
        className="card-body terminal-body"
        ref={containerRef}
        onPointerDown={(e) => e.stopPropagation()}
        onWheel={(e) => e.stopPropagation()}
      />
      <ResizeHandles card={card} onUpdate={(p) => onUpdate(p as Partial<TerminalCard>)} />
    </div>
  );
}
