'use client'

import { useCallback, useEffect, useRef, useState } from 'react'

import { useMapContext } from '../context'

export type MapScrollProtectionMode = 'off' | 'desktop' | 'mobile'

export interface UseMapScrollProtectionResult {
  /** Whether the lock overlay should be shown. */
  locked: boolean
  /**
   * Click/tap handler for the overlay. On desktop it unlocks scroll-zoom
   * in place; on mobile it stays locked (the caller opens fullscreen).
   */
  unlock: () => void
  /** True when running in mobile mode — the caller should expand to
   *  fullscreen on tap instead of unlocking in place. */
  isMobileLock: boolean
}

/**
 * Gesture guard for an inline map.
 *
 * - `'desktop'` — Google-Maps-style scroll isolation. Only the wheel/
 *   trackpad **scroll-zoom** is blocked until the user clicks the map;
 *   drag-pan / double-click / pinch stay live. Click inside unlocks,
 *   click outside re-locks. So scrolling a page past the map never
 *   hijacks the zoom, but the map is never fully inert.
 *
 * - `'mobile'` — the map is made **fully static**: scroll-zoom, drag-pan,
 *   touch pinch/rotate, double-click and keyboard are all disabled, so a
 *   finger dragging over the map scrolls the PAGE. There is no in-place
 *   unlock; the caller turns a tap into "open fullscreen", where a fresh
 *   map runs with all gestures enabled. This is the native-app pattern
 *   (static map preview in a feed → tap to open the full map).
 *
 * - `'off'` — every gesture enabled, no overlay.
 */
export function useMapScrollProtection(
  mode: MapScrollProtectionMode,
): UseMapScrollProtectionResult {
  const { mapRef, isLoaded } = useMapContext()
  const [locked, setLocked] = useState(true)

  // Latest `locked` without forcing the document-listener effect to
  // re-subscribe on every toggle.
  const lockedRef = useRef(locked)
  lockedRef.current = locked

  // On mobile the lock is permanent (tap opens fullscreen); only desktop
  // unlocks in place.
  const unlock = useCallback(() => {
    if (mode === 'desktop') setLocked(false)
  }, [mode])

  // All the maplibre gesture handlers we toggle in mobile mode. Listed
  // defensively (some are absent depending on options) — each is guarded.
  const setAllGestures = useCallback(
    (map: maplibregl.Map, enable: boolean) => {
      const handlers = [
        map.scrollZoom,
        map.boxZoom,
        map.dragRotate,
        map.dragPan,
        map.keyboard,
        map.doubleClickZoom,
        map.touchZoomRotate,
        (map as unknown as { touchPitch?: { enable(): void; disable(): void } }).touchPitch,
      ]
      for (const h of handlers) {
        if (h && typeof h.enable === 'function') {
          enable ? h.enable() : h.disable()
        }
      }
    },
    [],
  )

  // Apply the lock to maplibre. Re-runs on load and whenever mode/lock change.
  useEffect(() => {
    const map = mapRef.current?.getMap()
    if (!map) return

    if (mode === 'off') {
      // Make sure nothing we may have disabled stays off.
      map.scrollZoom.enable()
      return
    }

    if (mode === 'mobile') {
      // Fully static while locked; fully live once unlocked (never happens
      // on mobile, but keep the branch symmetric).
      setAllGestures(map, !locked)
      return
    }

    // desktop: only the scroll-zoom is gated.
    if (locked) {
      map.scrollZoom.disable()
    } else {
      map.scrollZoom.enable()
    }
  }, [mapRef, isLoaded, mode, locked, setAllGestures])

  // Re-lock (desktop only) when the user clicks anywhere OUTSIDE the map.
  // We do NOT re-lock on mouse-leave: once you've clicked into the map you
  // keep interacting even as the pointer wanders off and back — the Google
  // Maps behavior. Capture phase so we see the click before inner handlers
  // can stop it. Mobile never unlocks in place, so it needs no re-lock.
  useEffect(() => {
    if (mode !== 'desktop') return
    const container = mapRef.current?.getMap()?.getContainer()
    if (!container) return

    const handleDocPointerDown = (e: Event) => {
      const target = e.target as Element | null
      // A toolbar chip (or a menu/tooltip it opens) — never re-locks, and
      // when the map is LOCKED, pressing a control unlocks it: the user is
      // clearly working with the map, so the scroll guard should step aside.
      const onControl = !!target?.closest?.(
        '[data-map-control],[data-radix-popper-content-wrapper],[role="menu"],[role="tooltip"]',
      )

      if (lockedRef.current) {
        // Only desktop unlocks in place; `unlock()` is a no-op on mobile.
        if (onControl) unlock()
        return
      }

      // Unlocked: clicking a control must NOT re-lock (otherwise pressing a
      // toolbar button would immediately re-lock the map).
      if (onControl) return
      if (!container.contains(e.target as Node)) {
        setLocked(true)
      }
    }

    document.addEventListener('pointerdown', handleDocPointerDown, true)
    return () => {
      document.removeEventListener('pointerdown', handleDocPointerDown, true)
    }
  }, [mapRef, isLoaded, mode, unlock])

  // Reset to locked whenever the feature is (re)enabled.
  useEffect(() => {
    if (mode !== 'off') setLocked(true)
  }, [mode])

  return {
    locked: mode !== 'off' && locked,
    unlock,
    isMobileLock: mode === 'mobile' && locked,
  }
}
