import React from 'react'
import PropTypes from 'prop-types'
import fscreen from 'fscreen'
import styled from 'styled-components'
import {
  useThemeTokens,
  selectSystemProps,
  getTokensPropType,
  variantProp
} from '@telus-uds/components-base'
import { Spinner } from '../Spinner/Spinner'
import { warn, htmlAttrs } from '../utils'
import { VideoSplash } from '../shared/VideoSplash/VideoSplash'
import { MiddleControlButton } from './MiddleControlButton/MiddleControlButton'
import { ControlBar } from './ControlBar/ControlBar'
import { videoText } from './videoText'

const [selectProps, selectedSystemPropTypes] = selectSystemProps([htmlAttrs])

// TODO: replace with the actual spinner component from UDS
const VideoPlayerContainer = styled.div(({ $videoBorder, $borderColor }) => ({
  width: '100%',
  outline: 'none',
  position: 'relative',
  border: $videoBorder ? `20px solid ${$borderColor}` : undefined
}))

const VideoElementContainer = styled.div({
  outline: 'none',
  width: '100%',
  height: '100%',
  fontSize: 0
})

const VideoPlayer = styled.video({
  width: '100%',
  height: '100%'
})

const VideoOverlayContainer = styled.div(({ $mouseInactive }) => ({
  width: '100%',
  height: '100%',
  position: 'absolute',
  cursor: $mouseInactive ? 'none' : 'pointer'
}))

const VideoSplashContainer = styled.div({
  position: 'absolute',
  width: '100%',
  height: '100%',
  zIndex: 5
})

const VideoOverlayElementContainer = styled.div({
  position: 'absolute',
  zIndex: 4,
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center',
  width: '100%',
  height: '100%'
})

