import { useState, useEffect, useRef, useCallback } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import clsx from 'clsx'
import type { Photo } from '../../types/photo'
import {
  getLoupeImageSrc,
  getLoupeFallbackSrc,
  getVideoSrc,
  isVideo,
} from './loupeSrc'

interface LoupeImageProps {
  photo: Photo
}

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

export function LoupeImage({ photo }: LoupeImageProps) {
  if (isVideo(photo)) {
    return <LoupeVideo photo={photo} />
  }
  return <LoupeStillImage photo={photo} />
}

function LoupeVideo({ photo }: { photo: Photo }) {
  return (
    <div className="flex flex-1 items-center justify-center bg-black">
      <video
        key={photo.id}
        src={getVideoSrc(photo)}
        controls
        autoPlay
        muted
        className="max-h-full max-w-full"
      />
    </div>
  )
}

function LoupeStillImage({ 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 = getLoupeImageSrc(photo)
  const fallbackSrc = getLoupeFallbackSrc(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()
  }, [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 }}
    >
      {!loaded && (
        <div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-text-muted">
          <div className="h-10 w-10 animate-spin rounded-full border-2 border-primary/30 border-t-primary" />
        </div>
      )}
      <img
        ref={imgRef}
        key={`${photo.id}-${usingFallback}`}
        src={src}
        alt={photo.filename}
        loading="eager"
        decoding="async"
        draggable={false}
        onLoad={() => setLoaded(true)}
        onError={handleError}
        className={clsx(
          'max-h-full max-w-full object-contain transition-opacity duration-150',
          loaded ? 'opacity-100' : 'opacity-0'
        )}
        style={{
          transform: `translate(${offset.x}px, ${offset.y}px) scale(${scale})`,
          transformOrigin: 'center center',
          // Disable transition during pan/zoom — only fade-in is animated.
          transition: 'opacity 150ms',
          willChange: 'transform',
        }}
      />

      {/* 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>
  )
}
