import { CircleNotchIcon, PauseIcon, PlayIcon } from '@phosphor-icons/react'
import React, { useCallback, useEffect, useRef, useState } from 'react'

import { isDevBuild } from '../../utils/isDevBuild'
import { nativePlayerProps } from '../../utils/nativeMediaPlayers'

import { renderTypeIcon } from './utils/icons'
import { getSourceType } from './utils/mimeType'

type TouchEventUnion =
  MouseEvent | TouchEvent | React.MouseEvent | React.TouchEvent

const getClientXFromEvent = (e: TouchEventUnion): number => {
  if ('touches' in e) {
    return e.touches[0]?.clientX ?? e.changedTouches[0]?.clientX ?? 0
  }
  return e.clientX
}

export interface MediaPlayerProps {
  source: string
  mimeType: string
  poster?: string
  autoPlay?: boolean
  /** Controlled playing state. When provided, syncs to internal play/pause. */
  playing?: boolean
  loop?: boolean
  controls?: boolean
  showProgress?: boolean
  /** When true, requests muted playback (helps autoplay policies on video). */
  muted?: boolean
  /**
   * When provided, overrides the default click-to-play-toggle behaviour on the
   * player container. The play/pause button (which calls stopPropagation) is
   * unaffected, so inline playback still works.
   */
  onContainerClick?: (e: React.MouseEvent) => void
}

