import {useCallback, useEffect, useRef, useState} from 'react'
import {View} from 'react-native'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'

import {clamp} from '#/lib/numbers'
import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons'
import {atoms as a, useTheme, web} from '#/alf'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {IS_WEB_FIREFOX, IS_WEB_TOUCH_DEVICE} from '#/env'
import {formatTime} from './utils'

export function Scrubber({
  duration,
  currentTime,
  onSeek,
  onSeekEnd,
  onSeekStart,
  seekLeft,
  seekRight,
  togglePlayPause,
  drawFocus,
}: {
  duration: number
  currentTime: number
  onSeek: (time: number) => void
  onSeekEnd: () => void
  onSeekStart: () => void
  seekLeft: () => void
  seekRight: () => void
  togglePlayPause: () => void
  drawFocus: () => void
}) {
  const {_} = useLingui()
  const t = useTheme()
  const [scrubberActive, setScrubberActive] = useState(false)
  const enableSquareButtons = useEnableSquareButtons()
  const {
    state: hovered,
    onIn: onStartHover,
    onOut: onEndHover,
  } = useInteractionState()
  const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
  const [seekPosition, setSeekPosition] = useState(0)
  const isSeekingRef = useRef(false)
  const barRef = useRef<HTMLDivElement>(null)
  const circleRef = useRef<HTMLDivElement>(null)

  const seek = useCallback(
    (evt: React.PointerEvent<HTMLDivElement>) => {
      if (!barRef.current) return
      const {left, width} = barRef.current.getBoundingClientRect()
      const x = evt.clientX
      const percent = clamp((x - left) / width, 0, 1) * duration
      onSeek(percent)
      setSeekPosition(percent)
    },
    [duration, onSeek],
  )

  const onPointerDown = useCallback(
    (evt: React.PointerEvent<HTMLDivElement>) => {
      const target = evt.target
      if (target instanceof Element) {
        evt.preventDefault()
        target.setPointerCapture(evt.pointerId)
        isSeekingRef.current = true
        seek(evt)
        setScrubberActive(true)
        onSeekStart()
      }
    },
    [seek, onSeekStart],
  )

  const onPointerMove = useCallback(
    (evt: React.PointerEvent<HTMLDivElement>) => {
      if (isSeekingRef.current) {
        evt.preventDefault()
        seek(evt)
      }
    },
    [seek],
  )

  const onPointerUp = useCallback(
    (evt: React.PointerEvent<HTMLDivElement>) => {
      const target = evt.target
      if (isSeekingRef.current && target instanceof Element) {
        evt.preventDefault()
        target.releasePointerCapture(evt.pointerId)
        isSeekingRef.current = false
        onSeekEnd()
        setScrubberActive(false)
      }
    },
    [onSeekEnd],
  )

  useEffect(() => {
    // HACK: there's divergent browser behaviour about what to do when
    // a pointerUp event is fired outside the element that captured the
    // pointer. Firefox clicks on the element the mouse is over, so we have
    // to make everything unclickable while seeking -sfn
    if (IS_WEB_FIREFOX && scrubberActive) {
      document.body.classList.add('force-no-clicks')

      return () => {
        document.body.classList.remove('force-no-clicks')
      }
    }
  }, [scrubberActive, onSeekEnd])

  useEffect(() => {
    if (!circleRef.current) return
    if (focused) {
      const abortController = new AbortController()
      const {signal} = abortController
      circleRef.current.addEventListener(
        'keydown',
        evt => {
          // space: play/pause
          // arrow left: seek backward
          // arrow right: seek forward

          if (evt.key === ' ') {
            evt.preventDefault()
            drawFocus()
            togglePlayPause()
          } else if (evt.key === 'ArrowLeft') {
            evt.preventDefault()
            drawFocus()
            seekLeft()
          } else if (evt.key === 'ArrowRight') {
            evt.preventDefault()
            drawFocus()
            seekRight()
          }
        },
        {signal},
      )

      return () => abortController.abort()
    }
  }, [focused, seekLeft, seekRight, togglePlayPause, drawFocus])

  const progress = scrubberActive ? seekPosition : currentTime
  const progressPercent = (progress / duration) * 100

  if (duration < 3) return null

  return (
    <View
      testID="scrubber"
      style={[
        {height: IS_WEB_TOUCH_DEVICE ? 32 : 18, width: '100%'},
        a.flex_shrink_0,
        a.px_xs,
      ]}
      onPointerEnter={onStartHover}
      onPointerLeave={onEndHover}>
      <div
        ref={barRef}
        style={{
          flex: 1,
          display: 'flex',
          alignItems: 'center',
          position: 'relative',
          cursor: scrubberActive ? 'grabbing' : 'grab',
          padding: '4px 0',
        }}
        onPointerDown={onPointerDown}
        onPointerMove={onPointerMove}
        onPointerUp={onPointerUp}
        onPointerCancel={onPointerUp}>
        <View
          style={[
            a.w_full,
            enableSquareButtons ? a.rounded_sm : a.rounded_full,
            a.overflow_hidden,
            {backgroundColor: 'rgba(255, 255, 255, 0.4)'},
            {height: hovered || scrubberActive ? 6 : 3},
            web({transition: 'height 0.1s ease'}),
          ]}>
          {duration > 0 && (
            <View
              style={[
                a.h_full,
                {backgroundColor: t.palette.white},
                {width: `${progressPercent}%`},
              ]}
            />
          )}
        </View>
        <div
          ref={circleRef}
          aria-label={_(
            msg`Seek slider. Use the arrow keys to seek forwards and backwards, and space to play/pause`,
          )}
          role="slider"
          aria-valuemax={duration}
          aria-valuemin={0}
          aria-valuenow={currentTime}
          aria-valuetext={_(
            msg`${formatTime(currentTime)} of ${formatTime(duration)}`,
          )}
          tabIndex={0}
          onFocus={onFocus}
          onBlur={onBlur}
          style={{
            position: 'absolute',
            height: 16,
            width: 16,
            left: `calc(${progressPercent}% - 8px)`,
            borderRadius: 8,
            pointerEvents: 'none',
          }}>
          <View
            style={[
              a.w_full,
              a.h_full,
              enableSquareButtons ? a.rounded_sm : a.rounded_full,
              {backgroundColor: t.palette.white},
              {
                transform: [
                  {
                    scale:
                      hovered || scrubberActive || focused
                        ? scrubberActive
                          ? 1
                          : 0.6
                        : 0,
                  },
                ],
              },
            ]}
          />
        </div>
      </div>
    </View>
  )
}
