import {
  useRef,
  useEffect,
  useState,
  useCallback,
  forwardRef,
  useImperativeHandle,
  useMemo,
} from 'react'

import videojs from 'video.js'
import Player from 'video.js/dist/types/player'
import 'video.js/dist/video-js.css'

export interface VideoPlayerRef {
  play: () => Promise<void>
  pause: () => void
}

interface VideoPlayerProps {
  src: string
  /**
   * The format/MIME type of the video.
   * @default 'video/mp4'
   */
  format?: string
  muted?: boolean
  poster?: string
  autoplay?: boolean
  width?: number
  height?: number
  playButtonComponent?: React.ReactNode
  onPlay?: () => void
  onPause?: () => void
  onEnded?: () => void
  onReady?: () => void
}

export const VideoPlayer: React.ForwardRefExoticComponent<
  VideoPlayerProps & React.RefAttributes<VideoPlayerRef>
> = forwardRef<VideoPlayerRef, VideoPlayerProps>(
  (
    {
      src,
      format = 'video/mp4',
      poster,
      muted,
      autoplay,
      width,
      height,
      playButtonComponent,
      onPlay,
      onPause,
      onEnded,
      onReady,
    },
    ref
  ) => {
    const videoContainerRef = useRef<HTMLDivElement>(null)
    const playerRef = useRef<Player | null>(null)

    const [isPlaying, setIsPlaying] = useState(false)

    // Expose imperative methods to parent components
    useImperativeHandle(
      ref,
      () => ({
        play: async () => {
          if (playerRef.current) {
            try {
              await playerRef.current.play()
              setIsPlaying(true)
            } catch (error) {
              console.error('Error playing video:', error)
            }
          }
        },
        pause: () => {
          if (playerRef.current) {
            playerRef.current.pause()
            setIsPlaying(false)
          }
        },
      }),
      []
    )

    const options = useMemo(
      () => ({
        controls: false,
        controlBar: {
          volumePanel: {
            inline: false,
          },
        },
        preload: 'auto',
        muted,
        // This makes sure that the video player does not take over the whole screen
        preferFullWindow: false,
        playsinline: true,
        poster,
        height,
        width,
        sources: [
          {
            src,
            type: format,
          },
        ],
      }),
      [muted, poster, height, width, src, format]
    )

    useEffect(() => {
      const player = playerRef.current

      return () => {
        if (player && !player.isDisposed()) {
          player.dispose()
          playerRef.current = null
        }
      }
    }, [playerRef])

    useEffect(() => {
      if (!playerRef.current) return

      const handlePlay = () => {
        onPlay?.()
        setIsPlaying(true)
      }
      const handlePause = () => {
        onPause?.()
        setIsPlaying(false)
      }
      const handleEnded = () => {
        onEnded?.()
        setIsPlaying(false)
      }

      playerRef.current.on('play', handlePlay)
      playerRef.current.on('pause', handlePause)
      playerRef.current.on('ended', handleEnded)

      return () => {
        if (playerRef.current) {
          playerRef.current.off('play', handlePlay)
          playerRef.current.off('pause', handlePause)
          playerRef.current.off('ended', handleEnded)
        }
      }
    }, [onEnded, onPause, onPlay])

    const togglePlayPause = useCallback(() => {
      if (isPlaying) {
        playerRef.current?.pause()
        setIsPlaying(false)
      } else {
        playerRef.current?.play()
        setIsPlaying(true)
      }
    }, [isPlaying])

    useEffect(() => {
      if (!playerRef.current) {
        const videoElement = document.createElement('video-js')

        // The Video.js player needs to be _inside_ the component element for React 18 Strict Mode.
        videoContainerRef.current?.appendChild(videoElement)

        playerRef.current = videojs(videoElement, options, () => {
          onReady && onReady()

          if (autoplay) {
            togglePlayPause()
          }
        })
      }
    }, [options, videoContainerRef, autoplay, onReady, togglePlayPause])

    const containerClassName = [
      width ? `w-${width}` : undefined,
      'relative',
    ].join(' ')

    return (
      <div className="flex">
        <div data-vjs-player className={containerClassName}>
          <div ref={videoContainerRef} />
          <div
            className="absolute top-0 left-0 w-full h-full flex items-center justify-center cursor-pointer"
            onClick={togglePlayPause}
            aria-hidden
          >
            {isPlaying ? null : (playButtonComponent ?? <DefaultPlayIcon />)}
          </div>
        </div>
      </div>
    )
  }
)

const DefaultPlayIcon = () => (
  <div className="absolute inset-0 flex items-center justify-center bg-black/20 cursor-pointer animate-in fade-in duration-200">
    <svg
      viewBox="0 0 24 24"
      className="w-10 h-10 text-white"
      fill="currentColor"
    >
      <path d="M8 5v14l11-7z" />
    </svg>
  </div>
)
