import * as React from "react";
import { Animated, EasingFunction, StyleProp, ViewStyle } from "react-native";
import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";
import { toBooleanWithDefaultFalse } from "@applicaster/zapp-react-native-utils/booleanUtils";
import { noop } from "@applicaster/zapp-react-native-utils/functionUtils";

type AnimatedInterpolatedStyle =
  | Animated.AnimatedInterpolation
  | [{ [Key: string]: Animated.AnimatedInterpolation }];

type AnimationConfig = {
  duration: number;
  easing: EasingFunction;
  styles: (animatedValue: Animated.Value) => {
    [Key: string]: AnimatedInterpolatedStyle | StyleProp<any>;
  };
  delay?: number;
  onAnimationEnd?: () => void;
};

export type AnimatedInConfig =
  | AnimationConfig
  | {
      in: AnimationConfig;
      out: AnimationConfig;
    };

type Props = {
  visible: boolean;
  animationConfig: AnimatedInConfig;
  children: React.ReactElement<any>;
  staticStyles?: ViewStyle;
};

function getAnimation(config: AnimatedInConfig, phase: "in" | "out") {
  return config[phase] || config;
}

export function AnimatedInOut({
  visible,
  animationConfig,
  staticStyles = {},
  children,
}: Props) {
  const [animatedValue] = React.useState(new Animated.Value(visible ? 1 : 0));
  const [animating, setAnimating] = React.useState(undefined);

  const previousVisible = usePrevious(toBooleanWithDefaultFalse(visible));

  function startAnimation(toValue, config) {
    if (animating) {
      animating.reset();
    }

    const { duration, easing, delay = 0, onAnimationEnd = noop } = config;

    const compositeAnimation = Animated.timing(animatedValue, {
      duration,
      toValue,
      easing,
      delay,
      useNativeDriver: true,
    }).start(() => {
      setAnimating(undefined);
      onAnimationEnd();
    });

    setAnimating(compositeAnimation);
  }

  React.useEffect(() => {
    if (!previousVisible && visible) {
      startAnimation(1.0, getAnimation(animationConfig, "in"));
    }

    if (previousVisible && !visible) {
      startAnimation(0.0, getAnimation(animationConfig, "out"));
    }
  }, [visible, previousVisible]);

  const styles = visible
    ? getAnimation(animationConfig, "in").styles
    : getAnimation(animationConfig, "out").styles;

  return (
    <Animated.View
      renderToHardwareTextureAndroid={animating}
      style={[styles(animatedValue), staticStyles]}
    >
      {children}
    </Animated.View>
  );
}
