import { useState, useEffect, useRef, useCallback } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import type { Photo } from '../../types/photo'
import {
  getPreviewImageSrc,
  getPreviewFallbackSrc,
  getVideoSrc,
  isVideo,
} from './previewSrc'

interface PreviewImageProps {
  photo: Photo
}

const MIN_SCALE = 1
const MAX_SCALE = 8
const WHEEL_STEP = 1.15

export function PreviewImage({ photo }: PreviewImageProps) {
  if (isVideo(photo)) {
    return <PreviewVideo photo={photo} />
  }
  return <PreviewStillImage photo={photo} />
}

function PreviewVideo({ photo }: { photo: Photo }) {
  // min-h-0 + overflow-hidden are needed so a portrait/vertical video
  // doesn't push past the column's allotted height and shove the
  // filmstrip off-screen — flex items default to min-height:auto, which
  // makes intrinsically-tall content overflow.
  return (
    <div className="flex min-h-0 flex-1 items-center justify-center overflow-hidden bg-black">
      <video
        key={photo.id}
        src={getVideoSrc(photo)}
        controls
        autoPlay
        muted
        className="max-h-full max-w-full object-contain"
      />
    </div>
  )
}

function PreviewStillImage({ photo }: { photo: Photo }) {
  const [loaded, setLoaded] = useState(false)
  const [usingFallback, setUsingFallback] = useState(false)

  // scale=1 means "fit to viewport". Anything >1 zooms in; we don't allow <1
  // because the fit size already fills the viewport.
  const [scale, setScale] = useState(1)
  const [offset, setOffset] = useState({ x: 0, y: 0 })
  const dragStateRef = useRef<{ x: number; y: number; ox: number; oy: number } | null>(null)
  const imgRef = useRef<HTMLImageElement>(null)

  // Reset everything when the photo changes.
  useEffect(() => {
    setLoaded(false)
    setUsingFallback(false)
    setScale(1)
    setOffset({ x: 0, y: 0 })
  }, [photo.id])

  const primarySrc = getPreviewImageSrc(photo)
  const fallbackSrc = getPreviewFallbackSrc(photo)
  const src = usingFallback ? fallbackSrc : primarySrc

  const handleError = () => {
    if (!usingFallback && primarySrc !== fallbackSrc) {
      setUsingFallback(true)
    }
  }

  // Z key: toggle between fit (scale=1) and actual size (natural/displayed).
  // If we're already zoomed (manual wheel zoom), Z snaps back to fit.
  const toggleZoom = useCallback(() => {
    if (scale !== 1) {
      setScale(1)
      setOffset({ x: 0, y: 0 })
      return
    }
    const img = imgRef.current
    if (!img) return
    const ratio = img.naturalWidth / img.clientWidth
    if (!isFinite(ratio) || ratio <= 1) return
    setScale(Math.min(ratio, MAX_SCALE))
  }, [scale])

  useHotkeys(
    'z',
    (e) => {
      e.preventDefault()
      toggleZoom()
    },
    { preventDefault: true },
    [toggleZoom]
  )

  const handleWheel = (e: React.WheelEvent) => {
    e.preventDefault()
    const delta = e.deltaY < 0 ? WHEEL_STEP : 1 / WHEEL_STEP
    setScale((prev) => {
      const next = Math.min(MAX_SCALE, Math.max(MIN_SCALE, prev * delta))
      // Snapping back to 1 also resets pan offset.
      if (next === 1) setOffset({ x: 0, y: 0 })
      return next
    })
  }

  const handleMouseDown = (e: React.MouseEvent) => {
    if (scale === 1) return
    e.preventDefault()
    dragStateRef.current = {
      x: e.clientX,
      y: e.clientY,
      ox: offset.x,
      oy: offset.y,
    }
  }

  const handleMouseMove = (e: React.MouseEvent) => {
    const drag = dragStateRef.current
    if (!drag) return
    setOffset({
      x: drag.ox + (e.clientX - drag.x),
      y: drag.oy + (e.clientY - drag.y),
    })
  }

  const endDrag = () => {
    dragStateRef.current = null
  }

  const isZoomed = scale > 1
  const cursor = isZoomed
    ? dragStateRef.current
      ? 'grabbing'
      : 'grab'
    : 'zoom-in'

  return (
    <div
      className="relative flex flex-1 select-none items-center justify-center overflow-hidden bg-black"
      onWheel={handleWheel}
      onMouseDown={handleMouseDown}
      onMouseMove={handleMouseMove}
      onMouseUp={endDrag}
      onMouseLeave={endDrag}
      style={{ cursor }}
    >
      <img
        ref={imgRef}
        key={`${photo.id}-${usingFallback}`}
        src={src}
        alt={photo.filename}
        loading="eager"
        decoding="async"
        draggable={false}
        onLoad={() => setLoaded(true)}
        onError={handleError}
        className="max-h-full max-w-full object-contain"
        style={{
          transform: `translate(${offset.x}px, ${offset.y}px) scale(${scale})`,
          transformOrigin: 'center center',
          willChange: 'transform',
        }}
      />
      {!loaded && (
        <div className="pointer-events-none absolute inset-0 flex items-center justify-center text-text-muted">
          <div className="h-10 w-10 animate-spin rounded-full border-2 border-primary/30 border-t-primary" />
        </div>
      )}

      {/* Zoom indicator */}
      {isZoomed && (
        <div className="pointer-events-none absolute bottom-3 left-1/2 -translate-x-1/2 rounded bg-black/60 px-2 py-1 text-xs font-mono text-white">
          {Math.round(scale * 100)}%
        </div>
      )}
    </div>
  )
}
