import React from 'react'
import { View, StyleSheet, Animated, Easing } from 'react-native'
import Svg, { Circle } from 'react-native-svg'
import {
  DOTS_TOTAL_ANIMATION_DURATION,
  OVERSHOOT_FACTOR,
  UNDERSHOOT_FACTOR,
  DOTS_SPACING_X,
  DOTS_BASE_Y_FACTOR,
  DOTS_JUMP_HEIGHT_FACTOR,
  DOTS_PADDING_FACTOR,
  DOT_FADEOUT_INPUT_RANGE,
  DOT_FADEOUT_OUTPUT_RANGE,
  DOT1_ANIMATION_INPUT_RANGE,
  DOT2_ANIMATION_INPUT_RANGE,
  DOT3_ANIMATION_INPUT_RANGE,
  propTypes
} from './shared'
import { useA11yInfo } from '../A11yInfoProvider/A11yInfoProvider'

const AnimatedCircle = Animated.createAnimatedComponent(Circle)

export const Dots = React.forwardRef(
  ({ size, color, indicatorBackgroundColor, label, isStatic = false }, ref) => {
    const { reduceMotionEnabled } = useA11yInfo()
    const reduceMotion = reduceMotionEnabled || isStatic

    const spacingX = size + DOTS_SPACING_X
    const baseY = size * DOTS_BASE_Y_FACTOR
    const jumpHeight = size * DOTS_JUMP_HEIGHT_FACTOR
    const padding = size * DOTS_PADDING_FACTOR
    const width = (size + 1) * 6
    const height = size * 6

    const animationValue = React.useRef(new Animated.Value(0)).current

    React.useEffect(() => {
      if (reduceMotion) {
        animationValue.stopAnimation()
        animationValue.setValue(0)
        return () => {}
      }
      const loopAnimation = Animated.loop(
        Animated.timing(animationValue, {
          toValue: 1,
          duration: DOTS_TOTAL_ANIMATION_DURATION,
          easing: Easing.linear,
          useNativeDriver: false
        })
      )
      loopAnimation.start()
      return () => loopAnimation.stop()
    }, [reduceMotion, animationValue])

    const makeStepInterpolation = (steps) => {
      const inputRange = []
      const outputRange = []
      steps.forEach(({ time, value }) => {
        inputRange.push(time)
        outputRange.push(value)
      })
      return { inputRange, outputRange }
    }

    // ---- DOT 1 ----
    const dot1Cy = animationValue.interpolate({
      inputRange: DOT1_ANIMATION_INPUT_RANGE,
      outputRange: [
        baseY,
        baseY - jumpHeight * OVERSHOOT_FACTOR,
        baseY + jumpHeight * UNDERSHOOT_FACTOR,
        baseY
      ],
      extrapolate: 'clamp'
    })

    const { inputRange: dot1FillInputRange, outputRange: dot1FillOutputRange } =
      makeStepInterpolation([
        { time: 0, value: color },
        { time: 0.3, value: color },
        { time: 0.4, value: indicatorBackgroundColor },
        { time: 1, value: indicatorBackgroundColor }
      ])
    const dot1Fill = animationValue.interpolate({
      inputRange: dot1FillInputRange,
      outputRange: dot1FillOutputRange,
      extrapolate: 'clamp'
    })

    // Fade-out Dot 1
    const dot1Opacity = animationValue.interpolate({
      inputRange: DOT_FADEOUT_INPUT_RANGE,
      outputRange: DOT_FADEOUT_OUTPUT_RANGE,
      extrapolate: 'clamp'
    })

    // ---- DOT 2 ----
    const dot2Cy = animationValue.interpolate({
      inputRange: DOT2_ANIMATION_INPUT_RANGE,
      outputRange: [
        baseY,
        baseY,
        baseY - jumpHeight * OVERSHOOT_FACTOR,
        baseY + jumpHeight * UNDERSHOOT_FACTOR,
        baseY
      ],
      extrapolate: 'clamp'
    })

    const { inputRange: dot2FillInputRange, outputRange: dot2FillOutputRange } =
      makeStepInterpolation([
        { time: 0, value: indicatorBackgroundColor },
        { time: 0.3, value: indicatorBackgroundColor },
        { time: 0.4, value: color },
        { time: 0.6, value: color },
        { time: 0.7, value: indicatorBackgroundColor },
        { time: 1, value: indicatorBackgroundColor }
      ])
    const dot2Fill = animationValue.interpolate({
      inputRange: dot2FillInputRange,
      outputRange: dot2FillOutputRange,
      extrapolate: 'clamp'
    })

    // Fade-out Dot 2
    const dot2Opacity = animationValue.interpolate({
      inputRange: DOT_FADEOUT_INPUT_RANGE,
      outputRange: DOT_FADEOUT_OUTPUT_RANGE,
      extrapolate: 'clamp'
    })

    // ---- DOT 3 ----
    const dot3Cy = animationValue.interpolate({
      inputRange: DOT3_ANIMATION_INPUT_RANGE,
      outputRange: [
        baseY,
        baseY,
        baseY - jumpHeight * OVERSHOOT_FACTOR,
        baseY + jumpHeight * UNDERSHOOT_FACTOR,
        baseY
      ],
      extrapolate: 'clamp'
    })

    const { inputRange: dot3FillInputRange, outputRange: dot3FillOutputRange } =
      makeStepInterpolation([
        { time: 0, value: indicatorBackgroundColor },
        { time: 0.5, value: indicatorBackgroundColor },
        { time: 0.6, value: color },
        { time: 0.8, value: color },
        { time: 1, value: color }
      ])
    const dot3Fill = animationValue.interpolate({
      inputRange: dot3FillInputRange,
      outputRange: dot3FillOutputRange,
      extrapolate: 'clamp'
    })

    const dot3Opacity = new Animated.Value(1)

    return (
      <View
        ref={ref}
        style={styles.container}
        accessible
        accessibilityLabel={label}
        accessibilityRole="progressbar"
        accessibilityState={{ busy: true }}
      >
        <Svg width={width} height={height} viewBox={`0 0 ${width} ${height}`}>
          {/* DOT 1 */}
          <AnimatedCircle
            cx={size}
            cy={reduceMotion ? baseY : dot1Cy}
            r={padding}
            fill={reduceMotion ? color : dot1Fill}
            opacity={reduceMotion ? 1 : dot1Opacity}
          />

          {/* DOT 2 */}
          <AnimatedCircle
            cx={2 * size + spacingX}
            cy={reduceMotion ? baseY : dot2Cy}
            r={padding}
            fill={reduceMotion ? indicatorBackgroundColor : dot2Fill}
            opacity={reduceMotion ? 1 : dot2Opacity}
          />

          {/* DOT 3 */}
          <AnimatedCircle
            cx={3 * size + 2 * spacingX}
            cy={reduceMotion ? baseY : dot3Cy}
            r={padding}
            fill={reduceMotion ? indicatorBackgroundColor : dot3Fill}
            opacity={reduceMotion ? 1 : dot3Opacity}
          />
        </Svg>
      </View>
    )
  }
)

Dots.displayName = 'Dots'
Dots.propTypes = propTypes

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    justifyContent: 'center'
  }
})
