'use client'

import { useRef, useState, useTransition } from 'react'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { MapPin, Pencil, Trash2, X, Map as MapIcon, Box, Plus, ImagePlus, ChevronRight, Eye, EyeOff } from 'lucide-react'
import { upsertPin, deletePin, deleteMap, replaceMapImage, createLocationAndPin } from '../actions'
import { renameLocationAction } from '@/app/locations/actions'
import { PinRow } from './pin-row'

interface Pin {
  id: string
  x: number
  y: number
  locationId: string
  locationName: string
  locationPath: string
  hasOwnMap: boolean
  itemCount: number
}

interface LocOption {
  id: string
  path: string
}

export function MapView({
  mapId,
  imagePath,
  width,
  height,
  pins,
  locations,
  focusLocationId,
}: {
  mapId: string
  imagePath: string
  width: number
  height: number
  pins: Pin[]
  locations: LocOption[]
  focusLocationId?: string
}) {
  const [editing, setEditing] = useState(false)
  const [pickAt, setPickAt] = useState<{ x: number; y: number } | null>(null)
  const [picker, setPicker] = useState('')
  const [renamingPinId, setRenamingPinId] = useState<string | null>(null)
  const [renameDraft, setRenameDraft] = useState('')
  // Labels are hidden by default to avoid spaghetti when pins cluster. The
  // user can either tap a pin to reveal just its label (selectedPinId), or
  // flip the "Show all labels" toggle to keep every label visible. The toggle
  // sticks across visits via localStorage.
  // If the caller passed ?focus=<locationId> (e.g. landing here from a
  // breadcrumb image), pre-select the matching pin so the user sees the
  // label of where they came from without having to tap.
  const [selectedPinId, setSelectedPinId] = useState<string | null>(() => {
    if (!focusLocationId) return null
    return pins.find(p => p.locationId === focusLocationId)?.id ?? null
  })
  const [showAllLabels, setShowAllLabels] = useState(() => {
    if (typeof window === 'undefined') return false
    return window.localStorage.getItem('eorg.map.showAllLabels') === '1'
  })

  const toggleShowAllLabels = () => {
    setShowAllLabels(v => {
      const next = !v
      if (typeof window !== 'undefined') {
        window.localStorage.setItem('eorg.map.showAllLabels', next ? '1' : '0')
      }
      return next
    })
  }
  const [pending, startTransition] = useTransition()
  const containerRef = useRef<HTMLDivElement>(null)
  const draggingRef = useRef<{
    pinId: string
    pointerId: number
    startClientX: number
    startClientY: number
    active: boolean
  } | null>(null)
  const router = useRouter()

  // How far (in pixels) the pointer must travel before we consider it a drag
  // rather than a click. Defeats the tiny drift during a double-click and
  // similar accidental nudges.
  const DRAG_PX_THRESHOLD = 8

  const beginRename = (pin: Pin) => {
    setRenamingPinId(pin.id)
    setRenameDraft(pin.locationName)
  }

  const cancelRename = () => {
    setRenamingPinId(null)
    setRenameDraft('')
  }

  const commitRename = (pin: Pin) => {
    const trimmed = renameDraft.trim()
    if (!trimmed || trimmed === pin.locationName) {
      cancelRename()
      return
    }
    startTransition(async () => {
      await renameLocationAction(pin.locationId, trimmed)
      router.refresh()
      cancelRename()
    })
  }

  // Locations not yet pinned on this map.
  const pinnedIds = new Set(pins.map(p => p.locationId))
  const availableLocations = locations.filter(l => !pinnedIds.has(l.id))
  const filteredPicker = picker.trim()
    ? availableLocations.filter(l =>
        l.path.toLowerCase().includes(picker.trim().toLowerCase()),
      )
    : availableLocations

  const aspect = `${width} / ${height}`

  const normalizedFromEvent = (e: React.PointerEvent | PointerEvent) => {
    const el = containerRef.current
    if (!el) return null
    const rect = el.getBoundingClientRect()
    const x = (e.clientX - rect.left) / rect.width
    const y = (e.clientY - rect.top) / rect.height
    return { x: Math.max(0, Math.min(1, x)), y: Math.max(0, Math.min(1, y)) }
  }

  const onImageTap = (e: React.PointerEvent) => {
    // Pin buttons live inside the container, so their pointer events bubble
    // up here. We only want background taps — anything that started on a
    // child (a pin, the rename input, the pickAt placeholder) is the child's
    // job to handle.
    if (e.target !== e.currentTarget) return
    if (!editing) {
      // View mode: tapping the map outside a pin dismisses the revealed label.
      setSelectedPinId(null)
      return
    }
    if (draggingRef.current) return // mid-drag
    const norm = normalizedFromEvent(e)
    if (!norm) return
    setPickAt(norm)
  }

  const onPinPointerDown = (pinId: string) => (e: React.PointerEvent) => {
    if (!editing) return
    e.stopPropagation()
    draggingRef.current = {
      pinId,
      pointerId: e.pointerId,
      startClientX: e.clientX,
      startClientY: e.clientY,
      active: false,
    }
    e.currentTarget.setPointerCapture(e.pointerId)
  }

  const onPinPointerMove = (pinId: string) => (e: React.PointerEvent) => {
    if (!editing) return
    const drag = draggingRef.current
    if (!drag || drag.pinId !== pinId) 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 // still within tap deadzone; ignore micro-movement
      }
      drag.active = true
    }
    const norm = normalizedFromEvent(e)
    if (!norm) return
    const el = e.currentTarget as HTMLElement
    el.style.left = `${norm.x * 100}%`
    el.style.top = `${norm.y * 100}%`
  }

  const onPinPointerUp = (pin: Pin) => (e: React.PointerEvent) => {
    if (!editing) return
    const drag = draggingRef.current
    if (!drag || drag.pinId !== pin.id) return
    draggingRef.current = null
    if (!drag.active) return // never crossed threshold — treat as tap
    const norm = normalizedFromEvent(e)
    if (!norm) return
    startTransition(async () => {
      await upsertPin(mapId, pin.locationId, norm.x, norm.y)
      router.refresh()
    })
  }

  const choosePinned = (locationId: string) => {
    if (!pickAt) return
    const { x, y } = pickAt
    setPickAt(null)
    setPicker('')
    startTransition(async () => {
      await upsertPin(mapId, locationId, x, y)
      router.refresh()
    })
  }

  const createAndPin = (name: string) => {
    if (!pickAt) return
    const trimmed = name.trim()
    if (!trimmed) return
    const { x, y } = pickAt
    setPickAt(null)
    setPicker('')
    startTransition(async () => {
      const res = await createLocationAndPin(mapId, trimmed, x, y)
      if (!res.ok) {
        alert(res.error)
        return
      }
      router.refresh()
    })
  }

  const exactMatch = picker.trim()
    ? availableLocations.some(
        l => l.path.toLowerCase() === picker.trim().toLowerCase(),
      )
    : false

  const removePin = (pinId: string) => {
    startTransition(async () => {
      await deletePin(pinId)
      router.refresh()
    })
  }

  const navigateForPin = (pin: Pin) => {
    if (editing) return
    if (pin.hasOwnMap) {
      // Drill into the location's own map.
      router.push(`/locations/${pin.locationId}`)
    } else {
      router.push(`/locations/${pin.locationId}`)
    }
  }

  const removeMap = () => {
    if (!confirm('Delete this map? Pins will be removed too.')) return
    startTransition(async () => {
      await deleteMap(mapId)
    })
  }

  const replaceImageRef = useRef<HTMLInputElement>(null)
  const onReplaceImagePick = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0]
    e.target.value = ''
    if (!file) return
    if (!confirm('Replace the map image? Pins keep their positions on the new image.')) return
    const fd = new FormData()
    fd.set('mapId', mapId)
    fd.set('image', file)
    startTransition(async () => {
      const res = await replaceMapImage(fd)
      if (!res.ok) {
        alert(res.error)
        return
      }
      router.refresh()
    })
  }

  return (
    <div className="space-y-3">
      <div className="flex items-center justify-end gap-2">
        <button
          type="button"
          onClick={toggleShowAllLabels}
          aria-label={showAllLabels ? 'Hide all labels' : 'Show all labels'}
          aria-pressed={showAllLabels}
          title={showAllLabels ? 'Hide all labels' : 'Show all labels'}
          className={
            'inline-flex items-center justify-center size-9 rounded-lg border ' +
            (showAllLabels
              ? 'bg-primary text-primary-foreground border-primary'
              : 'text-muted-foreground hover:text-foreground hover:bg-accent/40')
          }
        >
          {showAllLabels ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
        </button>
        <button
          type="button"
          onClick={() => replaceImageRef.current?.click()}
          disabled={pending}
          aria-label="Replace image"
          title="Replace image (pins keep their positions)"
          className="inline-flex items-center justify-center size-9 rounded-lg border text-muted-foreground hover:text-foreground hover:bg-accent/40"
        >
          <ImagePlus className="size-4" />
        </button>
        <input
          ref={replaceImageRef}
          type="file"
          accept="image/*"
          className="sr-only"
          onChange={onReplaceImagePick}
        />
        <button
          type="button"
          onClick={removeMap}
          disabled={pending}
          aria-label="Delete map"
          title="Delete map"
          className="inline-flex items-center justify-center size-9 rounded-lg border text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-950/30"
        >
          <Trash2 className="size-4" />
        </button>
        {editing ? (
          <button
            type="button"
            onClick={() => setEditing(false)}
            className="inline-flex items-center gap-1 h-9 px-3 rounded-lg bg-primary text-primary-foreground text-sm font-medium"
          >
            Done
          </button>
        ) : (
          <button
            type="button"
            onClick={() => setEditing(true)}
            className="inline-flex items-center gap-1 h-9 px-3 rounded-lg border text-sm"
          >
            <Pencil className="size-4" /> Edit pins
          </button>
        )}
      </div>

      <div
        ref={containerRef}
        onPointerDown={onImageTap}
        className="relative w-full rounded-xl border bg-muted overflow-hidden select-none touch-none"
        style={{ aspectRatio: aspect }}
      >
        {/* eslint-disable-next-line @next/next/no-img-element */}
        <img
          src={`/api/uploads/${imagePath}`}
          alt=""
          draggable={false}
          className="absolute inset-0 w-full h-full object-contain pointer-events-none"
        />
        {pins.map(pin => {
          const isRenaming = renamingPinId === pin.id
          // Labels are hidden by default in both modes to keep clustered pins
          // readable. Tap a pin to reveal its label; tap a different pin (or
          // the bare map background, in view mode) to switch / dismiss.
          // Rename always shows the input even if the pin isn't selected.
          const showLabel = isRenaming || selectedPinId === pin.id || showAllLabels
          return (
            <button
              key={pin.id}
              type="button"
              onPointerDown={isRenaming ? undefined : onPinPointerDown(pin.id)}
              onPointerMove={isRenaming ? undefined : onPinPointerMove(pin.id)}
              onPointerUp={isRenaming ? undefined : onPinPointerUp(pin)}
              onClick={e => {
                e.stopPropagation()
                if (isRenaming) return
                if (editing) {
                  // Edit mode: tap toggles label visibility. Double-click
                  // (handled below) begins rename.
                  setSelectedPinId(prev => (prev === pin.id ? null : pin.id))
                  return
                }
                if (selectedPinId === pin.id) {
                  navigateForPin(pin)
                } else {
                  setSelectedPinId(pin.id)
                }
              }}
              onDoubleClick={e => {
                if (!editing) return
                e.stopPropagation()
                e.preventDefault()
                beginRename(pin)
              }}
              style={{
                left: `${pin.x * 100}%`,
                top: `${pin.y * 100}%`,
                zIndex: selectedPinId === pin.id ? 20 : 10,
              }}
              className={
                editing
                  ? 'absolute -translate-x-1/2 -translate-y-full flex flex-col items-center cursor-grab active:cursor-grabbing'
                  : 'absolute -translate-x-1/2 -translate-y-full flex flex-col items-center hover:scale-105 transition-transform'
              }
            >
              {showLabel && (
                isRenaming ? (
                  <input
                    autoFocus
                    value={renameDraft}
                    onChange={e => setRenameDraft(e.target.value)}
                    onPointerDown={e => e.stopPropagation()}
                    onClick={e => e.stopPropagation()}
                    onBlur={() => commitRename(pin)}
                    onKeyDown={e => {
                      if (e.key === 'Enter') {
                        e.preventDefault()
                        ;(e.target as HTMLInputElement).blur()
                      } else if (e.key === 'Escape') {
                        cancelRename()
                      }
                    }}
                    disabled={pending}
                    className="px-2 py-0.5 rounded-md bg-background border-2 border-primary text-xs font-medium outline-none w-[20ch] max-w-[40vw] text-center cursor-text"
                    onFocus={e => e.currentTarget.select()}
                  />
                ) : (
                  <span className="px-2 py-0.5 rounded-md bg-background border shadow-sm text-xs font-medium whitespace-nowrap max-w-[60vw] truncate">
                    {pin.locationName}
                    {pin.hasOwnMap && (
                      <MapIcon className="inline size-3 ml-1 text-muted-foreground" />
                    )}
                  </span>
                )
              )}
              <MapPin
                className={
                  (pin.hasOwnMap
                    ? 'size-7 -mt-1 fill-amber-500 text-amber-700 drop-shadow'
                    : 'size-7 -mt-1 fill-primary text-primary-foreground drop-shadow') +
                  (selectedPinId === pin.id ? ' scale-110' : '')
                }
              />
            </button>
          )
        })}

        {editing && pickAt && (
          <div
            style={{ left: `${pickAt.x * 100}%`, top: `${pickAt.y * 100}%` }}
            className="absolute -translate-x-1/2 -translate-y-full pointer-events-none z-30"
          >
            {/* The freshly dropped pin "lands" with a quick drop-in so the tap
                clearly registers before the picker dialog fades over it. */}
            <MapPin className="size-8 fill-primary text-primary-foreground drop-shadow-lg animate-in zoom-in-50 slide-in-from-top-4 duration-300" />
          </div>
        )}
      </div>

      {editing && pins.length > 0 && (
        <div className="space-y-1">
          <div className="text-xs uppercase tracking-wide text-muted-foreground">
            Pinned locations
          </div>
          <div className="rounded-xl border divide-y bg-card">
            {pins.map(pin => (
              <PinRow
                key={pin.id}
                locationId={pin.locationId}
                locationName={pin.locationName}
                locationPath={pin.locationPath}
                itemCount={pin.itemCount}
                hasOwnMap={pin.hasOwnMap}
                onRemove={() => removePin(pin.id)}
                busy={pending}
              />
            ))}
          </div>
        </div>
      )}

      {!editing && pins.length === 0 && (
        <div className="rounded-xl border bg-card p-4 text-center text-sm text-muted-foreground">
          <Box className="size-5 mx-auto mb-1 text-muted-foreground/60" />
          No pins yet. Tap <strong>Edit pins</strong> to start labelling.
        </div>
      )}

      {!editing && pins.length > 0 && (
        <div className="space-y-1">
          <div className="text-xs uppercase tracking-wide text-muted-foreground">
            Pinned locations ({pins.length})
            {!showAllLabels && ' — tap a pin to reveal its label'}
          </div>
          <div className="rounded-xl border divide-y bg-card">
            {pins.map(pin => (
              <Link
                key={pin.id}
                href={`/locations/${pin.locationId}`}
                className="flex items-center gap-2 p-3 hover:bg-accent/40"
              >
                <MapPin
                  className={
                    pin.hasOwnMap
                      ? 'size-4 fill-amber-500 text-amber-700 shrink-0'
                      : 'size-4 fill-primary text-primary-foreground shrink-0'
                  }
                />
                <div className="flex-1 min-w-0">
                  <div className="text-sm font-medium truncate flex items-center gap-1.5">
                    <span className="truncate">{pin.locationName}</span>
                    {pin.hasOwnMap && (
                      <MapIcon className="size-3 text-muted-foreground shrink-0" />
                    )}
                  </div>
                  <div className="text-xs text-muted-foreground">
                    {pin.itemCount} item{pin.itemCount === 1 ? '' : 's'}
                    {pin.hasOwnMap && ' · has interior map'}
                  </div>
                </div>
                <ChevronRight className="size-4 text-muted-foreground shrink-0" />
              </Link>
            ))}
          </div>
        </div>
      )}

      {/* Location picker dialog */}
      {pickAt && editing && (
        <div
          // Top-anchored on mobile so the soft keyboard (which slides up from
          // the bottom) can't cover the dialog — a bottom-anchored sheet hid
          // the search input the moment you started typing. Centered on wider
          // screens where there's no on-screen keyboard.
          className="fixed inset-0 z-50 flex items-start sm:items-center justify-center bg-black/40 p-4 pt-[max(1rem,env(safe-area-inset-top))] animate-in fade-in duration-200"
          onClick={() => {
            setPickAt(null)
            setPicker('')
          }}
        >
          <div
            onClick={e => e.stopPropagation()}
            className="w-full max-w-md rounded-2xl bg-background border shadow-xl p-4 space-y-3 max-h-[80vh] flex flex-col animate-in fade-in zoom-in-95 slide-in-from-top-2 duration-200"
          >
            <div className="flex items-center justify-between">
              <div className="font-medium">Pin which location?</div>
              <button
                type="button"
                onClick={() => {
                  setPickAt(null)
                  setPicker('')
                }}
                aria-label="Close"
                className="text-muted-foreground hover:text-foreground"
              >
                <X className="size-4" />
              </button>
            </div>
            <input
              autoFocus
              value={picker}
              onChange={e => setPicker(e.target.value)}
              onKeyDown={e => {
                if (e.key === 'Enter' && picker.trim() && !exactMatch) {
                  e.preventDefault()
                  createAndPin(picker)
                }
              }}
              placeholder="Search or type a new name…"
              className="w-full h-10 rounded-lg border bg-background px-3 text-base outline-none focus:ring-2 focus:ring-ring"
            />
            <div className="flex-1 overflow-auto rounded-lg border divide-y">
              {picker.trim() && !exactMatch && (
                <button
                  type="button"
                  onClick={() => createAndPin(picker)}
                  className="w-full text-left p-2 text-sm hover:bg-accent/40 flex items-center gap-2 text-primary"
                >
                  <Plus className="size-4" />
                  Create &ldquo;{picker.trim()}&rdquo;
                </button>
              )}
              {filteredPicker.length === 0 && !picker.trim() && (
                <div className="p-3 text-sm text-muted-foreground text-center">
                  {availableLocations.length === 0
                    ? 'Every location is already pinned. Type a name above to create a new one.'
                    : 'Search to filter, or type a new name.'}
                </div>
              )}
              {filteredPicker.map(l => (
                <button
                  key={l.id}
                  type="button"
                  onClick={() => choosePinned(l.id)}
                  className="w-full text-left p-2 text-sm hover:bg-accent/40"
                >
                  {l.path}
                </button>
              ))}
            </div>
          </div>
        </div>
      )}
    </div>
  )
}
