import { useCallback, useEffect, useState } from 'react';
import type { Terminal } from '@xterm/xterm';
import { toast } from 'sonner';
import {
  chatInputsRegistry,
  sendToChat,
  terminalsRegistry,
  type ChatInputRegistration,
} from '@/lib/events';
import { cellSize } from './useTerminalFit';

// useTerminalSelection — the touch long-press selection + word-range gesture
// subsystem, the document pointer/contextmenu handlers, clipboard custom keys,
// the terminal + chat-input registries, and the floating-menu actions. All of
// it is independent of the WS/fit path and was kept verbatim from v1.10.4.

const LONG_PRESS_MS = 500;
const LONG_PRESS_TOLERANCE_PX = 10;

function pointToCell(
  term: Terminal,
  container: HTMLElement,
  clientX: number,
  clientY: number,
): { col: number; bufferRow: number } {
  const rect = container.getBoundingClientRect();
  const { w, h } = cellSize(term, container);
  const localX = Math.max(0, clientX - rect.left);
  const localY = Math.max(0, clientY - rect.top);
  const col = Math.min(term.cols - 1, Math.floor(localX / Math.max(w, 1)));
  const screenRow = Math.min(term.rows - 1, Math.floor(localY / Math.max(h, 1)));
  const bufferRow = term.buffer.active.viewportY + screenRow;
  return { col, bufferRow };
}

const WORD_RE = /[\w.~$\-/]+/g;
function wordRangeAt(line: string, col: number): { start: number; end: number } | null {
  for (const m of line.matchAll(WORD_RE)) {
    const start = m.index ?? 0;
    const end = start + m[0].length;
    if (col >= start && col < end) return { start, end };
    if (start > col) return null;
  }
  return null;
}

interface SelectionDeps {
  termRef: React.MutableRefObject<Terminal | null>;
  containerRef: React.MutableRefObject<HTMLDivElement | null>;
  sessionId: string;
  paneId: string;
  label: string;
  send: (text: string) => void;
}

export interface TerminalSelectionActions {
  copy: () => void;
  paste: () => void;
  selectAll: () => void;
  search: () => void;
  sendToChat: (chatId: string) => void;
}

export interface TerminalSelection {
  menu: { x: number; y: number } | null;
  setMenu: React.Dispatch<React.SetStateAction<{ x: number; y: number } | null>>;
  searchOpen: boolean;
  setSearchOpen: React.Dispatch<React.SetStateAction<boolean>>;
  chatInputs: ChatInputRegistration[];
  hasSelection: boolean;
  actions: TerminalSelectionActions;
}

