'use client'

import { useEffect, useRef, useState, useTransition } from 'react'
import { Star, Trash2, Plus } from 'lucide-react'
import { addPhoto, deletePhoto, setPrimaryPhoto, reorderPhotos } from '../actions'

interface PhotoLite {
  id: string
  path: string
  thumbPath: string
  isPrimary: boolean
}

// How far (in pixels) the pointer must travel before a mouse press becomes a
// drag rather than a click (same deadzone as map pins).
const DRAG_PX_THRESHOLD = 8
// On touch the strip scrolls horizontally, so movement can't start a drag.
// Instead a still long-press "lifts" the thumbnail; moving before the hold
// elapses falls through to native scrolling.
const TOUCH_HOLD_MS = 250

const moveItem = <T,>(arr: T[], from: number, to: number): T[] => {
  const next = arr.slice()
  const [item] = next.splice(from, 1)
  next.splice(to, 0, item)
  return next
}

export function PhotosSection({ itemId, photos }: { itemId: string; photos: PhotoLite[] }) {
  const [, startTransition] = useTransition()
  const fileRef = useRef<HTMLInputElement>(null)

  // Local copies of the gallery so drags / star taps / deletes show instantly;
  // a fresh `photos` prop (after revalidation) replaces them.
  const [prevPhotos, setPrevPhotos] = useState(photos)
  const [order, setOrder] = useState(photos)
  // Optimistic primary id — set the moment the star is tapped, cleared when
  // the server round-trip lands in new props.
  const [primaryOverride, setPrimaryOverride] = useState<string | null>(null)
  if (photos !== prevPhotos) {
    setPrevPhotos(photos)
    setOrder(photos)
    setPrimaryOverride(null)
  }

  const primaryId = primaryOverride ?? photos.find(p => p.isPrimary)?.id ?? null
  // Track the viewed photo by id (not object) so it never holds stale data.
  const [selectedId, setSelectedId] = useState<string | null>(primaryId)
  const selected = order.find(p => p.id === selectedId) ?? order[0]

  const stripRef = useRef<HTMLDivElement>(null)
  const dragRef = useRef<{
    photoId: string
    startClientX: number
    startClientY: number
    startIndex: number
    baseOrder: PhotoLite[]
    pitch: number // horizontal distance between thumbnail slots
    holdTimer: number | null
    active: boolean
  } | null>(null)
  const [draggingId, setDraggingId] = useState<string | null>(null)
  // A drag eats the click that the browser fires right after pointerup, so it
  // doesn't double as a "select this photo" tap.
  const suppressClickRef = useRef(false)

  const hasPhotos = photos.length > 0

  // Once a drag is active we must stop the strip from scrolling under the
  // dragged thumb. touch-action can't change mid-gesture, so preventDefault
  // the native (non-passive) touchmove instead.
  useEffect(() => {
    if (!hasPhotos) return
    const el = stripRef.current
    if (!el) return
    const onTouchMove = (e: TouchEvent) => {
      if (dragRef.current?.active) e.preventDefault()
    }
    el.addEventListener('touchmove', onTouchMove, { passive: false })
    return () => el.removeEventListener('touchmove', onTouchMove)
  }, [hasPhotos])

  const onPick = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const files = Array.from(e.target.files ?? [])
    if (files.length === 0) return
    startTransition(async () => {
      // Sequential so each upload appends after the previous one's position.
      for (const file of files) {
        const fd = new FormData()
        fd.set('itemId', itemId)
        fd.set('photo', file)
        await addPhoto(fd)
      }
    })
    if (fileRef.current) fileRef.current.value = ''
  }

  const targetIndex = (e: React.PointerEvent) => {
    const drag = dragRef.current!
    const raw = drag.startIndex + Math.round((e.clientX - drag.startClientX) / drag.pitch)
    return Math.max(0, Math.min(drag.baseOrder.length - 1, raw))
  }

  const clearDrag = () => {
    const drag = dragRef.current
    if (drag?.holdTimer) window.clearTimeout(drag.holdTimer)
    dragRef.current = null
    setDraggingId(null)
  }

  const onThumbPointerDown = (photo: PhotoLite, index: number) => (e: React.PointerEvent) => {
    if (order.length < 2) return
    const strip = stripRef.current
    // All slots share one pitch (fixed-size thumbs + fixed gap), so the drop
    // index is plain arithmetic on the pointer's horizontal travel.
    const a = strip?.children[0]?.getBoundingClientRect()
    const b = strip?.children[1]?.getBoundingClientRect()
    const drag = {
      photoId: photo.id,
      startClientX: e.clientX,
      startClientY: e.clientY,
      startIndex: index,
      baseOrder: order,
      pitch: a && b ? b.left - a.left : 72,
      holdTimer: null as number | null,
      active: false,
    }
    dragRef.current = drag
    e.currentTarget.setPointerCapture(e.pointerId)
    if (e.pointerType !== 'mouse') {
      drag.holdTimer = window.setTimeout(() => {
        drag.holdTimer = null
        drag.active = true
        setDraggingId(photo.id)
      }, TOUCH_HOLD_MS)
    }
  }

  const onThumbPointerMove = (photoId: string) => (e: React.PointerEvent) => {
    const drag = dragRef.current
    if (!drag || drag.photoId !== photoId) return
    if (!drag.active) {
      const dx = e.clientX - drag.startClientX
      const dy = e.clientY - drag.startClientY
      if (dx * dx + dy * dy < DRAG_PX_THRESHOLD * DRAG_PX_THRESHOLD) return
      if (e.pointerType === 'mouse') {
        drag.active = true
        setDraggingId(photoId)
      } else {
        // Touch moved before the hold elapsed — it's a scroll, not a drag.
        clearDrag()
        return
      }
    }
    setOrder(moveItem(drag.baseOrder, drag.startIndex, targetIndex(e)))
  }

  const onThumbPointerUp = (photoId: string) => (e: React.PointerEvent) => {
    const drag = dragRef.current
    if (!drag || drag.photoId !== photoId) return
    if (!drag.active) {
      clearDrag()
      return // plain tap — let the click handler select the photo
    }
    suppressClickRef.current = true
    const next = moveItem(drag.baseOrder, drag.startIndex, targetIndex(e))
    clearDrag()
    setOrder(next)
    if (next.some((p, i) => p.id !== drag.baseOrder[i].id)) {
      startTransition(() => reorderPhotos(itemId, next.map(p => p.id)))
    }
  }

  const onThumbPointerCancel = (photoId: string) => () => {
    const drag = dragRef.current
    if (!drag || drag.photoId !== photoId) return
    if (drag.active) setOrder(drag.baseOrder) // snap back, nothing persisted
    clearDrag()
  }

  if (photos.length === 0) {
    return (
      <div
        onClick={() => fileRef.current?.click()}
        className="rounded-xl border-2 border-dashed p-8 text-center text-muted-foreground hover:bg-accent/40 cursor-pointer"
      >
        <Plus className="size-6 mx-auto mb-2" />
        <div className="text-sm">Add a photo</div>
        <input ref={fileRef} type="file" accept="image/*" multiple className="sr-only" onChange={onPick} />
      </div>
    )
  }

  const selectedIsPrimary = selected.id === primaryId

  return (
    <div className="space-y-2">
      <div className="relative">
        {/* eslint-disable-next-line @next/next/no-img-element */}
        <img
          src={`/api/uploads/${selected.path}`}
          alt=""
          className="w-full max-h-[60vh] object-contain rounded-xl bg-muted"
        />
        <div className="absolute top-2 right-2 flex gap-2">
          <form
            action={() => {
              if (selectedIsPrimary) return
              // Fill the star right away so the tap visibly registers.
              setPrimaryOverride(selected.id)
              startTransition(() => setPrimaryPhoto(itemId, selected.id))
            }}
          >
            <button
              type="submit"
              aria-label={selectedIsPrimary ? 'This is the cover photo' : 'Make cover photo'}
              aria-pressed={selectedIsPrimary}
              className="size-9 rounded-full bg-black/60 text-white flex items-center justify-center"
            >
              <Star
                className={
                  selectedIsPrimary ? 'size-4 fill-amber-400 text-amber-400' : 'size-4'
                }
              />
            </button>
          </form>
          <form
            action={() =>
              startTransition(async () => {
                if (!confirm('Delete this photo?')) return
                const deletedId = selected.id
                // Drop it locally so the viewer never shows the dead image
                // while the server round-trip is in flight.
                setOrder(prev => prev.filter(p => p.id !== deletedId))
                setSelectedId(order.find(p => p.id !== deletedId)?.id ?? null)
                await deletePhoto(itemId, deletedId)
              })
            }
          >
            <button
              type="submit"
              aria-label="Delete photo"
              className="size-9 rounded-full bg-black/60 text-white flex items-center justify-center"
            >
              <Trash2 className="size-4" />
            </button>
          </form>
        </div>
      </div>
      <div
        ref={stripRef}
        // touch-callout off so the iOS long-press image menu doesn't fight the
        // lift gesture; contextmenu likewise (Android long-press).
        onContextMenu={e => {
          if (dragRef.current) e.preventDefault()
        }}
        className="flex gap-2 overflow-x-auto py-1 select-none [-webkit-touch-callout:none]"
      >
        {order.map((p, i) => (
          <div key={p.id} className="relative shrink-0">
            {/* eslint-disable-next-line @next/next/no-img-element */}
            <img
              src={`/api/uploads/${p.thumbPath}`}
              alt=""
              draggable={false}
              onPointerDown={onThumbPointerDown(p, i)}
              onPointerMove={onThumbPointerMove(p.id)}
              onPointerUp={onThumbPointerUp(p.id)}
              onPointerCancel={onThumbPointerCancel(p.id)}
              onClick={() => {
                if (suppressClickRef.current) {
                  suppressClickRef.current = false
                  return
                }
                setSelectedId(p.id)
              }}
              className={`size-16 rounded-lg object-cover cursor-pointer ring-2 transition-all ${
                draggingId === p.id
                  ? 'ring-primary scale-110 shadow-lg z-10 cursor-grabbing'
                  : p.id === selected.id
                    ? 'ring-primary'
                    : 'ring-transparent'
              }`}
            />
            {p.id === primaryId && (
              <Star className="absolute top-1 right-1 size-3.5 fill-amber-400 text-amber-400 drop-shadow pointer-events-none" />
            )}
          </div>
        ))}
        <button
          type="button"
          onClick={() => fileRef.current?.click()}
          className="size-16 rounded-lg border-2 border-dashed shrink-0 flex items-center justify-center text-muted-foreground hover:bg-accent/40"
          aria-label="Add photo"
        >
          <Plus className="size-5" />
        </button>
        <input ref={fileRef} type="file" accept="image/*" multiple className="sr-only" onChange={onPick} />
      </div>
    </div>
  )
}
