import { forwardRef, useEffect, useImperativeHandle, useRef } from 'react';
import { Terminal } from '@xterm/xterm';
import { FitAddon } from '@xterm/addon-fit';
import '@xterm/xterm/css/xterm.css';
import type { ConsoleSessionApi } from '../../../../hooks/useConsoleSession';

export interface TerminalViewHandle {
  scrollToBottom: () => void;
  isScrolledUp: () => boolean;
}

/**
 * xterm.js wrapper. The xterm instance is created **once** on mount and
 * disposed on unmount — never re-created when `session` identity changes.
 * Re-creating xterm would clear the screen, so we keep the latest session
 * reference in a ref and read from it inside the long-lived event handlers.
 *
 * Fit strategy: ResizeObserver on the container is the source of truth.
 */
export const TerminalView = forwardRef<TerminalViewHandle, { session: ConsoleSessionApi }>(
  function TerminalView({ session }, ref) {
    const containerRef = useRef<HTMLDivElement | null>(null);
    const sessionRef = useRef(session);
    const termRef = useRef<Terminal | null>(null);

    useEffect(() => {
      sessionRef.current = session;
    }, [session]);

    useImperativeHandle(
      ref,
      () => ({
        scrollToBottom: () => termRef.current?.scrollToBottom(),
        isScrolledUp: () => {
          const t = termRef.current;
          if (!t) return false;
          return t.buffer.active.viewportY < t.buffer.active.length - t.rows;
        },
      }),
      [],
    );

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

      const term = new Terminal({
        cursorBlink: true,
        fontFamily: 'monospace',
        fontSize: 13,
        theme: {
          background: '#0b1020',
          selectionBackground: '#3b82f6',
          selectionForeground: '#fff',
        },
        scrollback: 5000,
      });
      termRef.current = term;
      const fit = new FitAddon();
      term.loadAddon(fit);
      term.open(el);

      const tryFit = () => {
        if (el.clientWidth < 4 || el.clientHeight < 4) return;
        try {
          fit.fit();
        } catch {
          /* swallow; ResizeObserver will retry */
        }
      };

      const offData = term.onData((d) => sessionRef.current.send(d));
      const offResize = term.onResize(({ cols, rows }) => sessionRef.current.sendResize(cols, rows));
      const unsubOutput = sessionRef.current.onOutput((bytes) => term.write(bytes));
      const offSelection = term.onSelectionChange(() => {
        const text = term.getSelection();
        if (!text) return;
        // Silent failure on non-HTTPS or when clipboard permission is denied.
        // Don't await — fire-and-forget; selection events fire frequently.
        navigator.clipboard?.writeText(text).catch(() => undefined);
      });

      const ro = new ResizeObserver(() => tryFit());
      ro.observe(el);
      window.addEventListener('resize', tryFit);

      requestAnimationFrame(tryFit);

      return () => {
        offData.dispose();
        offResize.dispose();
        offSelection.dispose();
        unsubOutput();
        ro.disconnect();
        window.removeEventListener('resize', tryFit);
        term.dispose();
        termRef.current = null;
      };
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    return <div ref={containerRef} className="h-full w-full bg-[#0b1020]" />;
  },
);
