import React, {
  forwardRef,
  useCallback,
  useEffect,
  useImperativeHandle,
  useRef,
  useState,
} from 'react';
import type { ForwardedRef } from 'react';
import { Polyline as BarePolyline } from '../../bare';
import type { PolylineProps } from '../../bare';
import Reanimated, {
  interpolateColor,
  useAnimatedProps,
  useSharedValue,
  withTiming,
  Easing,
  interpolate,
  // @ts-ignore
} from 'react-native-reanimated';
import { delay, withFastCompare } from '../../utils';

export const Polyline = withFastCompare(BarePolyline);

const AnimatedBarePolyline = withFastCompare(
  Reanimated.createAnimatedComponent(BarePolyline)
);

export type AnimatedPolylineRef = {
  animate: (
    point: number,
    duration?: number,
    ignoreMiddlePoints?: boolean
  ) => Promise<void>;
  reset: () => void;
};

export type AnimationPrimitive = {
  // From 0 to 1
  animationValue: number;
  inputRange: Array<any>;
  outputRange: Array<any>;
};

export type AnimatedPolylineAnimationProp = {
  color?: AnimationPrimitive;
  width?: AnimationPrimitive;
};

export const AnimatedPolyline = withFastCompare(
  forwardRef(
    (
      props: PolylineProps & {
        sectionId: number;
        animation?: AnimatedPolylineAnimationProp;
      },
      ref: ForwardedRef<AnimatedPolylineRef>
    ) => {
      const {
        points: _points,
        strokeColor: _strokeColor,
        strokeWidth: _strokeWidth,
        ...p
      } = props;
      const [internalPoints, setInternalPoints] = useState(_points);

      const pointsRef = useRef(_points);
      const currentPoint = useRef(0);

      const lat = useSharedValue(_points[0]?.lat ?? 0);
      const lon = useSharedValue(_points[0]?.lon ?? 0);

      const colorAnimation = useSharedValue(0);
      const widthAnimation = useSharedValue(0);

      useEffect(() => {
        if (props.animation?.color) {
          colorAnimation.value = props.animation.color.animationValue;
        } else {
          colorAnimation.value = withTiming(0);
        }
        // eslint-disable-next-line
      }, [props.animation?.color]);

      useEffect(() => {
        if (props.animation?.width) {
          widthAnimation.value = props.animation.width.animationValue;
        } else {
          colorAnimation.value = withTiming(0);
        }
      }, [props.animation?.width]);

      const animatedProps = useAnimatedProps(() => {
        return {
          points: [
            {
              lat: lat.value,
              lon: lon.value,
            },
            ...internalPoints,
          ],
          strokeColor: interpolateColor(
            colorAnimation.value,
            props.animation?.color ? props.animation.color.inputRange : [0, 0],
            props.animation?.color
              ? props.animation.color.outputRange
              : [_strokeColor, _strokeColor]
          ),
          strokeWidth: interpolate(
            widthAnimation.value,
            props.animation?.width ? props.animation.width.inputRange : [0, 0],
            props.animation?.width
              ? props.animation.width.outputRange
              : [_strokeWidth, _strokeWidth]
          ),
        };
      }, [internalPoints, colorAnimation, props, _strokeColor, _strokeWidth]);

      const reset = useCallback(() => {
        setInternalPoints(_points);
        lat.value = _points[0]?.lat ?? 0;
        lon.value = _points[0]?.lon ?? 0;
      }, [_points, lat, lon]);

      const animate = useCallback(
        async (
          point: number,
          duration?: number,
          ignoreMiddlePoints?: boolean
        ) => {
          if (point === currentPoint.current) {
            return;
          }

          setInternalPoints(
            pointsRef.current.slice(
              ignoreMiddlePoints ? pointsRef.current.length - 1 : point,
              pointsRef.current.length
            )
          );

          await delay(16); // FRAME_TIME

          lat.value = withTiming(pointsRef.current[point]!.lat, {
            duration: duration ?? 0,
            easing: Easing.linear,
          });
          lon.value = withTiming(pointsRef.current[point]!.lon, {
            duration: duration ?? 0,
            easing: Easing.linear,
          });

          if (duration) {
            await delay(duration);
          }
          // eslint-disable-next-line
      }, []);

      useImperativeHandle(ref, () => ({
        reset,
        animate,
      }));

      // @ts-ignore
      return <AnimatedBarePolyline {...p} animatedProps={animatedProps} />;
    }
  )
);
