import { useEffect, useRef, useState } from 'react'
import { imgUrl } from '../api'
import { isTypingTarget } from '../useEscape'
import useSwipe from '../useSwipe'

const BASE_SHORTCUTS = [
  ['← / →', 'previous / next photo'],
  ['?', 'show / hide shortcuts'],
  ['Space / Esc', 'close'],
]

// `actions` defines the page's shortcuts as one table — display and dispatch
// come from the same entry, so the help overlay can't drift from behavior:
// { keys: ['p'], help: ['P', 'accept…'], run: (photo, key) => … }.
// Keys fire only outside text inputs and while the help overlay is closed.
// `gestures` ({ up, down }) maps vertical touch flicks on the photo — the
// horizontal axis always navigates, so voting and browsing never collide.
// `closing` fades the whole modal out; `onClosed` fires when the fade ends
// (the page then actually unmounts the lightbox).
export default function Lightbox({
  photos,
  index,
  onClose,
  onNav,
  footer,
  actions,
  gestures,
  closing,
  onClosed,
}) {
  const photo = photos[index]
  const [showHelp, setShowHelp] = useState(false)

  // Handlers and view state live in a ref, updated every render, so the
  // window listener is attached once yet always dispatches against current
  // values — re-subscribing per render leaves a gap until effects re-run in
  // which a fast second keystroke hits a stale closure (and e.g. re-votes
  // the previous photo).
  const live = useRef({})
  live.current = { index, count: photos.length, photo, onClose, onNav, actions, showHelp, closing }

  useEffect(() => {
    const handler = (e) => {
      const s = live.current
      if (s.closing) return
      if (isTypingTarget(e.target)) {
        if (e.key === 'Escape') e.target.blur()
        return
      }
      if (e.metaKey || e.ctrlKey || e.altKey) return
      if (e.key === 'Escape') {
        if (s.showHelp) setShowHelp(false)
        else s.onClose()
        return
      }
      if (e.key === '?') {
        setShowHelp((h) => !h)
        return
      }
      // With the help overlay up, keys must not act on the photo behind it.
      if (s.showHelp) return
      if (e.key === ' ') {
        e.preventDefault()
        s.onClose()
        return
      }
      if (e.key === 'ArrowRight') {
        e.preventDefault()
        if (s.index < s.count - 1) s.onNav(s.index + 1)
        return
      }
      if (e.key === 'ArrowLeft') {
        e.preventDefault()
        if (s.index > 0) s.onNav(s.index - 1)
        return
      }
      const key = e.key.toLowerCase()
      const action = s.actions?.find((a) => a.keys.includes(key))
      if (action && s.photo) action.run(s.photo, key)
    }
    window.addEventListener('keydown', handler)
    return () => window.removeEventListener('keydown', handler)
  }, [])

  useEffect(() => {
    document.body.style.overflow = 'hidden'
    return () => {
      document.body.style.overflow = ''
    }
  }, [])

  const preview = (p) => imgUrl(p, 'preview')
  const prevPhoto = photos[index - 1]
  const nextPhoto = photos[index + 1]

  const swipe = useSwipe({
    photo,
    index,
    count: photos.length,
    onNav,
    gestures,
    hasNext: index < photos.length - 1,
  })

  // Immediate neighbors are real DOM slides (loaded by definition); warm the
  // browser cache a few photos further out so fast swiping never hits the
  // network.
  useEffect(() => {
    for (const i of [index + 2, index + 3, index - 2]) {
      if (photos[i]) new Image().src = preview(photos[i])
    }
  }, [index, photos])

  if (!photo) return null

  // Did the vote fly off the photo we're still showing (no advance target)?
  const exit = swipe.exit
  const exitSelf = exit && exit.photo.id === photo.id

  return (
    <div
      className={`lightbox${closing ? ' lb-closing' : ''}`}
      onClick={onClose}
      onAnimationEnd={(e) => {
        if (e.animationName === 'lb-fade-out') onClosed?.()
      }}
    >
      <div className="lb-top" onClick={(e) => e.stopPropagation()}>
        <span className="lb-name">{photo.filename}</span>
        <span className="lb-count">
          {index + 1} / {photos.length}
        </span>
        <button
          className="lb-btn"
          onClick={() => setShowHelp((h) => !h)}
          title="Keyboard shortcuts (?)"
        >
          ?
        </button>
        <button className="lb-btn" onClick={onClose} title="Close (Esc)">
          ✕
        </button>
      </div>
      <button
        className="lb-nav lb-prev"
        disabled={index === 0}
        onClick={(e) => {
          e.stopPropagation()
          onNav(index - 1)
        }}
      >
        ‹
      </button>
      <div className="lb-stage" {...swipe.handlers}>
        <div
          className="lb-track"
          ref={swipe.trackRef}
          style={swipe.trackStyle}
          onTransitionEnd={swipe.onTrackTransitionEnd}
        >
          {prevPhoto && (
            <div key={prevPhoto.id} className="lb-slide" style={{ transform: 'translateX(-100vw)' }}>
              <img className="lb-img" src={preview(prevPhoto)} alt="" />
            </div>
          )}
          <div key={photo.id} className="lb-slide" style={{ zIndex: 1 }}>
            <img
              ref={swipe.imgRef}
              className={`lb-img${exit ? (exitSelf ? ' lb-hidden' : ' lb-enter') : ''}`}
              style={swipe.imgStyle}
              onTransitionEnd={swipe.onImgTransitionEnd}
              src={preview(photo)}
              alt={photo.filename}
              onClick={(e) => e.stopPropagation()}
            />
          </div>
          {nextPhoto &&
            (swipe.showBehind ? (
              <div
                key={nextPhoto.id}
                ref={swipe.behindRef}
                className="lb-slide lb-behind"
                style={swipe.behindStyle}
              >
                <img className="lb-img" src={preview(nextPhoto)} alt="" />
              </div>
            ) : (
              <div key={nextPhoto.id} className="lb-slide" style={{ transform: 'translateX(100vw)' }}>
                <img className="lb-img" src={preview(nextPhoto)} alt="" />
              </div>
            ))}
        </div>
        {exit && (
          <div
            className="lb-slide lb-ghost"
            style={{ '--dy': `${exit.dy}px`, '--rot': `${exit.dy / 40}deg` }}
            onAnimationEnd={swipe.clearExit}
          >
            <img className={`lb-img lb-exit-${exit.dir}`} src={preview(exit.photo)} alt="" />
          </div>
        )}
        {swipe.showBadge && <div ref={swipe.badgeRef} className="lb-flick" />}
      </div>
      <button
        className="lb-nav lb-next"
        disabled={index === photos.length - 1}
        onClick={(e) => {
          e.stopPropagation()
          onNav(index + 1)
        }}
      >
        ›
      </button>
      {footer && (
        <div className="lb-footer" onClick={(e) => e.stopPropagation()}>
          {footer(photo)}
        </div>
      )}
      {showHelp && (
        <div
          className="lb-help"
          onClick={(e) => {
            e.stopPropagation()
            setShowHelp(false)
          }}
        >
          <div className="lb-help-card">
            <h3>Keyboard shortcuts</h3>
            {[...(actions?.map((a) => a.help) || []), ...BASE_SHORTCUTS].map(([keys, label]) => (
              <div key={keys} className="lb-help-row">
                <kbd>{keys}</kbd>
                <span className="muted">{label}</span>
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  )
}
