import { useLayoutEffect, useRef, useState } from 'react'
import { ACCEPT_GLYPH, REJECT_GLYPH } from './components/Thumbs'

const COMMIT_MS = 260
const CANCEL_MS = 180
const DEAD_ZONE = 12 // px of travel before the axis locks
const FLICK_MS = 250 // releases faster than this commit at the lower threshold
const H_THRESHOLD = { slow: 70, fast: 30 }
const V_THRESHOLD = { slow: 90, fast: 40 }
const V_SATURATE = 160 // px of vertical drag for full badge/preview intensity
const ZOOM_MAX = 4

// Touch engine for the lightbox: horizontal swipes navigate, vertical flicks
// dispatch gestures.up/down (when provided), two fingers pinch-zoom the
// current photo (springs back on release, never votes).
//
// Navigation and votes are dispatched SYNCHRONOUSLY on release; every
// animation afterwards is pure decoration (a ghost flying off, the track
// settling). Closing the lightbox, key presses or list changes during an
// animation therefore can't drop, duplicate or misdirect an action.
//
// Per-move motion is written straight to the DOM via refs — dragging costs
// no React renders; state changes happen only at axis lock and release.
export default function useSwipe({ photo, index, count, onNav, gestures, hasNext }) {
  const [dragAxis, setDragAxis] = useState(null) // 'h' | 'v' | 'z' while a finger is down
  const [spring, setSpring] = useState(null) // 'h' | 'v' | 'z': sub-threshold release gliding back
  const [settleFrom, setSettleFrom] = useState(null) // px the track settles from after a nav commit
  const [settleRun, setSettleRun] = useState(false)
  const [exit, setExit] = useState(null) // { photo, dir, dy }: ghost flying off after a vote

  const trackRef = useRef(null)
  const imgRef = useRef(null)
  const behindRef = useRef(null)
  const badgeRef = useRef(null)
  const g = useRef(null)

  // FLIP: paint the track at the release offset around the NEW index first,
  // force a style flush, then let it transition to center.
  useLayoutEffect(() => {
    if (settleFrom == null) {
      setSettleRun(false)
      return
    }
    trackRef.current?.getBoundingClientRect()
    setSettleRun(true)
  }, [settleFrom])

  const clearManual = () => {
    for (const ref of [trackRef, imgRef, behindRef, badgeRef]) {
      if (ref.current) {
        ref.current.style.transform = ''
        ref.current.style.opacity = ''
        ref.current.style.transition = ''
        ref.current.style.transformOrigin = ''
      }
    }
  }

  const onTouchStart = (e) => {
    // Fresh input takes over: decorative animations yield immediately.
    setSpring(null)
    setSettleFrom(null)
    if (e.touches.length === 2 && imgRef.current) {
      // Second finger at any point turns the gesture into a pinch — a pinch
      // never navigates and never votes.
      clearManual()
      const [a, b] = e.touches
      const rect = imgRef.current.getBoundingClientRect()
      const cx = (a.clientX + b.clientX) / 2
      const cy = (a.clientY + b.clientY) / 2
      imgRef.current.style.transformOrigin = `${cx - rect.left}px ${cy - rect.top}px`
      g.current = {
        mode: 'pinch',
        d0: Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY),
        cx,
        cy,
      }
      setDragAxis('z')
      return
    }
    if (e.touches.length !== 1) {
      g.current = null
      setDragAxis(null)
      clearManual()
      return
    }
    const t = e.touches[0]
    g.current = { mode: 'swipe', x0: t.clientX, y0: t.clientY, t0: e.timeStamp, axis: null, dx: 0, dy: 0 }
  }

  const onTouchMove = (e) => {
    const s = g.current
    if (!s) return
    if (s.mode === 'pinch') {
      if (e.touches.length < 2 || !imgRef.current) return
      const [a, b] = e.touches
      const scale = Math.min(
        ZOOM_MAX,
        Math.max(1, Math.hypot(a.clientX - b.clientX, a.clientY - b.clientY) / s.d0),
      )
      const mx = (a.clientX + b.clientX) / 2 - s.cx
      const my = (a.clientY + b.clientY) / 2 - s.cy
      imgRef.current.style.transition = 'none'
      imgRef.current.style.transform = `translate(${mx}px, ${my}px) scale(${scale})`
      return
    }
    if (e.touches.length !== 1) return
    const t = e.touches[0]
    s.dx = t.clientX - s.x0
    s.dy = t.clientY - s.y0
    if (!s.axis) {
      if (Math.hypot(s.dx, s.dy) < DEAD_ZONE) return
      s.axis = Math.abs(s.dx) >= Math.abs(s.dy) || !gestures ? 'h' : 'v'
      setDragAxis(s.axis)
    }
    if (s.axis === 'h') {
      if (trackRef.current) {
        trackRef.current.style.transition = 'none'
        trackRef.current.style.transform = `translateX(${s.dx}px)`
      }
    } else {
      if (imgRef.current) {
        imgRef.current.style.transition = 'none'
        imgRef.current.style.transform = `translateY(${s.dy}px) rotate(${s.dy / 40}deg)`
      }
      const p = Math.min(1, Math.abs(s.dy) / V_SATURATE)
      if (behindRef.current) {
        behindRef.current.style.transition = 'none'
        behindRef.current.style.transform = `scale(${0.22 + 0.15 * p})`
        behindRef.current.style.opacity = p
      }
      if (badgeRef.current) {
        badgeRef.current.style.opacity = p
        badgeRef.current.textContent = s.dy < 0 ? ACCEPT_GLYPH : REJECT_GLYPH
      }
    }
  }

  const onTouchEnd = (e) => {
    const s = g.current
    if (!s) return
    if (s.mode === 'pinch') {
      if (e.touches.length > 0) return
      g.current = null
      setDragAxis(null)
      setSpring('z')
      return
    }
    g.current = null
    setDragAxis(null)
    if (!s.axis) {
      clearManual()
      return
    }
    const quick = e.timeStamp - s.t0 < FLICK_MS
    const past = (v, th) => Math.abs(v) > th.slow || (quick && Math.abs(v) > th.fast)
    if (s.axis === 'h') {
      if (s.dx < 0 && past(s.dx, H_THRESHOLD) && index < count - 1) {
        onNav(index + 1)
        setSettleFrom(s.dx + window.innerWidth)
      } else if (s.dx > 0 && past(s.dx, H_THRESHOLD) && index > 0) {
        onNav(index - 1)
        setSettleFrom(s.dx - window.innerWidth)
      } else {
        setSpring('h')
      }
    } else if (past(s.dy, V_THRESHOLD)) {
      const dir = s.dy < 0 ? 'up' : 'down'
      gestures?.[dir]?.(photo)
      clearManual()
      setExit({ photo, dir, dy: s.dy })
    } else {
      setSpring('v')
    }
  }

  const onTouchCancel = () => {
    g.current = null
    setDragAxis(null)
    clearManual()
  }

  const trackStyle =
    settleFrom != null
      ? settleRun
        ? { transform: 'translateX(0)', transition: `transform ${COMMIT_MS}ms ease-out` }
        : { transform: `translateX(${settleFrom}px)`, transition: 'none' }
      : spring === 'h'
        ? { transform: 'translateX(0)', transition: `transform ${CANCEL_MS}ms ease-out` }
        : undefined

  const imgStyle =
    spring === 'v'
      ? { transform: 'translateY(0) rotate(0deg)', transition: `transform ${CANCEL_MS}ms ease-out` }
      : spring === 'z'
        ? { transform: 'none', transition: `transform ${CANCEL_MS}ms ease-out` }
        : undefined

  const behindStyle =
    spring === 'v'
      ? { transform: 'scale(0.22)', opacity: 0, transition: `all ${CANCEL_MS}ms ease-out` }
      : undefined

  return {
    handlers: { onTouchStart, onTouchMove, onTouchEnd, onTouchCancel },
    trackRef,
    imgRef,
    behindRef,
    badgeRef,
    trackStyle,
    imgStyle,
    behindStyle,
    showBehind: (dragAxis === 'v' || spring === 'v') && hasNext,
    showBadge: dragAxis === 'v',
    exit,
    clearExit: () => setExit(null),
    onTrackTransitionEnd: (e) => {
      if (e.target !== trackRef.current) return
      setSettleFrom(null)
      if (spring === 'h') setSpring(null)
    },
    onImgTransitionEnd: (e) => {
      if (e.target !== imgRef.current) return
      if (spring === 'v' || spring === 'z') {
        setSpring(null)
        clearManual()
      }
    },
  }
}
