import { useCallback, useRef, useState } from "preact/hooks";
import type { AgentState } from "../types";

const HOLD_MS = 300;
const MOVE_CANCEL_PX = 16;

interface UsePTTOptions {
  connected: boolean;
  currentAgentState: AgentState;
  onSendPtt(pressed: boolean): void;
  onBootstrap(): Promise<void>;
  onInterrupt?(): void;
  onTap?(): void; // called on a short press (< HOLD_MS) that didn't activate PTT
}

interface PTTState {
  agentStateOverride: AgentState | null;
  handlePointerDown(e: Event): Promise<void>;
  handlePointerMove(e: Event): void;
  handlePointerUp(e: Event): void;
}

function dispatchMicEnable(enabled: boolean): void {
  window.dispatchEvent(new CustomEvent("nanobot-mic-enable", { detail: { enabled } }));
}

/** Manages push-to-talk pointer events and mic enable/disable. */
export function usePTT({
  connected,
  currentAgentState,
  onSendPtt,
  onBootstrap,
  onInterrupt,
  onTap,
}: UsePTTOptions): PTTState {
  const [agentStateOverride, setAgentStateOverride] = useState<AgentState | null>(null);
  const activePointers = useRef(new Set<number>());
  const appStartedRef = useRef(false);
  const holdTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  const pttFiredRef = useRef(false);
  const pointerStartRef = useRef<{ x: number; y: number } | null>(null);
  const beginPTT = useCallback(() => {
    if (!connected) return;
    if (agentStateOverride === "listening") return;
    if (currentAgentState === "thinking" || currentAgentState === "speaking") {
      onInterrupt?.();
    }
    pttFiredRef.current = true;
    setAgentStateOverride("listening");
    dispatchMicEnable(true);
    onSendPtt(true);
  }, [connected, agentStateOverride, currentAgentState, onInterrupt, onSendPtt]);
  const endPTT = useCallback(() => {
    if (agentStateOverride !== "listening") return;
    setAgentStateOverride(null);
    dispatchMicEnable(false);
    onSendPtt(false);
    window.dispatchEvent(
      new CustomEvent("nanobot-show-status", {
        detail: { text: "Hold anywhere to talk", persistMs: 1800 },
      }),
    );
  }, [agentStateOverride, onSendPtt]);

  const handlePointerDown = useCallback(
    async (e: Event) => {
      const pe = e as PointerEvent;
      if (!(pe.target instanceof Element) || !pe.target.closest("[data-ptt='1']")) return;
      activePointers.current.add(pe.pointerId);
      if (!appStartedRef.current) {
        appStartedRef.current = true;
        await onBootstrap();
      }
      if (activePointers.current.size !== 1) return;
      pttFiredRef.current = false;
      pointerStartRef.current = { x: pe.clientX, y: pe.clientY };
      holdTimerRef.current = setTimeout(beginPTT, HOLD_MS);
    },
    [onBootstrap, beginPTT],
  );

  const handlePointerMove = useCallback((e: Event) => {
    if (pttFiredRef.current) return;
    if (holdTimerRef.current === null) return;
    const pe = e as PointerEvent;
    if (!activePointers.current.has(pe.pointerId)) return;
    const start = pointerStartRef.current;
    if (!start) return;

    const dx = Math.abs(pe.clientX - start.x);
    const dy = Math.abs(pe.clientY - start.y);
    if (dx > MOVE_CANCEL_PX || dy > MOVE_CANCEL_PX) {
      clearTimeout(holdTimerRef.current);
      holdTimerRef.current = null;
    }
  }, []);

  const handlePointerUp = useCallback(
    (e: Event) => {
      const pe = e as PointerEvent;
      // Ignore pointers we never tracked (didn't hit a data-ptt target on down)
      if (!activePointers.current.has(pe.pointerId)) return;
      activePointers.current.delete(pe.pointerId);
      if (activePointers.current.size !== 0) return;
      if (holdTimerRef.current !== null) {
        clearTimeout(holdTimerRef.current);
        holdTimerRef.current = null;
      }
      pointerStartRef.current = null;
      if (pttFiredRef.current) {
        endPTT();
      } else {
        onTap?.();
      }
    },
    [endPTT, onTap],
  );

  return { agentStateOverride, handlePointerDown, handlePointerMove, handlePointerUp };
}