const MediaPlayer: React.FC<MediaPlayerProps> = ({
  source,
  mimeType,
  poster,
  autoPlay = false,
  playing: playingProp,
  loop = false,
  controls = true,
  showProgress = false,
  muted = false,
  onContainerClick,
}) => {
  // --- Derived ---
  const sourceType = getSourceType(mimeType)

  // --- Refs ---
  const playerRef = useRef<HTMLMediaElement>(null)
  const trackRef = useRef<HTMLDivElement>(null)
  const rafRef = useRef<number | null>(null)
  const prevPlayingPropRef = useRef(playingProp)

  // --- State: playback ---
  const [playing, setPlaying] = useState(autoPlay)
  const [played, setPlayed] = useState(0)
  const [seeking, setSeeking] = useState(false)

  // --- State: UI ---
  const [scrubberHovered, setScrubberHovered] = useState(false)
  /** Set when autoplay/play() was rejected so user can start via gesture (no controls UI). */
  const [manualPlayRequired, setManualPlayRequired] = useState(false)

  // --- State: loading ---
  const [buffering, setBuffering] = useState(false)
  /** True until the first canPlay fires for the current source — hides controls/spinner behind poster. */
  const [initialLoad, setInitialLoad] = useState(true)
  const [videoAspect, setVideoAspect] = useState<number | null>(null)

  // --- Callbacks ---
  const startPlaybackFromGesture = useCallback(() => {
    setManualPlayRequired(false)
    setPlaying(true)
  }, [])

  const getFraction = useCallback((e: TouchEventUnion) => {
    const track = trackRef.current
    if (!track) return 0
    const rect = track.getBoundingClientRect()
    return Math.max(
      0,
      Math.min(1, (getClientXFromEvent(e) - rect.left) / rect.width)
    )
  }, [])

  const seekTo = useCallback((fraction: number) => {
    const el = playerRef.current
    if (el && el.duration) el.currentTime = fraction * el.duration
  }, [])

  const handleTrackPointerDown = (
    e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>
  ) => {
    e.stopPropagation()
    setSeeking(true)
    const fraction = getFraction(e)
    setPlayed(fraction)
    seekTo(fraction)
  }

  const handleScrubberKeyDown = useCallback(
    (e: React.KeyboardEvent<HTMLDivElement>) => {
      if (e.key === 'ArrowRight') seekTo(Math.min(1, played + 0.05))
      if (e.key === 'ArrowLeft') seekTo(Math.max(0, played - 0.05))
    },
    [seekTo, played]
  )

  // --- Effects ---

  // Sync controlled playing prop to internal state
  useEffect(() => {
    if (
      playingProp !== undefined &&
      playingProp !== prevPlayingPropRef.current
    ) {
      prevPlayingPropRef.current = playingProp
      setPlaying(playingProp)
    }
  }, [playingProp])

  // RAF-driven progress updates
  useEffect(() => {
    if (!playing) {
      if (rafRef.current !== null) {
        cancelAnimationFrame(rafRef.current)
        rafRef.current = null
      }
      return
    }
    const tick = () => {
      const el = playerRef.current
      if (el && el.duration && !seeking) setPlayed(el.currentTime / el.duration)
      rafRef.current = requestAnimationFrame(tick)
    }
    rafRef.current = requestAnimationFrame(tick)
    return () => {
      if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)
    }
  }, [playing, seeking])

  // ReactPlayer v3 uses native HTML media elements and does not support a
  // declarative `playing` prop — playback must be driven imperatively.
  useEffect(() => {
    const el = playerRef.current
    if (!el) return
    if (playing) {
      void el.play().catch((err) => {
        setPlaying(false)
        setManualPlayRequired(true)
        if (isDevBuild()) {
          console.debug('[MediaPlayer] play() failed', err)
        }
      })
    } else {
      el.pause()
    }
  }, [playing])

  // Global seeking listeners
  useEffect(() => {
    if (!seeking) return
    const onMove = (e: MouseEvent | TouchEvent) => setPlayed(getFraction(e))
    const onUp = (e: MouseEvent | TouchEvent) => {
      setSeeking(false)
      seekTo(getFraction(e))
    }
    window.addEventListener('mousemove', onMove)
    window.addEventListener('mouseup', onUp)
    window.addEventListener('touchmove', onMove, { passive: true })
    window.addEventListener('touchend', onUp)
    return () => {
      window.removeEventListener('mousemove', onMove)
      window.removeEventListener('mouseup', onUp)
      window.removeEventListener('touchmove', onMove)
      window.removeEventListener('touchend', onUp)
    }
  }, [seeking, getFraction, seekTo])

  // --- Derived render values ---
  // Use natural aspect ratio once metadata loads, fall back to 16:9 before then.
  const aspectStyle = videoAspect
    ? { aspectRatio: String(videoAspect) }
    : undefined
  const aspectClass = !videoAspect ? ' aspect-video' : ''
  const scrubberPercent = Math.round(played * 100)

  return (
    <div
      role="button"
      tabIndex={0}
      className={`relative cursor-pointer overflow-hidden bg-black ${aspectClass}`}
      style={aspectStyle}
      onClick={(e) => {
        if (onContainerClick) {
          onContainerClick(e)
          return
        }
        if (manualPlayRequired) return
        if (controls) setPlaying((p) => !p)
      }}
      onKeyDown={(e) => {
        if (e.key !== 'Enter' && e.key !== ' ') return
        e.preventDefault()
        if (onContainerClick) {
          onContainerClick(e as unknown as React.MouseEvent)
          return
        }
        if (manualPlayRequired) return
        if (controls) setPlaying((p) => !p)
      }}
    >
      {/* For audio, poster persists as a visual background. For video, hide once loaded. */}
      {poster && (sourceType === 'audio' || initialLoad) && (
        <img
          src={poster}
          alt=""
          className="absolute inset-0 h-full w-full object-cover"
        />
      )}
      {!poster && (sourceType === 'audio' || initialLoad) && (
        <div className="absolute inset-0 flex items-center justify-center">
          {renderTypeIcon(mimeType, {
            className: 'size-12 text-black/20',
            weight: 'regular',
          })}
        </div>
      )}
      <div className="absolute inset-0">
        {sourceType === 'audio' ? (
          <audio
            {...nativePlayerProps}
            ref={playerRef as React.RefObject<HTMLAudioElement>}
            src={source}
            loop={loop}
            muted={muted}
            style={{ width: '100%', height: '100%' }}
            onLoadStart={() => setBuffering(true)}
            onCanPlay={() => {
              setBuffering(false)
              setInitialLoad(false)
            }}
            onWaiting={() => setBuffering(true)}
            onPlay={() => setManualPlayRequired(false)}
            onEnded={() => {
              if (!loop) {
                setPlaying(false)
                setPlayed(0)
              }
            }}
          >
            <track kind="captions" />
          </audio>
        ) : (
          <video
            ref={playerRef as React.RefObject<HTMLVideoElement>}
            src={source}
            loop={loop}
            muted={muted}
            playsInline
            style={{ width: '100%', height: '100%' }}
            onLoadStart={() => setBuffering(true)}
            onCanPlay={() => {
              setBuffering(false)
              setInitialLoad(false)
            }}
            onWaiting={() => setBuffering(true)}
            onPlay={() => setManualPlayRequired(false)}
            onLoadedMetadata={() => {
              const el = playerRef.current
              if (
                el instanceof HTMLVideoElement &&
                el.videoWidth &&
                el.videoHeight
              ) {
                setVideoAspect(el.videoWidth / el.videoHeight)
              }
            }}
            onEnded={() => {
              if (!loop) {
                setPlaying(false)
                setPlayed(0)
              }
            }}
          >
            <track kind="captions" />
          </video>
        )}
      </div>

      {buffering && !manualPlayRequired && (
        <div className="absolute inset-0 z-10 flex items-center justify-center">
          <CircleNotchIcon
            className="size-8 animate-spin text-white/80"
            weight="bold"
          />
        </div>
      )}

      {manualPlayRequired && !controls && (
        <div
          className="absolute inset-0 z-30 flex cursor-pointer items-center justify-center bg-black/35"
          role="button"
          tabIndex={0}
          aria-label="Play preview"
          onClick={(e) => {
            e.stopPropagation()
            startPlaybackFromGesture()
          }}
          onKeyDown={(e) => {
            if (e.key !== 'Enter' && e.key !== ' ') return
            e.preventDefault()
            e.stopPropagation()
            startPlaybackFromGesture()
          }}
        >
          <span className="flex size-16 items-center justify-center rounded-full bg-white/20 text-white backdrop-blur-sm">
            <PlayIcon className="size-9 translate-x-0.5" weight="fill" />
          </span>
        </div>
      )}

      {showProgress && !controls && (
        <div className="absolute inset-x-0 bottom-0 px-3 pb-2.5 pt-6 bg-gradient-to-t from-black/40 to-transparent">
          <div
            role="slider"
            aria-label="Playback position"
            aria-valuenow={scrubberPercent}
            aria-valuemin={0}
            aria-valuemax={100}
            tabIndex={0}
            ref={trackRef}
            className="relative flex h-4 w-full cursor-pointer items-center"
            onMouseDown={handleTrackPointerDown}
            onTouchStart={handleTrackPointerDown}
            onClick={(e) => e.stopPropagation()}
            onKeyDown={handleScrubberKeyDown}
          >
            <div className="w-full overflow-hidden rounded-full bg-white/30 h-1">
              <div
                className="h-full rounded-full bg-white"
                style={{ width: `${scrubberPercent}%` }}
              />
            </div>
          </div>
        </div>
      )}

      {controls && (
        <div className="absolute inset-x-0 bottom-0 flex items-center gap-2 bg-gradient-to-t from-black/60 to-transparent px-3 pb-2.5 pt-6 transition-all duration-200">
          <button
            type="button"
            onClick={(e) => {
              e.stopPropagation()
              setPlaying((p) => !p)
            }}
            className="shrink-0 text-white"
            aria-label={playing ? 'Pause' : 'Play'}
          >
            {playing ? (
              <PauseIcon className="size-5" weight="fill" />
            ) : (
              <PlayIcon className="size-5 translate-x-px" weight="fill" />
            )}
          </button>

          <div
            role="slider"
            aria-label="Playback position"
            aria-valuenow={scrubberPercent}
            aria-valuemin={0}
            aria-valuemax={100}
            tabIndex={0}
            ref={trackRef}
            className="relative flex h-4 w-full cursor-pointer items-center"
            onMouseDown={handleTrackPointerDown}
            onTouchStart={handleTrackPointerDown}
            onClick={(e) => e.stopPropagation()}
            onMouseEnter={() => setScrubberHovered(true)}
            onMouseLeave={() => setScrubberHovered(false)}
            onKeyDown={handleScrubberKeyDown}
          >
            <div
              className={`w-full overflow-hidden rounded-full bg-white/30 transition-all duration-200 ${scrubberHovered || seeking ? 'h-1.5' : 'h-1'}`}
            >
              <div
                className="h-full rounded-full bg-white"
                style={{ width: `${scrubberPercent}%` }}
              />
            </div>
            <div
              className={`absolute size-3 -translate-x-1/2 rounded-full bg-white shadow transition-[opacity,transform] duration-200 ${scrubberHovered || seeking ? 'scale-100 opacity-100' : 'scale-0 opacity-0'}`}
              style={{ left: `${scrubberPercent}%` }}
            />
          </div>
        </div>
      )}
    </div>
  )
}

export default MediaPlayer