export function useTerminalSelection({
  termRef,
  containerRef,
  sessionId,
  paneId,
  label,
  send,
}: SelectionDeps): TerminalSelection {
  const [menu, setMenu] = useState<{ x: number; y: number } | null>(null);
  const [searchOpen, setSearchOpen] = useState(false);
  const [chatInputs, setChatInputs] = useState<ChatInputRegistration[]>([]);

  const pasteFromClipboard = useCallback((): void => {
    if (!navigator.clipboard || typeof navigator.clipboard.readText !== 'function') {
      toast.error('Paste blocked — long-press input area instead');
      return;
    }
    navigator.clipboard
      .readText()
      .then((text) => {
        if (!text) return;
        send(text);
      })
      .catch(() => {
        toast.error('Paste blocked — long-press input area instead');
      });
  }, [send]);

  useEffect(() => {
    const termInit = termRef.current;
    const ctrInit = containerRef.current;
    if (!termInit || !ctrInit) return;
    // Non-null bindings so the gesture closures below keep the narrowing.
    const term: Terminal = termInit;
    const ctr: HTMLDivElement = ctrInit;

    let disposed = false;

    term.attachCustomKeyEventHandler((e) => {
      if (e.type !== 'keydown') return true;
      const mod = e.ctrlKey || e.metaKey;
      if (!mod) return true;
      const isC = e.key === 'c' || e.key === 'C';
      const isV = e.key === 'v' || e.key === 'V';
      const isF = e.key === 'f' || e.key === 'F';
      if (isC) {
        if (term.hasSelection()) {
          const sel = term.getSelection();
          if (sel) {
            navigator.clipboard.writeText(sel).catch(() => {
              toast.error('Clipboard write failed');
            });
          }
          return false;
        }
        return !e.shiftKey;
      }
      if (isV) {
        pasteFromClipboard();
        return false;
      }
      if (isF) {
        setSearchOpen(true);
        return false;
      }
      return true;
    });

    // Long-press selection + floating menu (touch).
    let lpTimer: ReturnType<typeof setTimeout> | null = null;
    let lpStart: { x: number; y: number } | null = null;
    let lpAnchor: { col: number; bufferRow: number } | null = null;
    let inSelection = false;
    function clearLp(): void {
      if (lpTimer !== null) {
        clearTimeout(lpTimer);
        lpTimer = null;
      }
      lpStart = null;
    }
    function selectWord(col: number, bufferRow: number): boolean {
      const line = term.buffer.active.getLine(bufferRow);
      if (!line) return false;
      const text = line.translateToString(true);
      const range = wordRangeAt(text, col);
      if (!range) return false;
      term.select(range.start, bufferRow, range.end - range.start);
      return true;
    }
    function extendSelection(
      anchor: { col: number; bufferRow: number },
      to: { col: number; bufferRow: number },
    ): void {
      const a = anchor;
      const b = to;
      let s: { col: number; row: number };
      let e: { col: number; row: number };
      if (a.bufferRow < b.bufferRow || (a.bufferRow === b.bufferRow && a.col <= b.col)) {
        s = { col: a.col, row: a.bufferRow };
        e = { col: b.col, row: b.bufferRow };
      } else {
        s = { col: b.col, row: b.bufferRow };
        e = { col: a.col, row: a.bufferRow };
      }
      const rowsBetween = e.row - s.row;
      const length = rowsBetween * term.cols + (e.col - s.col) + 1;
      term.select(s.col, s.row, length);
    }
    function onTouchStart(e: TouchEvent): void {
      if (e.touches.length !== 1) return;
      const t = e.touches[0]!;
      if ((e.target as Element | null)?.closest('[data-term-menu]')) return;
      lpStart = { x: t.clientX, y: t.clientY };
      lpAnchor = pointToCell(term, ctr, t.clientX, t.clientY);
      inSelection = false;
      lpTimer = setTimeout(() => {
        if (disposed || !lpAnchor) return;
        const ok = selectWord(lpAnchor.col, lpAnchor.bufferRow);
        if (!ok) return;
        inSelection = true;
        setMenu({ x: t.clientX, y: Math.max(t.clientY - 50, 8) });
      }, LONG_PRESS_MS);
    }
    function onTouchMove(e: TouchEvent): void {
      if (e.touches.length !== 1) return;
      const t = e.touches[0]!;
      if (inSelection && lpAnchor) {
        e.preventDefault();
        const to = pointToCell(term, ctr, t.clientX, t.clientY);
        extendSelection(lpAnchor, to);
        return;
      }
      if (!lpStart) return;
      const dx = t.clientX - lpStart.x;
      const dy = t.clientY - lpStart.y;
      if (Math.abs(dx) > LONG_PRESS_TOLERANCE_PX || Math.abs(dy) > LONG_PRESS_TOLERANCE_PX) {
        clearLp();
      }
    }
    function onTouchEnd(): void {
      if (!inSelection) clearLp();
      inSelection = false;
    }
    function onTouchCancel(): void {
      clearLp();
      inSelection = false;
    }
    ctr.addEventListener('touchstart', onTouchStart, { passive: true });
    ctr.addEventListener('touchmove', onTouchMove, { passive: false });
    ctr.addEventListener('touchend', onTouchEnd, { passive: true });
    ctr.addEventListener('touchcancel', onTouchCancel, { passive: true });

    function onContextMenu(e: MouseEvent): void {
      e.preventDefault();
      setMenu({ x: e.clientX, y: e.clientY });
    }
    ctr.addEventListener('contextmenu', onContextMenu);

    function onDocPointerDown(e: PointerEvent): void {
      const t = e.target as Element | null;
      if (t && t.closest('[data-term-menu]')) return;
      setMenu(null);
    }
    document.addEventListener('pointerdown', onDocPointerDown);

    return () => {
      disposed = true;
      if (lpTimer !== null) clearTimeout(lpTimer);
      document.removeEventListener('pointerdown', onDocPointerDown);
      ctr.removeEventListener('touchstart', onTouchStart);
      ctr.removeEventListener('touchmove', onTouchMove);
      ctr.removeEventListener('touchend', onTouchEnd);
      ctr.removeEventListener('touchcancel', onTouchCancel);
      ctr.removeEventListener('contextmenu', onContextMenu);
      // attachCustomKeyEventHandler has no disposable; term.dispose() drops it.
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [sessionId, paneId, send, pasteFromClipboard]);

  // Terminal registry: focus / openSearch / paste hooks for keyboard shortcuts
  // (Session.tsx) and the pane-header paste button (Workspace.tsx). Keyed on
  // [paneId, label] ONLY — NOT on sessionId — so a positional `label` renumber
  // ("Terminal N" after a pane add/remove) refreshes the registry entry
  // WITHOUT tearing down + reconnecting the live terminal. The focus closure
  // reads termRef lazily, so it tracks a terminal recreated on a session change
  // without needing this effect to re-run. (v2 Phase 9 latent-bug fix: `label`
  // was previously in the xterm-init effect's dep array.)
  useEffect(() => {
    const unregister = terminalsRegistry.register(
      paneId,
      label,
      () => {
        try {
          termRef.current?.focus();
        } catch {
          /* ignore */
        }
      },
      () => setSearchOpen(true),
      () => pasteFromClipboard(),
    );
    return () => unregister();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [paneId, label, pasteFromClipboard]);

  // Chat-input registry: populates the floating menu's "Send to chat" submenu.
  // Independent of session/pane/term — subscribe once.
  useEffect(() => {
    setChatInputs(chatInputsRegistry.list());
    const unsubChats = chatInputsRegistry.subscribe(() => {
      setChatInputs(chatInputsRegistry.list());
    });
    return () => unsubChats();
  }, []);

  function actCopy(): void {
    const term = termRef.current;
    if (!term) return;
    const sel = term.getSelection();
    if (!sel) {
      setMenu(null);
      return;
    }
    navigator.clipboard.writeText(sel).catch(() => toast.error('Clipboard write failed'));
    term.clearSelection();
    setMenu(null);
  }
  function actPaste(): void {
    const reg = terminalsRegistry.get(paneId);
    reg?.paste();
    setMenu(null);
  }
  function actSelectAll(): void {
    termRef.current?.selectAll();
    setMenu(null);
  }
  function actSearch(): void {
    setSearchOpen(true);
    setMenu(null);
  }
  function actSendToChat(chatId: string): void {
    const term = termRef.current;
    if (!term) return;
    const sel = term.getSelection();
    if (!sel) {
      setMenu(null);
      return;
    }
    sendToChat.emit({ chat_id: chatId, text: sel });
    setMenu(null);
  }

  const hasSelection = !!termRef.current?.getSelection();

  return {
    menu,
    setMenu,
    searchOpen,
    setSearchOpen,
    chatInputs,
    hasSelection,
    actions: {
      copy: actCopy,
      paste: actPaste,
      selectAll: actSelectAll,
      search: actSearch,
      sendToChat: actSendToChat,
    },
  };
}
