/**
 * Pointer-Input: alle pointer-Listener (enter/leave/down/up/move) + updateMouseFromEvent.
 *
 * Mutiert die shared Vector2 `mouse` (NDC für Raycaster) und das shared `lastMouse`-Object
 * (Pixel-Koordinaten für Tooltip-Platzierung). Touch: Tooltip-Pin erst bei pointerup
 * (Tap mit wenig Bewegung); größere Bewegung = Rotieren → kein neuer Pin, bestehender Pin wird gelöscht.
 *
 * Benannte Listener-Funktionen sind nötig, weil beim Viewer-Sort-Reinit cleanup() läuft und
 * nur benannte Referenzen wieder entfernt werden können — anonyme Listener würden Closures
 * auf die alte Scene/Renderer halten → Speicherleck.
 *
 * Diagram-Hold unterdrückt Hover-Tooltip (state.diagramHoldSuppressesTooltip): jeder Pointer,
 * dessen Down auf diesem Container gestartet wurde, bis zum zugehörigen pointerup/-cancel —
 * wie früher nur für Touch, jetzt auch Maus/Stift beim Orbit-Ziehen.
 *
 * `pointerdown` auf dem Container in Capture-Phase: OrbitControls (Canvas) kann in der
 * Zielphase `stopPropagation` nutzen — ohne Capture erreicht der Bubble den Container nicht.
 */
import { clearDiagramWakeSchedule } from './cePdTimerHub.js';

