import React, { useCallback, useMemo, useRef } from 'react';
import type {
  LayoutChangeEvent,
  StyleProp,
  TextStyle,
  ViewStyle,
} from 'react-native';
import { Text, View, Animated, Easing, StyleSheet } from 'react-native';

function usePrevious(value: number): number {
  const ref = React.useRef<number>(value);

  React.useEffect(() => {
    ref.current = value;
  });

  return ref.current;
}

export function createNumberArrayWithCommaAndDecimal(numberString: string): (number | string)[] {
  const [integerPart, decimalPart] = numberString.split('.');
  const integerArr = Array.from(integerPart, Number);

  const reducedArray = new Array(Math.ceil(integerPart.length / 3)).fill(0);

  reducedArray.forEach((_, index) => {
    if (index !== 0) {
      integerArr.splice(integerPart.length - index * 3, 0, ',');
    }
  });

  if (decimalPart) {
    return [...integerArr, '.', ...Array.from(decimalPart, Number)];
  }

  return integerArr;
}

export interface AnimatedNumberProps {
  animateToNumber: number;
  fontStyle?: StyleProp<TextStyle>;
  animationDuration?: number;
  includeComma?: boolean;
  decimals?: number;
  easing?: Animated.TimingAnimationConfig['easing'];
  containerStyle?: StyleProp<ViewStyle>;
  fontVariant?: TextStyle['fontVariant'];
}

const AnimatedNumber = ({
  animateToNumber: givenAnimateToNumber,
  fontStyle,
  animationDuration,
  includeComma,
  decimals = 2,
  easing,
  containerStyle
}: AnimatedNumberProps) => {
  const animationRef = useRef<Animated.CompositeAnimation | null>(null);

  const animateToNumber = parseFloat(givenAnimateToNumber.toFixed(decimals));

  const prevNumber = usePrevious(animateToNumber);

  const animateToNumberString = animateToNumber.toFixed(decimals);

  const prevNumberString = prevNumber.toFixed(decimals);

  const NUMBERS = useMemo(
    () => ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', ','],
    []
  );

  const nextNumbersArr = useMemo(() => {
 
    return includeComma
      ? createNumberArrayWithCommaAndDecimal(animateToNumberString)
      : animateToNumberString.split('').map(char => isNaN(Number(char)) ? char : Number(char));
  }, [animateToNumberString, includeComma]);

  const prevNumbersArr = useMemo(() => {
    return includeComma
      ? createNumberArrayWithCommaAndDecimal(prevNumberString)
      : prevNumberString.split('').map(char => isNaN(Number(char)) ? char : Number(char));
  }, [prevNumberString, includeComma]);

  const [height, setHeight] = React.useState(0);

  const animations = useMemo(
    () =>
      height === 0
        ? []
        : nextNumbersArr.map((__, index) => {
            const value = prevNumbersArr[index];
            if (typeof value !== 'number') {
              return new Animated.Value(0);
            }

            const animationHeight = -1 * (height * value);
            return new Animated.Value(animationHeight);
          }),
    [nextNumbersArr, height, prevNumbersArr]
  );

  const setButtonLayout = useCallback((e: LayoutChangeEvent) => {
    setHeight(e.nativeEvent.layout.height);
  }, []);

  React.useEffect(() => {
    
    if (height === 0) return;

    if (animationRef.current) {
      animationRef.current.stop();
    }

    const compositions = animations.reduce<Animated.CompositeAnimation[]>(
      (acc, animation, index) => {
        const value = nextNumbersArr[index];
        if (typeof value === 'number') {
          acc.push(
            Animated.timing(animation, {
              toValue: -1 * (height * value),
              duration: animationDuration || 1400,
              useNativeDriver: true,
              easing: easing || Easing.elastic(1.2),
            })
          );
        }

        return acc;
      },
      []
    );

    animationRef.current = Animated.parallel(compositions);
    animationRef.current.start();
  }, [animations, height, nextNumbersArr, animationDuration, easing]);
 
  return (
    <>
      {height !== 0 && (
        <View
          style={StyleSheet.flatten([
            containerStyle,
            { flexDirection: 'row', height },
          ])}
        >
          {animateToNumber < 0 && (
            <Text style={[fontStyle, { height }]}>-</Text>
          )}
          {nextNumbersArr.map((n, index) => {
            
            if (typeof n === 'string') {
              return (
                <Text key={index} style={[fontStyle, { height }]}>
                  {n}
                </Text>
              );
            }

            return (
              <View key={index} style={{ height, overflow: 'hidden' }}>
                <Animated.View
                  style={[
                    {
                      transform: [
                        {
                          translateY: animations[index]!,
                        },
                      ],
                    },
                  ]}
                >
                  {NUMBERS.map((number, i) => (
                    <Text
                      style={StyleSheet.flatten([
                        fontStyle,
                        { height },
                      ])}
                      key={i}
                    >
                      {number}
                    </Text>
                  ))}
                </Animated.View>
              </View>
            );
          })}
        </View>
      )}
      <View style={{ opacity: 0, position: 'absolute' }} pointerEvents="none">
        <Text style={fontStyle} onLayout={setButtonLayout} numberOfLines={1}>
          {animateToNumberString}
        </Text>
      </View>
    </>
  );
};

export default AnimatedNumber;