export const Video = React.forwardRef(
  (
    {
      analyticsTracking,
      videoTitle,
      videoBorder = false,
      simpleMode = false,
      copy,
      posterSrc,
      crossOrigin,
      defaultVolume = 100,
      beginMuted = false,
      sources,
      tracks,
      defaultDesktopQuality = 1,
      defaultMobileQuality = 1,
      tokens,
      variant = {},
      ...rest
    },
    ref
  ) => {
    const refVideoPlayer = React.useRef({})
    const refVideoPlayerContainer = React.useRef({})
    const refKeyboardInstructions = React.useRef({})

    let inactivityTimer = null

    const { borderColor, pauseIcon, playIcon, replayIcon } = useThemeTokens(
      'Video',
      tokens,
      variant
    )

    const playerOptions = {
      mouseTimeout: simpleMode ? 750 : 1500, // defined in ms
      keyboardSeekIncrement: 5, // defined in s
      keyboardVolumeIncrement: 0.1, // from 0 to 1
      compactModeThreshold: 700 // in px
    }

    const [videoPlayerState, setVideoPlayerState] = React.useState({
      loadedSources: null,
      loadedTracks: null,
      videoLength: 0,
      videoCurrentTime: -1,
      videoBufferEnd: 0,
      videoUnplayed: true,
      videoIsBuffering: false,
      videoCurrentVolume: 1,
      videoIsPlaying: false,
      videoIsMuted: false,
      videoIsFullscreen: false,
      mouseInactive: false,
      videoEnded: false,
      videoQuality: defaultDesktopQuality,
      videoQualityChanged: false,
      selectedTextTrack: -1,
      mouseOnControlBar: false,
      qualityMenuOpen: false,
      captionsMenuOpen: false,
      isMobile: false,
      videoPlayerWidth: 0,
      percentageWatched: 'watched 0%'
    })

    const videoPlayerStateRef = React.useRef(videoPlayerState)
    videoPlayerStateRef.current = videoPlayerState

    const generateSources = (videoQuality) => {
      return sources.map((source) => {
        if (source.qualityRank === videoQuality) {
          return React.createElement('source', {
            src: source.source,
            type: source.mediaType,
            key: source.source
          })
        }

        return undefined
      })
    }

    const generateTracks = () => {
      return tracks.map((track) => {
        return React.createElement('track', {
          label: track.label,
          kind: track.kind,
          srcLang: track.language,
          src: track.source,
          default: track.isDefault,
          key: track.source
        })
      })
    }

    const refreshMedia = () => {
      const { videoUnplayed } = videoPlayerState

      // Handle mobile check
      const isMobile = navigator && navigator.userAgent.indexOf('Mobi') >= 0
      if (videoUnplayed && isMobile) {
        setVideoPlayerState((prevState) => ({
          ...prevState,
          isMobile,
          videoQuality: isMobile ? defaultMobileQuality : defaultDesktopQuality
        }))
      }

      // Load media
      setVideoPlayerState((prevState) => ({
        ...prevState,
        loadedSources: generateSources(prevState.videoQuality),
        loadedTracks: tracks && generateTracks()
      }))
    }

    const percentageBucket = (percentValue) => {
      if (percentValue < 25) {
        return setVideoPlayerState((prevState) => {
          if (prevState !== 'watched 0%') {
            return { ...prevState, percentageWatched: 'watched 0%' }
          }
          return false
        })
      }
      if (percentValue < 50) {
        return setVideoPlayerState((prevState) => {
          if (prevState !== 'watched 25%') {
            return { ...prevState, percentageWatched: 'watched 25%' }
          }
          return false
        })
      }
      if (percentValue < 75) {
        return setVideoPlayerState((prevState) => {
          if (prevState !== 'watched 50%') {
            return { ...prevState, percentageWatched: 'watched 50%' }
          }
          return false
        })
      }
      if (percentValue < 100) {
        return setVideoPlayerState((prevState) => {
          if (prevState !== 'watched 75%') {
            return { ...prevState, percentageWatched: 'watched 75%' }
          }
          return false
        })
      }
      if (percentValue === 100) {
        return setVideoPlayerState((prevState) => {
          if (prevState !== 'watched 100%') {
            return { ...prevState, percentageWatched: 'watched 100%' }
          }
          return false
        })
      }
      return false
    }

    // required for analytics
    const calculatePercentageWatched = () => {
      const { videoCurrentTime, videoLength, percentageWatched } = videoPlayerState

      let percentValue = (videoCurrentTime / videoLength) * 100
      percentValue = Math.round(percentValue)

      const previousValue = percentageWatched
      percentageBucket(percentValue)

      if (previousValue !== percentageWatched) {
        const analyticsObject = { name: 'video tracking', details: videoTitle }
        analyticsObject.action = percentageWatched
        analyticsTracking(analyticsObject)
      }
    }

    const setPlaying = async (isPlaying) => {
      const videoPlayer = refVideoPlayer.current

      if (isPlaying) {
        await videoPlayer.play()

        if (analyticsTracking !== undefined && videoTitle) {
          const intervalId = setInterval(calculatePercentageWatched, 300)
          setVideoPlayerState((prevState) => ({ ...prevState, intervalId }))
        }
      } else {
        const { intervalId } = videoPlayerState

        videoPlayer.pause()
        clearInterval(intervalId)
      }
    }

    const updateAnalyticsData = () => {
      const { videoIsPlaying, percentageWatched } = videoPlayerState

      const analyticsObject = { name: 'video tracking', details: videoTitle }
      analyticsObject.action = videoIsPlaying ? 'play' : 'pause'
      if (percentageWatched !== 'watched 100%') {
        analyticsTracking(analyticsObject)
      }
    }

    const setSeek = (seconds) => {
      refVideoPlayer.current.currentTime = seconds
    }

    const qualitySwitchSeek = () => {
      const { videoCurrentTime } = videoPlayerState

      // The following setState gets the video length on the splash screen in iOS
      setVideoPlayerState((prevState) => ({
        ...prevState,
        videoLength: refVideoPlayer.current.duration
      }))

      if (videoCurrentTime > -1) {
        setSeek(videoCurrentTime)
      }
    }

    const updateVideoTimestamp = () => {
      const { buffered, currentTime } = refVideoPlayer.current
      setVideoPlayerState((prevState) => ({
        ...prevState,
        videoCurrentTime: currentTime,
        videoBufferEnd:
          buffered.length === 0 ? prevState.videoBufferEnd : buffered.end(buffered.length - 1)
      }))
    }

    const initializeVideoDuration = () => {
      // This will run on every load() call, meaning it will also run when video quality is changed.
      setVideoPlayerState((prevState) => {
        return {
          ...prevState,
          videoLength: refVideoPlayer.current.duration,
          videoBufferEnd:
            refVideoPlayer.current.buffered.length === 0
              ? 0
              : refVideoPlayer.current.buffered.end(refVideoPlayer.current.buffered.length - 1),
          videoUnplayed:
            refVideoPlayer.current.played.length === 0 && !prevState.videoQualityChanged
        }
      })

      const { videoQualityChanged, videoIsPlaying } = videoPlayerState

      // Prevents an IE 11 bug where the video will not continue playing after a quality switch
      if (videoQualityChanged && videoIsPlaying) {
        setPlaying(true)
      }
    }

    const clearInactivityTimer = () => {
      setVideoPlayerState((prevState) => ({ ...prevState, mouseInactive: false }))
      clearTimeout(inactivityTimer)
    }

    const setAsBuffering = () => {
      // Prevent IE infinite buffer bug with readyState
      if (refVideoPlayer.current.readyState < 4) {
        clearInactivityTimer()
        setVideoPlayerState((prevState) => ({
          ...prevState,
          videoIsPlaying: false,
          videoIsBuffering: true,
          mouseInactive: true
        }))
        setPlaying(false)
      }
    }

    const playAfterBuffer = () => {
      const { videoIsBuffering, videoCurrentTime, videoQualityChanged } =
        videoPlayerStateRef.current

      if (videoIsBuffering && videoCurrentTime !== -1 && !videoQualityChanged) {
        setPlaying(true)
        setVideoPlayerState((prevState) => ({ ...prevState, videoIsBuffering: false }))
      }
    }

    const resetInactivityTimer = () => {
      clearInactivityTimer()

      const { videoQualityChanged, videoIsPlaying, mouseOnControlBar } = videoPlayerState

      if (!videoQualityChanged && videoIsPlaying) {
        inactivityTimer = setTimeout(() => {
          if (!mouseOnControlBar) {
            setVideoPlayerState((prevState) => ({ ...prevState, mouseInactive: true }))
          }
        }, playerOptions.mouseTimeout)
      }
    }

    const setAsPlaying = () => {
      setVideoPlayerState((prevState) => ({
        ...prevState,
        videoIsPlaying: true,
        videoIsBuffering: false,
        videoEnded: false,
        videoUnplayed: false,
        videoQualityChanged: false
      }))

      if (analyticsTracking) {
        updateAnalyticsData()
      }

      resetInactivityTimer()
    }

    const setAsPaused = () => {
      clearInactivityTimer()
      setVideoPlayerState((prevState) => ({ ...prevState, videoIsPlaying: false }))

      if (analyticsTracking) {
        updateAnalyticsData()
      }
    }

    const returnFromEndState = () => {
      resetInactivityTimer()
      setVideoPlayerState((prevState) => ({ ...prevState, videoEnded: false }))
    }

    const setAsEnded = () => {
      setVideoPlayerState((prevState) => ({
        ...prevState,
        videoIsPlaying: false,
        videoEnded: true
      }))
      clearInactivityTimer()
    }

    const updateBufferProgress = () => {
      const { videoCurrentTime, videoQualityChanged } = videoPlayerState
      const videoPlayer = refVideoPlayer.current
      if (videoPlayer && videoPlayer.readyState >= 2) {
        const { buffered } = videoPlayer
        setVideoPlayerState((prevState) => ({
          ...prevState,
          videoBufferEnd: buffered.length === 0 ? 0 : buffered.end(buffered.length - 1)
        }))
      } else if (videoCurrentTime !== -1 && !videoQualityChanged && !videoPlayer) {
        setVideoPlayerState((prevState) => ({
          ...prevState,
          videoIsPlaying: false,
          videoIsBuffering: true
        }))
      }
    }

    const updateVolumeState = () => {
      resetInactivityTimer()

      const videoPlayer = refVideoPlayer.current

      setVideoPlayerState((prevState) => ({
        ...prevState,
        videoCurrentVolume: videoPlayer.volume,
        videoIsMuted: videoPlayer.muted
      }))
    }

    const getPlayerWidth = () => {
      setVideoPlayerState((prevState) => ({
        ...prevState,
        videoPlayerWidth: refVideoPlayerContainer.current.offsetWidth
      }))
    }

    const setMouseOnControlBar = (isOver) => {
      setVideoPlayerState((prevState) => ({ ...prevState, mouseOnControlBar: isOver }))
    }

    const togglePlayPause = () => {
      const { videoIsPlaying } = videoPlayerState

      setPlaying(!videoIsPlaying)
    }

    const setTextTracks = (trackNumber) => {
      const { textTracks } = refVideoPlayer.current

      for (let i = 0; i < textTracks.length; i += 1) {
        textTracks[i].mode = i === trackNumber ? 'showing' : 'hidden'
      }
      setVideoPlayerState((prevState) => ({ ...prevState, selectedTextTrack: trackNumber }))
    }

    const beginVideo = () => {
      setTextTracks(-1)
      setPlaying(true)
      refVideoPlayerContainer.current.focus()
    }

    const closeSubMenus = () => {
      setVideoPlayerState((prevState) => ({
        ...prevState,
        qualityMenuOpen: false,
        captionsMenuOpen: false
      }))
    }

    const incrementSeek = (seconds) => {
      refVideoPlayer.current.currentTime += seconds
    }

    const replayVideo = async () => {
      setSeek(0)
      togglePlayPause()
    }

    const incrementVolume = (amount) => {
      refVideoPlayer.current.volume += amount
    }

    const setVolume = (amount) => {
      refVideoPlayer.current.volume = amount
    }

    const toggleMute = () => {
      refVideoPlayer.current.muted = !refVideoPlayer.current.muted
    }

    const setCaptionsMenuOpen = (isOpen) => {
      setVideoPlayerState((prevState) => ({ ...prevState, captionsMenuOpen: isOpen }))
    }

    const toggleFullscreen = () => {
      if (fscreen.fullscreenEnabled) {
        if (fscreen.fullscreenElement === null) {
          fscreen.requestFullscreen(refVideoPlayerContainer.current)
        } else {
          fscreen.exitFullscreen()
        }
        setVideoPlayerState((prevState) => {
          return { ...prevState, videoIsFullscreen: !prevState.videoIsFullscreen }
        })
      }
    }

    const setVideoQuality = async (newVideoQuality) => {
      await setPlaying(false)
      setVideoPlayerState((prevState) => ({
        ...prevState,
        videoLength: 0,
        videoBufferEnd: 0,
        videoQuality: newVideoQuality,
        loadedSources: generateSources(newVideoQuality),
        loadedTracks: tracks && generateTracks(),
        videoQualityChanged: true
      }))
      await refVideoPlayer.current.load()
      resetInactivityTimer()
      setSeek(videoPlayerState.videoCurrentTime)
    }

    const setQualityMenuOpen = (isOpen) => {
      setVideoPlayerState((prevState) => ({ ...prevState, qualityMenuOpen: isOpen }))
    }

    const handleClick = (event) => {
      resetInactivityTimer()

      // This allows playing videos within components that act like
      // links, e.g. `PreviewCard`, `StoryCard`, etc.
      event?.preventDefault()
      event?.stopPropagation()
    }

    const handleKeyboard = (event) => {
      const { videoLength, videoCurrentVolume } = videoPlayerState

      const key = event.key || event.keyCode

      // *** Begin Seek & Play Control ****
      if (key === ' ' || key === 32 || key === 'k' || key === 75) {
        event.preventDefault()
        resetInactivityTimer()
        togglePlayPause()
      }

      // Disables all keys except for play/pause when in simpleMode
      if (!simpleMode) {
        if (key === 'ArrowRight' || key === 39 || key === '.' || key === 190) {
          resetInactivityTimer()

          incrementSeek(playerOptions.keyboardSeekIncrement)
        }

        if (key === 'ArrowLeft' || key === 37 || key === ',' || key === 188) {
          resetInactivityTimer()

          incrementSeek(-playerOptions.keyboardSeekIncrement)
        }

        if (key === '0' || key === 48 || key === 'numpad 0' || key === 96) {
          setSeek(0)
        }

        if (key === '1' || key === 49 || key === 'numpad 1' || key === 97) {
          setSeek(videoLength * 0.1)
        }

        if (key === '2' || key === 50 || key === 'numpad 2' || key === 98) {
          setSeek(videoLength * 0.2)
        }

        if (key === '3' || key === 51 || key === 'numpad 3' || key === 99) {
          setSeek(videoLength * 0.3)
        }

        if (key === '4' || key === 52 || key === 'numpad 4' || key === 100) {
          setSeek(videoLength * 0.4)
        }

        if (key === '5' || key === 53 || key === 'numpad 5' || key === 101) {
          setSeek(videoLength * 0.5)
        }

        if (key === '6' || key === 54 || key === 'numpad 6' || key === 102) {
          setSeek(videoLength * 0.6)
        }

        if (key === '7' || key === 55 || key === 'numpad 7' || key === 103) {
          setSeek(videoLength * 0.7)
        }

        if (key === '8' || key === 56 || key === 'numpad 8' || key === 104) {
          setSeek(videoLength * 0.8)
        }

        if (key === '9' || key === 57 || key === 'numpad 9' || key === 105) {
          setSeek(videoLength * 0.9)
        }

        // **** End Seek & Play Control ****

        // **** Begin Volume Control ****

        const { keyboardVolumeIncrement } = playerOptions

        if (
          key === 'ArrowUp' ||
          key === 38 ||
          key === '=' ||
          key === 187 ||
          key === 'add' ||
          key === 107
        ) {
          resetInactivityTimer()

          if (videoCurrentVolume + keyboardVolumeIncrement < 1) {
            incrementVolume(keyboardVolumeIncrement)
          } else {
            setVolume(1)
          }
        }

        if (
          key === 'ArrowDown' ||
          key === 40 ||
          key === '-' ||
          key === 189 ||
          key === 'subtract' ||
          key === 109
        ) {
          resetInactivityTimer()

          if (videoCurrentVolume - keyboardVolumeIncrement > 0) {
            incrementVolume(-keyboardVolumeIncrement)
          } else {
            setVolume(0)
          }
        }

        if (key === 'm' || key === 77) {
          resetInactivityTimer()

          toggleMute()
        }

        if (key === 'f' || key === 70) {
          resetInactivityTimer()

          toggleFullscreen()
        }

        if (key === 'Escape' || key === 27) {
          // Resets focus back to container if user is focused on ControlBar button
          refVideoPlayerContainer.current.focus()
          closeSubMenus()
        }

        if (key === 'q' || key === 81) {
          refKeyboardInstructions.current.focus()
        }
      }
    }

    // Prepares video and caption files
    React.useEffect(() => {
      refreshMedia()
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [])

    // Initial Setup after loading sources
    React.useEffect(() => {
      const { loadedSources } = videoPlayerState

      if (loadedSources) {
        const videoPlayer = refVideoPlayer.current

        // Initializes Settings
        videoPlayer.volume = defaultVolume / 100
        videoPlayer.muted = beginMuted || simpleMode

        getPlayerWidth()

        // Reports when the video has completed loading metadata (used for seeking after quality switch)
        videoPlayer.onloadedmetadata = qualitySwitchSeek

        videoPlayer.ondurationchange = () => {
          setVideoPlayerState((prevState) => ({
            ...prevState,
            videoLength: refVideoPlayer.current.duration
          }))
        }

        videoPlayer.oncanplay = () => {
          initializeVideoDuration()
          playAfterBuffer()
        }

        // Reports the video's width on resize
        window.addEventListener('resize', getPlayerWidth)

        // Reports the current video timestamp
        videoPlayer.ontimeupdate = updateVideoTimestamp

        // Reports when the video has paused due to buffering
        videoPlayer.onwaiting = setAsBuffering

        // Reports the video's latest buffering progress if video player is properly initialized
        videoPlayer.onprogress = updateBufferProgress

        // Reports when the video has recovered from buffering
        videoPlayer.oncanplaythrough = playAfterBuffer

        // Reports when the video has ended
        videoPlayer.onended = setAsEnded

        // Reports when the video is playing and disables the buffer spinner (even if buffering is still needed)
        videoPlayer.onplay = setAsPlaying

        // Reports when the video has been paused
        videoPlayer.onpause = setAsPaused

        // Reports when the video has been seeked
        videoPlayer.onseeked = returnFromEndState

        // Reports when the video's volume has been changed, or if the video has been muted
        videoPlayer.onvolumechange = updateVolumeState

        // Video Analytics
        if (
          (analyticsTracking !== undefined && videoTitle === undefined) ||
          (analyticsTracking === undefined && videoTitle !== undefined)
        ) {
          warn(
            'Video',
            'Both the `analyticsTracking` and `videoTitle` props must be defined in order to return a proper analytics object.'
          )
        }

        return () => {
          clearInactivityTimer()
          window.removeEventListener('resize', getPlayerWidth)
          clearInterval(videoPlayerState.intervalId)
        }
      }

      return () => {} // returning a noop for sake of consistency
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [videoPlayerState.loadedSources, videoPlayerState.loadedTracks])

    const {
      loadedSources,
      loadedTracks,
      isMobile,
      mouseInactive,
      selectedTextTrack,
      qualityMenuOpen,
      captionsMenuOpen,
      videoUnplayed,
      videoEnded,
      videoIsBuffering,
      videoIsPlaying,
      videoIsFullscreen,
      videoIsMuted,
      videoQualityChanged,
      videoLength,
      videoBufferEnd,
      videoCurrentTime,
      videoCurrentVolume,
      videoQuality,
      videoPlayerWidth
    } = videoPlayerState
    const { style, className, as, ...safeRest } = rest

    return (
      <VideoPlayerContainer
        {...safeRest}
        ref={refVideoPlayerContainer}
        $videoBorder={videoBorder}
        $borderColor={borderColor}
        onMouseMove={resetInactivityTimer}
        onClick={handleClick}
        onKeyDown={handleKeyboard}
        tabIndex="0"
        aria-label={
          simpleMode ? videoText[copy].videoSelectedSimple : videoText[copy].videoSelected
        }
        data-testid="videoPlayer"
        {...selectProps(rest)}
      >
        <VideoOverlayContainer
          $mouseInactive={mouseInactive}
          onClick={() => {
            closeSubMenus()
            togglePlayPause()
          }}
        >
          {/* ======== Video Splash Screen ======== */}
          {videoUnplayed && !videoQualityChanged && (
            <VideoSplashContainer>
              <VideoSplash
                poster={posterSrc}
                copy={copy}
                videoLength={videoLength}
                simpleMode={simpleMode}
                iconLeftOffsetPx={2}
                onClick={beginVideo}
              />
            </VideoSplashContainer>
          )}
          {/* =================================== */}
          <VideoOverlayElementContainer>
            {/* ======== Replay Button ======== */}
            {videoEnded && <MiddleControlButton icon={replayIcon} onClick={replayVideo} />}
            {/* ================================ */}
            {/* ======== Middle Play/Pause Button ======= */}
            {!videoUnplayed && !videoIsBuffering && !videoEnded && !isMobile && (
              <MiddleControlButton
                icon={videoIsPlaying ? pauseIcon : playIcon}
                isHidden={mouseInactive}
                onClick={togglePlayPause}
                onFocus={resetInactivityTimer}
              />
            )}
            {/* ========================================== */}

            {/* ======== Spinner Display ======= */}
            {videoIsBuffering && !isMobile && <Spinner show />}
            {/* ================================ */}
          </VideoOverlayElementContainer>
        </VideoOverlayContainer>

        {/* ======== Video Element ======= */}
        <VideoElementContainer ref={ref}>
          <VideoPlayer
            ref={refVideoPlayer}
            controls={isMobile}
            crossOrigin={crossOrigin}
            playsInline
          >
            {loadedSources}
            {loadedTracks}
            Your browser does not support the video tag.
          </VideoPlayer>
        </VideoElementContainer>
        {/* ============================== */}

        {!simpleMode && (
          <ControlBar
            videoPlayer={refVideoPlayer}
            videoPlayerContainer={refVideoPlayerContainer}
            sources={sources}
            tracks={tracks}
            videoLength={videoLength}
            videoBufferEnd={videoBufferEnd}
            videoCurrentTime={videoCurrentTime}
            videoPlaying={refVideoPlayer.current !== null ? !refVideoPlayer.current.paused : false}
            videoUnplayed={videoUnplayed}
            videoCurrentVolume={videoCurrentVolume}
            videoIsMuted={videoIsMuted}
            setVolume={setVolume}
            isHidden={(mouseInactive || videoUnplayed) && !videoEnded}
            isMobile={isMobile}
            tracksAvailable={Array.isArray(tracks) && tracks.length > 0}
            togglePlayPause={togglePlayPause}
            setSeek={setSeek}
            toggleMute={toggleMute}
            toggleFullscreen={toggleFullscreen}
            videoIsFullscreen={videoIsFullscreen}
            setTextTracks={setTextTracks}
            selectedTextTrack={selectedTextTrack}
            resetInactivityTimer={resetInactivityTimer}
            videoQuality={videoQuality}
            setVideoQuality={setVideoQuality}
            qualityMenuOpen={qualityMenuOpen}
            setQualityMenuOpen={setQualityMenuOpen}
            captionsMenuOpen={captionsMenuOpen}
            setCaptionsMenuOpen={setCaptionsMenuOpen}
            clearInactivityTimer={clearInactivityTimer}
            copy={copy}
            compactModeThreshold={playerOptions.compactModeThreshold}
            videoPlayerWidth={videoPlayerWidth}
            onMouseOver={() => setMouseOnControlBar(true)}
            onMouseOut={() => setMouseOnControlBar(false)}
            onFocus={() => {}}
            onBlur={() => {}}
          />
        )}

        {/* ======== Screen Reader Keyboard Instructions ======= */}
        <span
          tabIndex="-1"
          ref={refKeyboardInstructions}
          aria-label={videoText[copy].videoPlayer}
          role="note"
        />
        {/* ==================================================== */}
      </VideoPlayerContainer>
    )
  }
)
Video.displayName = 'Video'

Video.propTypes = {
  ...selectedSystemPropTypes,
  /**
   * An array of objects that defines each video file. See the "Basic Usage" section for more information.
   */
  sources: PropTypes.arrayOf(
    PropTypes.shape({
      /** A path to a video file */
      source: PropTypes.string.isRequired,
      /** The video's MIME type (example: `video/mp4`) */
      mediaType: PropTypes.string.isRequired,
      /** The label to be given to this file in the quality selector (example: `1080p`) */
      qualityName: PropTypes.string.isRequired,
      /** A number indicating the video's quality, with 1 being the best, and the number increasing from there as quality degrades */
      qualityRank: PropTypes.number.isRequired,
      /** A boolean that defines its source as a fallback for another source with the same `qualityRank` */
      isFallback: PropTypes.bool
    })
  ).isRequired,
  /**
   * A path of the image that will be displayed on the video's splash screen.
   */
  posterSrc: PropTypes.string.isRequired,
  /**
   * An array of objects that defines each caption file. See the "Basic Usage" section for more information.
   */
  tracks: PropTypes.arrayOf(
    PropTypes.shape({
      /** A path to the track file */
      source: PropTypes.string.isRequired,
      /** The name of the track file as it will appear in the "Captions" dialogue */
      label: PropTypes.string.isRequired,
      /** The track's file type, typically one of "subtitles", "captions", or "descriptions". See [MDN's documentation](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track#Attributes) for more information on these types */
      kind: PropTypes.string.isRequired,
      /** The language of the track file represented as a ISO 639-1 language code */
      language: PropTypes.string.isRequired
    })
  ),
  /**
   * The video's default volume, defined from 1 to 100. Please use the `beginMuted` prop to have the video start silenced.
   */
  defaultVolume: PropTypes.number,
  /**
   * Defines if the video should start muted.
   */
  beginMuted: PropTypes.bool,
  /**
   * The default quality level if the user is on mobile. See the `sources` prop for available quality levels.
   */
  defaultMobileQuality: PropTypes.number,
  /**
   * The default quality level if the user is on desktop. See the `sources` prop for available quality levels.
   */
  defaultDesktopQuality: PropTypes.number,
  /**
   * The video player UI's language as an ISO language code. It currently supports English and French.
   */
  copy: PropTypes.oneOf(['en', 'fr']).isRequired,
  /**
   * Sets the `video` tag's `crossorigin` mode. Please note that content loaded without CORS approval may be insecure.
   * @since 1.1.0
   */
  crossOrigin: PropTypes.oneOf(['anonymous', 'use-credentials']),
  /**
   * Disables the control bar during playback and reduces the amount of time until the UI hides itself.
   * For special approved internal uses only.
   * @since 1.2.0
   */
  simpleMode: PropTypes.bool,
  /**
   * Renders a gainsboro colored border around the video. Used to seperate the video from the rest of the page if the video's background is the same color as the container's.
   * @since 1.2.0
   */
  videoBorder: PropTypes.bool,
  /**
   * A callback function that fires when a customer interacts with Video.
   * When using `analyticsTracking`, `videoTitle` must also be defined.
   *
   * When invoked, your callback function returns an object containing three keys (see below).
   * @since 1.3.0
   *
   * @param {Object} analyticsObject Contains video data based on customer interactions
   * @param {string} analyticsObject.name Always 'video tracking'
   * @param {string} analyticsObject.action Contains the latest customer action
   * @param {string} analyticsObject.details The name of the video based on the `videoTitle` prop
   */
  analyticsTracking: PropTypes.func,
  /**
   * The title of the video being rendered. This is used for analytics purposes in conjunction with `analyticsTracking`.
   * @since 1.3.0
   */
  videoTitle: PropTypes.string,
  tokens: getTokensPropType('Video'),
  variant: variantProp.propType
}
