import {
  useState,
  useEffect,
  useRef,
  useCallback,
  type KeyboardEvent,
  type MouseEvent,
  type PointerEvent as ReactPointerEvent,
} from "react";
import { type LeafNode, resolveFontSize } from "./tree";
import { useOrchestration } from "./orchestration";
import XtermPane from "../../components/XtermPane";
import "./LeafPane.css";

const IDLE_THRESHOLD_MS = 5000;

export default function LeafPane({ leaf }: { leaf: LeafNode }) {
  const orch = useOrchestration();
  const isActive = orch.activeLeafId === leaf.id;
  const isBroadcasting = !!leaf.broadcast;

  // ---- status (from XtermPane) -------------------------------------------
  const [status, setStatus] = useState("starting…");
  const [statusOk, setStatusOk] = useState(true);

  // ---- label editing -----------------------------------------------------
  const [editingLabel, setEditingLabel] = useState(false);
  const [labelDraft, setLabelDraft] = useState("");
  const labelInputRef = useRef<HTMLInputElement | null>(null);

  const startEditLabel = useCallback(
    (e: MouseEvent) => {
      e.stopPropagation();
      setLabelDraft(leaf.label ?? "");
      setEditingLabel(true);
      // Focus on next tick so input is mounted
      queueMicrotask(() => labelInputRef.current?.select());
    },
    [leaf.label],
  );
  const commitLabel = useCallback(() => {
    if (!editingLabel) return;
    orch.setLabel(leaf.id, labelDraft);
    setEditingLabel(false);
  }, [editingLabel, orch.setLabel, leaf.id, labelDraft]);
  const cancelLabel = useCallback(() => setEditingLabel(false), []);
  const onLabelKey = useCallback(
    (e: KeyboardEvent<HTMLInputElement>) => {
      if (e.key === "Enter") {
        e.preventDefault();
        commitLabel();
      } else if (e.key === "Escape") {
        e.preventDefault();
        cancelLabel();
      }
    },
    [commitLabel, cancelLabel],
  );

  // ---- distro popover ----------------------------------------------------
  const [distroOpen, setDistroOpen] = useState(false);
  const toggleDistroMenu = useCallback((e: MouseEvent) => {
    e.stopPropagation();
    setDistroOpen((v) => !v);
  }, []);
  const pickDistro = useCallback(
    (d: string) => {
      setDistroOpen(false);
      if (d !== leaf.distro) orch.setDistro(leaf.id, d);
    },
    [orch.setDistro, leaf.id, leaf.distro],
  );
  // Dismiss popover on outside click
  useEffect(() => {
    if (!distroOpen) return;
    const onDocClick = () => setDistroOpen(false);
    window.addEventListener("click", onDocClick);
    return () => window.removeEventListener("click", onDocClick);
  }, [distroOpen]);

  // ---- idle detection ----------------------------------------------------
  // Local boolean for the red border + status text on this pane; reported
  // up to App via orch.reportLeafIdle for the titlebar's "N idle" badge.
  const lastDataTimeRef = useRef(Date.now());
  const [isIdle, setIsIdle] = useState(false);
  const onDataReceived = useCallback(() => {
    lastDataTimeRef.current = Date.now();
    setIsIdle((cur) => {
      if (cur) orch.reportLeafIdle(leaf.id, false);
      return false;
    });
  }, [orch.reportLeafIdle, leaf.id]);
  useEffect(() => {
    const id = window.setInterval(() => {
      const dt = Date.now() - lastDataTimeRef.current;
      const nowIdle = dt >= IDLE_THRESHOLD_MS;
      setIsIdle((cur) => {
        if (cur === nowIdle) return cur;
        orch.reportLeafIdle(leaf.id, nowIdle);
        return nowIdle;
      });
    }, 1000);
    return () => clearInterval(id);
  }, [leaf.id, orch.reportLeafIdle]);
  // Clear from the app-level idle set when this pane unmounts.
  useEffect(() => {
    return () => orch.reportLeafIdle(leaf.id, false);
  }, [leaf.id, orch.reportLeafIdle]);

  // ---- broadcast ---------------------------------------------------------
  const onTerminalInput = useCallback(
    (b64: string) => {
      if (isBroadcasting) orch.broadcastFrom(leaf.id, b64);
    },
    [isBroadcasting, orch.broadcastFrom, leaf.id],
  );

  // ---- focus / active highlighting ---------------------------------------
  const [focusTrigger, setFocusTrigger] = useState(0);
  // When this leaf becomes active, bump focusTrigger so XtermPane refocuses.
  useEffect(() => {
    if (isActive) setFocusTrigger((n) => n + 1);
  }, [isActive]);

  const onPaneClick = useCallback(() => {
    orch.setActive(leaf.id);
  }, [orch.setActive, leaf.id]);

  const onPaneSpawned = useCallback(
    (paneId: number) => {
      orch.registerPaneId(leaf.id, paneId);
    },
    [orch.registerPaneId, leaf.id],
  );
  // Unregister on TRUE unmount only — depending on `orch` here would
  // delete the paneId from App's lookup on every activeLeafId change,
  // which broke broadcast routing (peers found, but their paneIds
  // had been silently removed from the map).
  useEffect(() => {
    return () => orch.registerPaneId(leaf.id, null);
  }, [orch.registerPaneId, leaf.id]);

  const onXtermFocus = useCallback(
    () => orch.setActive(leaf.id),
    [orch.setActive, leaf.id],
  );

  const onStatus = useCallback((msg: string, ok: boolean) => {
    setStatus(msg);
    setStatusOk(ok);
  }, []);

  // ---- header-drag swap ---------------------------------------------------
  // Drag the toolbar onto another pane's toolbar/body to swap their tree
  // positions. Uses a movement threshold so accidental tiny moves while
  // clicking a label etc don't initiate a drag.
  const DRAG_THRESHOLD_PX = 5;
  const dragStartRef = useRef<{ x: number; y: number; armed: boolean; dragging: boolean } | null>(
    null,
  );
  const isDragSource = orch.dragSourceId === leaf.id;
  const isDragTarget =
    orch.dragOverId === leaf.id && orch.dragSourceId !== leaf.id;

  const onToolbarPointerDown = useCallback(
    (e: ReactPointerEvent<HTMLDivElement>) => {
      const target = e.target as HTMLElement;
      // Skip if the click landed on an interactive child.
      if (target.closest("button, input, .distro-menu")) return;
      (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
      dragStartRef.current = {
        x: e.clientX,
        y: e.clientY,
        armed: true,
        dragging: false,
      };
      // Make this pane active (since clicking the toolbar should focus it).
      orch.setActive(leaf.id);
    },
    [orch.setActive, leaf.id],
  );

  const onToolbarPointerMove = useCallback(
    (e: ReactPointerEvent<HTMLDivElement>) => {
      const st = dragStartRef.current;
      if (!st || !st.armed) return;
      const dx = e.clientX - st.x;
      const dy = e.clientY - st.y;
      if (!st.dragging) {
        if (Math.hypot(dx, dy) < DRAG_THRESHOLD_PX) return;
        st.dragging = true;
        orch.beginHeaderDrag(leaf.id);
        document.body.style.cursor = "grabbing";
      }
      // Find the leaf under the cursor.
      const el = document.elementFromPoint(e.clientX, e.clientY);
      const tEl = el?.closest("[data-leaf-id]");
      const targetId = tEl?.getAttribute("data-leaf-id") ?? null;
      orch.setHeaderDragOver(targetId);
    },
    [orch.beginHeaderDrag, orch.setHeaderDragOver, leaf.id],
  );

  const onToolbarPointerUp = useCallback(
    (e: ReactPointerEvent<HTMLDivElement>) => {
      const st = dragStartRef.current;
      if (!st) return;
      (e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);
      const wasDragging = st.dragging;
      dragStartRef.current = null;
      if (wasDragging) {
        document.body.style.cursor = "";
        orch.endHeaderDrag(true);
      }
    },
    [orch.endHeaderDrag],
  );

  const onToolbarPointerCancel = useCallback(
    (e: ReactPointerEvent<HTMLDivElement>) => {
      const st = dragStartRef.current;
      if (!st) return;
      (e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);
      const wasDragging = st.dragging;
      dragStartRef.current = null;
      if (wasDragging) {
        document.body.style.cursor = "";
        orch.endHeaderDrag(false);
      }
    },
    [orch.endHeaderDrag],
  );

  const labelText = leaf.label ?? "(unnamed)";

  return (
    <div
      className={`leaf${isActive ? " active" : ""}${isBroadcasting ? " broadcasting" : ""}${isIdle ? " idle" : ""}${isDragSource ? " drag-source" : ""}${isDragTarget ? " drag-target" : ""}`}
      role="group"
      aria-label={`Terminal pane: ${leaf.label ?? leaf.distro ?? "unnamed"}`}
      data-leaf-id={leaf.id}
      onPointerDown={onPaneClick}
    >
      <div
        className="pane-toolbar"
        onPointerDown={onToolbarPointerDown}
        onPointerMove={onToolbarPointerMove}
        onPointerUp={onToolbarPointerUp}
        onPointerCancel={onToolbarPointerCancel}
      >
        {editingLabel ? (
          <input
            ref={labelInputRef}
            className="label-input"
            value={labelDraft}
            onChange={(e) => setLabelDraft(e.target.value)}
            onKeyDown={onLabelKey}
            onBlur={commitLabel}
            placeholder="(label)"
          />
        ) : (
          <button
            className="pane-label"
            onClick={startEditLabel}
            title="Click to rename pane"
          >
            {labelText}
          </button>
        )}

        <span className="distro-wrap">
          <button
            className="distro-chip"
            onClick={toggleDistroMenu}
            title="Change distro (respawns the pane)"
          >
            {leaf.distro ?? "(default)"} ▾
          </button>
          {distroOpen && (
            <div
              className="distro-menu"
              role="menu"
              onClick={(e) => e.stopPropagation()}
            >
              {orch.distros.map((d) => (
                <button
                  key={d}
                  className={`distro-menu-item${d === leaf.distro ? " active" : ""}`}
                  onClick={() => pickDistro(d)}
                >
                  {d}
                </button>
              ))}
            </div>
          )}
        </span>

        <button
          className={`bcast-chip${isBroadcasting ? " on" : ""}`}
          onClick={(e) => {
            e.stopPropagation();
            orch.toggleBroadcast(leaf.id);
          }}
          title={
            isBroadcasting
              ? "Broadcasting (click or Ctrl+Shift+B to leave group)"
              : "Click or Ctrl+Shift+B to broadcast input to other broadcast panes"
          }
          aria-pressed={isBroadcasting ? "true" : "false"}
        >
          📡
        </button>

        {isIdle && statusOk ? (
          <span className="pane-status idle" title={`No output for ${IDLE_THRESHOLD_MS / 1000}s+`}>
            idle
          </span>
        ) : (
          <span className={`pane-status ${statusOk ? "ok" : "err"}`}>{status}</span>
        )}

        <span className="pane-actions">
          <button
            className="pane-btn"
            title="Split right (Ctrl+Shift+E)"
            onClick={(e) => {
              e.stopPropagation();
              orch.split(leaf.id, "h");
            }}
            aria-label="Split right"
          >
            ⇥
          </button>
          <button
            className="pane-btn"
            title="Split down (Ctrl+Shift+O)"
            onClick={(e) => {
              e.stopPropagation();
              orch.split(leaf.id, "v");
            }}
            aria-label="Split down"
          >
            ⇣
          </button>
          <button
            className="pane-btn close"
            title="Close pane (Ctrl+Shift+W)"
            onClick={(e) => {
              e.stopPropagation();
              orch.close(leaf.id);
            }}
            aria-label="Close pane"
          >
            ×
          </button>
        </span>
      </div>
      <div className="xterm-wrap">
        <XtermPane
          distro={leaf.distro}
          cwd={leaf.cwd}
          onStatus={onStatus}
          onSpawn={onPaneSpawned}
          onInput={onTerminalInput}
          onDataReceived={onDataReceived}
          onFocus={onXtermFocus}
          focusTrigger={focusTrigger}
          fontSize={resolveFontSize(leaf.fontSizeOffset)}
        />
      </div>
    </div>
  );
}
