import { useEffect, useRef, useState } from 'react'
import WaveSurfer from 'wavesurfer.js'
import RegionsPlugin from 'wavesurfer.js/dist/plugins/regions.esm.js'
import { Play, Pause, ZoomIn, ZoomOut, Repeat, X } from 'lucide-react'
import { mmss } from '@/lib/format'

interface Props {
  url: string
  dur: number
  /**
   * Precomputed waveform overview (0..1 per bucket). Without it wavesurfer
   * downloads and DECODES the whole file before drawing — fine for a 3-minute
   * take, a blank screen for an 80-minute set's 100 MB tracks. With peaks it
   * paints immediately and the media element streams the audio by Range.
   */
  peaks?: number[]
  /** called with current playback time (s) on every frame + on seek */
  onTime: (t: number) => void
  /** fired when the track reaches its end — drives auto-advance */
  onFinish?: () => void
  /** start playing as soon as the transport is ready (auto-advance landing) */
  autoPlay?: boolean
}

const MIN_ZOOM = 0 // 0 = fit-to-width
const MAX_ZOOM = 600 // px per second
const ZOOM_STEP = 1.6

/**
 * A take's transport: wavesurfer waveform with Audacity-style zoom/scroll and
 * drag-to-select + loop. Zoom: +/- buttons or ctrl/⌘ + wheel. Loop: drag a span
 * on the waveform, toggle Repeat to loop it (great for auditing a transition or
 * a single moment while iterating on the sound). Space = play/pause.
 */
