import React, { useRef, useEffect, useState } from 'react';
import {
  PanResponder,
  StyleSheet,
  View,
  Animated,
  Easing,
  ViewProps,
} from 'react-native';
import Star from './Star';
import { getStars } from './utils';
import type { Theme } from './../../theme/theme';
import { createBox } from '@shopify/restyle';

type AnimationConfig = {
  easing?: (value: number) => number;
  duration?: number;
  delay?: number;
  scale?: number;
};

type StarRatingProps = {
  rating: number;
  onChange?: (rating: number) => void | undefined;
  minRating?: number;
  maxStars?: number;
  starColor?: string;
  starSize?: number;
  enableSwiping?: boolean;
  animationConfig?: AnimationConfig;
  testID?: string;
};

const defaultAnimationConfig: Required<AnimationConfig> = {
  easing: Easing.elastic(2),
  duration: 300,
  scale: 1.2,
  delay: 300,
};

const ViewComponent = createBox<
  Theme,
  ViewProps & { children?: React.ReactNode }
>(View);

export const StarRating: React.FC<StarRatingProps> = ({
  rating,
  maxStars = 5,
  starColor = '#FFB800',
  starSize = 24,
  onChange,
  enableSwiping = true,
  animationConfig = defaultAnimationConfig,
  testID,
}) => {
  const width = useRef<number>();
  const ref = useRef<View>(null);
  const [isInteracting, setInteracting] = useState(false);

  const handleInteraction = (x: number) => {
    if (width.current) {
      const newRating = Math.max(
        0,
        Math.min(
          Math.round((x / width.current) * maxStars * 2 + 0.2) / 2,
          maxStars
        )
      );
      if (onChange !== undefined) {
        onChange(Math.ceil(newRating));
      }
    }
  };

  const [panResponder] = useState(() =>
    PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onStartShouldSetPanResponderCapture: () => true,
      onMoveShouldSetPanResponder: () => true,
      onMoveShouldSetPanResponderCapture: () => true,
      onPanResponderMove: (e) => {
        if (enableSwiping) {
          handleInteraction(e.nativeEvent.locationX);
        }
      },
      onPanResponderStart: (e) => {
        handleInteraction(e.nativeEvent.locationX);
        if (onChange !== undefined) {
          setInteracting(true);
        }
      },
      onPanResponderEnd: () => {
        setTimeout(() => {
          setInteracting(false);
        }, animationConfig.delay || defaultAnimationConfig.delay);
      },
    })
  );

  return (
    <ViewComponent
      ref={ref}
      flexDirection={'row'}
      alignSelf={'flex-start'}
      {...panResponder.panHandlers}
      onLayout={() => {
        if (ref.current) {
          ref.current.measure((_x, _y, w, _h) => (width.current = w));
        }
      }}
      testID={testID}
    >
      {getStars(rating, maxStars).map((starType, i) => {
        return (
          <AnimatedIcon
            key={i}
            active={isInteracting && rating - i >= 0}
            animationConfig={animationConfig}
          >
            <Star type={starType} size={starSize} color={starColor} />
          </AnimatedIcon>
        );
      })}
    </ViewComponent>
  );
};

type AnimatedIconProps = {
  active: boolean;
  children: React.ReactElement;
  animationConfig: AnimationConfig;
};

const AnimatedIcon: React.FC<AnimatedIconProps> = ({
  active,
  animationConfig,
  children,
}) => {
  const {
    scale = defaultAnimationConfig.scale,
    easing = defaultAnimationConfig.easing,
    duration = defaultAnimationConfig.duration,
  } = animationConfig;

  const animatedSize = useRef(new Animated.Value(active ? scale : 1));

  useEffect(() => {
    const animation = Animated.timing(animatedSize.current, {
      toValue: active ? scale : 1,
      useNativeDriver: true,
      easing,
      duration,
    });
    animation.start();
    return animation.stop;
  }, [active, scale, easing, duration]);

  return (
    <Animated.View
      pointerEvents="none"
      style={[
        styles.star,
        {
          transform: [
            {
              scale: animatedSize.current,
            },
          ],
        },
      ]}
    >
      {children}
    </Animated.View>
  );
};

const styles = StyleSheet.create({
  star: {
    marginHorizontal: 1,
  },
});