export function createPointerInput(ctx) {
  const {
    state, container, ownerDoc, ownCanvas,
    mouse, lastMouse,
    showIcon, markDirty, wake,
    pickAtCurrentMouse, tooltipCtl,
    DAMPING_SETTLE_FRAMES,
    /** Frontend: Klick/Tap auf einen Balken/Slice mit `link` navigiert dorthin.
     *  Im Editor-Preview immer false (kein Navigieren beim Bearbeiten). */
    enableLinkNavigation = false,
    /** Mobile „Tap to interact“: Wake per `click` (echtes Tap), nicht `pointerdown` — sonst weckt jeder Scroll-Start. */
    tapToInteractMobile = false,
    /** Optional: wenn gesetzt, statt nur `state.blockVisible` (≥6 % Fläche) — dünne Sichtstreifen + IO-Verzögerung. */
    tapWakeViewportCheck,
  } = ctx;

  /** Touch Tap vs. Drag: Pin nur wenn Down→Up-Bewegung unter diesem Schwellwert (px). */
  const TOUCH_TAP_MAX_MOVE_PX = 15;

  let diagramPointerDown = false;
  /** @type {{ x: number, y: number, pointerId: number } | null} */
  let touchTapStart = null;
  /** Klick/Tap-Erkennung für ALLE Pointer-Typen (Down-Position + primäre Taste) — für die Link-Navigation. */
  /** @type {{ x: number, y: number, pointerId: number, primary: boolean } | null} */
  let pointerDownStart = null;
  /** Pointer-IDs, deren Down auf diesem Diagramm-Container gestartet wurde (Touch/Maus/Stift). */
  const activeDiagramPointerIds = new Set();

  function updateMouseFromEvent(e) {
    lastMouse.x = e.clientX; lastMouse.y = e.clientY;
    const rect = ownCanvas.getBoundingClientRect();
    const rw = rect.width > 1e-6 ? rect.width : 1;
    const rh = rect.height > 1e-6 ? rect.height : 1;
    mouse.x = ((e.clientX - rect.left) / rw) * 2 - 1;
    mouse.y = -((e.clientY - rect.top) / rh) * 2 + 1;
  }

  function onContainerPointerEnter() {
    state.mouseOverContainer = true;
    showIcon();
    markDirty();
  }
  function onContainerPointerLeaveMouse(e) {
    state.mouseOverContainer = false;
    if (e.buttons === 0) state.isInteracting = false;
    markDirty();
  }
  function onContainerPointerDown(e) {
    if (state.hibernated) {
      clearDiagramWakeSchedule(state);
      if (!tapToInteractMobile) {
        try { wake(); } catch (_) { /* ignore */ }
      } else {
        return;
      }
    }
    diagramPointerDown = true;
    state.isInteracting = true;
    state.userHasOrbited = true;
    state.cameraCenterTarget.position = null; state.cameraCenterTarget.lookAt = null;
    updateMouseFromEvent(e);
    if (e.pointerType === 'touch') {
      touchTapStart = { x: e.clientX, y: e.clientY, pointerId: e.pointerId };
    }
    /* Primärtaste = Maus links (button 0) bzw. Touch/Stift. Rechts-/Mittelklick
     * (Pan) zählt nicht als Klick und löst keine Navigation aus. */
    pointerDownStart = { x: e.clientX, y: e.clientY, pointerId: e.pointerId, primary: e.button === 0 };
    activeDiagramPointerIds.add(e.pointerId);
    state.diagramHoldSuppressesTooltip = activeDiagramPointerIds.size > 0;
    markDirty();
  }
  function onContainerPointerUp() {
    diagramPointerDown = false;
    state.isInteracting = false;
    markDirty(DAMPING_SETTLE_FRAMES);
  }
  function onContainerPointerMove(e) {
    updateMouseFromEvent(e);
    markDirty();
  }
  function onContainerClickWake() {
    if (!tapToInteractMobile || !state.hibernated) return;
    if (typeof tapWakeViewportCheck === 'function') {
      if (!tapWakeViewportCheck()) return;
    } else if (!state.blockVisible) return;
    try { wake(); } catch (_) { /* ignore */ }
  }
  function onDocumentPointerDownDismissPinnedTooltip(e) {
    if (e.pointerType !== 'touch' || !tooltipCtl.isPinned()) return;
    if (container.contains(e.target)) return;
    tooltipCtl.clearPinned();
  }
  function finalizeTouchTooltipPin(e) {
    if (e.pointerType !== 'touch' || !touchTapStart || e.pointerId !== touchTapStart.pointerId) return;
    const start = touchTapStart;
    touchTapStart = null;
    const dx = e.clientX - start.x;
    const dy = e.clientY - start.y;
    if (Math.hypot(dx, dy) > TOUCH_TAP_MAX_MOVE_PX) {
      tooltipCtl.clearPinned();
      markDirty();
      return;
    }
    updateMouseFromEvent(e);
    const p = pickAtCurrentMouse();
    if (p) tooltipCtl.setPinned(p);
    else tooltipCtl.clearPinned();
    markDirty();
  }

  function releaseDiagramHoldIfPointer(e) {
    if (!activeDiagramPointerIds.has(e.pointerId)) return;
    activeDiagramPointerIds.delete(e.pointerId);
    state.diagramHoldSuppressesTooltip = activeDiagramPointerIds.size > 0;
  }

  function onWindowPointerCancel(e) {
    if (e.pointerType === 'touch' && touchTapStart && e.pointerId === touchTapStart.pointerId) {
      touchTapStart = null;
    }
    if (pointerDownStart && e.pointerId === pointerDownStart.pointerId) pointerDownStart = null;
    releaseDiagramHoldIfPointer(e);
  }

  /** Defense-in-Depth: nur http/https (zusätzlich zur PHP-Sanitisierung im Render-Callback). */
  function isSafeHttpUrl(link) {
    if (typeof link !== 'string' || link === '') return false;
    try {
      const u = new URL(link);
      return u.protocol === 'http:' || u.protocol === 'https:';
    } catch (_) {
      return false;
    }
  }

  /**
   * Echter Klick/Tap (gleicher Pointer, primäre Taste, Bewegung unter Tap-Schwelle)
   * auf einen Balken/Slice mit `link` → direkt navigieren. Gibt true zurück, wenn
   * navigiert wurde (dann kein Tooltip-Pin). Drag/Orbit überschreitet die Schwelle
   * und navigiert nie.
   */
  function tryLinkNavigation(e) {
    if (!enableLinkNavigation || !pointerDownStart || e.pointerId !== pointerDownStart.pointerId || !pointerDownStart.primary) {
      return false;
    }
    if (Math.hypot(e.clientX - pointerDownStart.x, e.clientY - pointerDownStart.y) > TOUCH_TAP_MAX_MOVE_PX) {
      return false;
    }
    updateMouseFromEvent(e);
    const pick = pickAtCurrentMouse();
    const link = pick && pick.hovered && pick.hovered.userData ? pick.hovered.userData.link : '';
    if (!isSafeHttpUrl(link)) return false;
    const win = ownerDoc.defaultView || window;
    if (pick.hovered.userData.linkNewTab === true) {
      win.open(link, '_blank', 'noopener,noreferrer');
    } else {
      win.location.href = link;
    }
    return true;
  }

  function onWindowPointerUp(e) {
    releaseDiagramHoldIfPointer(e);
    const navigated = tryLinkNavigation(e);
    pointerDownStart = null;
    if (navigated) {
      touchTapStart = null;
      tooltipCtl.clearPinned();
      if (diagramPointerDown) {
        diagramPointerDown = false;
        state.isInteracting = false;
        markDirty(DAMPING_SETTLE_FRAMES);
      }
      return;
    }
    finalizeTouchTooltipPin(e);
    if (diagramPointerDown) {
      diagramPointerDown = false;
      state.isInteracting = false;
      markDirty(DAMPING_SETTLE_FRAMES);
    } else if (e.pointerType === 'touch') {
      markDirty();
    }
  }

  container.addEventListener('pointerenter', onContainerPointerEnter);
  container.addEventListener('pointerleave', onContainerPointerLeaveMouse);
  container.addEventListener('pointerdown', onContainerPointerDown, true);
  container.addEventListener('pointerup', onContainerPointerUp);
  container.addEventListener('pointermove', onContainerPointerMove);
  if (tapToInteractMobile) container.addEventListener('click', onContainerClickWake);
  ownerDoc.addEventListener('pointerdown', onDocumentPointerDownDismissPinnedTooltip, true);
  window.addEventListener('pointerup', onWindowPointerUp);
  window.addEventListener('pointercancel', onWindowPointerCancel);

  function dispose() {
    container.removeEventListener('pointerenter', onContainerPointerEnter);
    container.removeEventListener('pointerleave', onContainerPointerLeaveMouse);
    container.removeEventListener('pointerdown', onContainerPointerDown, true);
    container.removeEventListener('pointerup', onContainerPointerUp);
    container.removeEventListener('pointermove', onContainerPointerMove);
    if (tapToInteractMobile) container.removeEventListener('click', onContainerClickWake);
    ownerDoc.removeEventListener('pointerdown', onDocumentPointerDownDismissPinnedTooltip, true);
    window.removeEventListener('pointerup', onWindowPointerUp);
    window.removeEventListener('pointercancel', onWindowPointerCancel);
  }

  return { dispose };
}