export function WaveformPlayer({ url, dur, peaks, onTime, onFinish, autoPlay }: Props) {
  const elRef = useRef<HTMLDivElement>(null)
  const wsRef = useRef<WaveSurfer | null>(null)
  const regionsRef = useRef<ReturnType<typeof RegionsPlugin.create> | null>(null)
  const activeRegionRef = useRef<{ start: number; end: number; play: (from?: boolean) => void } | null>(null)
  const onTimeRef = useRef(onTime)
  onTimeRef.current = onTime
  const onFinishRef = useRef(onFinish)
  onFinishRef.current = onFinish
  const autoPlayRef = useRef(autoPlay)
  autoPlayRef.current = autoPlay
  const loopRef = useRef(true)

  const [playing, setPlaying] = useState(false)
  const [ready, setReady] = useState(false)
  const [t, setT] = useState(0)
  const [zoom, setZoom] = useState(MIN_ZOOM)
  const [loop, setLoop] = useState(true)
  const [region, setRegion] = useState<{ start: number; end: number } | null>(null)

  useEffect(() => {
    loopRef.current = loop
  }, [loop])

  useEffect(() => {
    if (!elRef.current) return
    const regions = RegionsPlugin.create()
    // With precomputed peaks we must ALSO supply the media element. Passing
    // `peaks` + `url` alone makes wavesurfer treat the track as fully
    // pre-decoded and it never wires up playback — the waveform draws, the
    // transport looks fine, and pressing play throws "No audio loaded". Owning
    // the <audio> keeps rendering (peaks) and playback (Range-streamed) separate.
    const media = peaks?.length ? new Audio() : undefined
    if (media) {
      media.preload = 'metadata'
      media.crossOrigin = 'anonymous'
      media.src = url
    }
    const ws = WaveSurfer.create({
      container: elRef.current,
      height: 76,
      waveColor: '#6a6a70', // ink-faint
      progressColor: '#e8e8ea', // ink
      cursorColor: '#d900ff', // brand magenta — the now
      cursorWidth: 2,
      barWidth: 2,
      barGap: 1,
      barRadius: 1,
      normalize: true,
      autoScroll: true,
      // Either we own the media (peaks path) or wavesurfer fetches + decodes
      // the file itself (fine for the short heads).
      ...(media ? { media, peaks: [peaks!], duration: dur } : { url }),
      plugins: [regions],
    })
    wsRef.current = ws
    regionsRef.current = regions
    setReady(false)
    setPlaying(false)
    setRegion(null)
    activeRegionRef.current = null

    const emit = (time: number) => {
      setT(time)
      onTimeRef.current(time)
    }
    ws.on('ready', () => {
      setReady(true)
      // Auto-advance lands here. Browsers only allow this because the user
      // already gestured on the previous track; if they haven't, play()
      // rejects and we simply stay paused rather than throwing.
      if (autoPlayRef.current) ws.play().catch(() => {})
    })
    ws.on('timeupdate', emit)
    ws.on('interaction', () => {
      activeRegionRef.current = null
      emit(ws.getCurrentTime())
    })
    ws.on('play', () => setPlaying(true))
    ws.on('pause', () => setPlaying(false))
    ws.on('finish', () => {
      setPlaying(false)
      onFinishRef.current?.()
    })

    // single drag-selection that can be looped
    regions.enableDragSelection({ color: 'rgba(217, 0, 255, 0.12)' })
    regions.on('region-created', (r) => {
      for (const other of regions.getRegions()) if (other.id !== r.id) other.remove()
      activeRegionRef.current = r
      setRegion({ start: r.start, end: r.end })
    })
    regions.on('region-updated', (r) => setRegion({ start: r.start, end: r.end }))
    regions.on('region-in', (r) => {
      activeRegionRef.current = r
    })
    regions.on('region-out', (r) => {
      if (activeRegionRef.current === r && loopRef.current) r.play(true)
    })
    regions.on('region-clicked', (r, e) => {
      e.stopPropagation()
      activeRegionRef.current = r
      r.play(true)
    })

    return () => {
      ws.destroy()
      if (media) {
        media.pause()
        media.removeAttribute('src')
        media.load()          // release the connection; otherwise switching
      }                       // tracks leaves the old stream downloading
    }
  }, [url, peaks, dur])

  // apply zoom when it changes (and once ready)
  useEffect(() => {
    if (ready && wsRef.current) wsRef.current.zoom(zoom)
  }, [zoom, ready])

  const toggle = () => wsRef.current?.playPause()
  const zoomBy = (factor: number) =>
    setZoom((z) => {
      const base = z <= 0 ? 40 : z
      return Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, factor > 1 ? base * factor : z <= 40 ? 0 : z / ZOOM_STEP))
    })
  const clearRegion = () => {
    regionsRef.current?.clearRegions()
    activeRegionRef.current = null
    setRegion(null)
  }

  return (
    <div
      tabIndex={0}
      onKeyDown={(e) => {
        if (e.code === 'Space') {
          e.preventDefault()
          toggle()
        } else if (e.key === 'l') {
          setLoop((v) => !v)
        } else if (e.key === '+' || e.key === '=') {
          zoomBy(ZOOM_STEP)
        } else if (e.key === '-') {
          zoomBy(1 / ZOOM_STEP)
        }
      }}
      onWheel={(e) => {
        if (e.ctrlKey || e.metaKey) {
          e.preventDefault()
          zoomBy(e.deltaY < 0 ? ZOOM_STEP : 1 / ZOOM_STEP)
        }
      }}
      className="group rounded-md outline-none focus-visible:ring-2 focus-visible:ring-magenta/60"
    >
      <div className="flex items-center gap-3">
        <button
          onClick={toggle}
          disabled={!ready}
          aria-label={playing ? 'Pause' : 'Play'}
          className="flex h-10 w-10 shrink-0 items-center justify-center rounded-md
                     bg-ink text-surface transition-opacity hover:opacity-90
                     disabled:opacity-30"
        >
          {playing ? <Pause size={18} fill="currentColor" /> : <Play size={18} fill="currentColor" />}
        </button>
        <div ref={elRef} className="min-w-0 flex-1" />
      </div>

      <div className="mt-1 flex items-center justify-between gap-2 font-mono text-xs text-ink-muted tnum">
        <span>{ready ? mmss(t) : 'loading…'}</span>

        <div className="flex items-center gap-1">
          {region && (
            <>
              <button
                onClick={() => setLoop((v) => !v)}
                title="Loop the selection (L)"
                aria-pressed={loop}
                className={`flex items-center gap-1 rounded px-1.5 py-0.5 transition-colors ${
                  loop ? 'bg-magenta/15 text-magenta' : 'text-ink-faint hover:text-ink'
                }`}
              >
                <Repeat size={12} /> {mmss(region.start)}–{mmss(region.end)}
              </button>
              <button
                onClick={clearRegion}
                title="Clear selection"
                className="flex items-center rounded px-1 py-0.5 text-ink-faint hover:text-ink"
              >
                <X size={12} />
              </button>
              <span className="mx-1 text-hairline">|</span>
            </>
          )}
          <button
            onClick={() => zoomBy(1 / ZOOM_STEP)}
            disabled={!ready}
            title="Zoom out (− / ctrl-wheel)"
            className="flex items-center rounded px-1 py-0.5 text-ink-faint hover:text-ink disabled:opacity-30"
          >
            <ZoomOut size={13} />
          </button>
          <button
            onClick={() => zoomBy(ZOOM_STEP)}
            disabled={!ready}
            title="Zoom in (+ / ctrl-wheel)"
            className="flex items-center rounded px-1 py-0.5 text-ink-faint hover:text-ink disabled:opacity-30"
          >
            <ZoomIn size={13} />
          </button>
          <span className="w-14 text-right text-ink-faint">{mmss(dur)}</span>
        </div>
      </div>

      {!region && (
        <p className="mt-0.5 text-[10px] text-ink-faint/70">
          drag on the waveform to select &amp; loop a span · ctrl-wheel to zoom
        </p>
      )}
    </div>
  )
}
