import React, { useRef, useState } from "react";
import { Animated, View } from "react-native";

import { useSafeAreaInsets } from "react-native-safe-area-context";

import {
  useModalAnimationContext,
  PlayerAnimationStateEnum,
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";

import {
  useGetBottomTabBarHeight,
  useNavigation,
  useDimensions,
} from "@applicaster/zapp-react-native-utils/reactHooks";
import { useIsRTL } from "@applicaster/zapp-react-native-utils/localizationUtils";
import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
import { useAppState } from "@applicaster/zapp-react-native-utils/reactHooks/app";

import {
  gestureListenerHelper,
  getAnimationStyle,
  defaultAspectRatioWidth,
  getAnimationDefaultValue,
  ComponentAnimationType,
  AUDIO_PLAYER_HORIZONTAL_PADDING,
} from "./utils";

type Props = {
  animationType: string;
  children: React.ReactNode;
  style?: Record<string, any>;
  additionalData?: { [key: string]: any };
};

export const AnimationView = ({
  animationType,
  style,
  children,
  additionalData = {},
}: Props) => {
  const {
    startComponentsAnimation,
    setStartComponentsAnimation,
    playerAnimationState,
    minimisedHeight,
    animatedValues: { dragScrollY, dragVideoPlayerY },
    lastScrollYValue,
    scrollPosition,
    tabletLandscapePlayerTopPosition,
    setTabletLandscapePlayerTopPosition,
    modalSnapPoints,
    startComponentsAnimationDistance,
    progressBarHeight,
  } = useModalAnimationContext();

  const isRTL = useIsRTL();
  const isAppActive = useAppState(true);

  const {
    width: screenWidth,
    deviceInfo: { isTablet, isTabletLandscape },
  } = useDimensions("window", {
    deviceInfo: true,
    updateForInactiveScreens: true,
  });

  const { videoModalState } = useNavigation();
  const { mode: videoModalMode, item: videoModalItem } = videoModalState;

  const insets = useSafeAreaInsets();
  const bottomTabBarHeight = useGetBottomTabBarHeight() + (insets.bottom || 0);
  const minimisedWidth = defaultAspectRatioWidth(minimisedHeight);
  const animationComponentRef = useRef(null);

  // retrieving top position of the player for tablet landscape mode
  const measureView = React.useCallback(() => {
    if (isTabletLandscape && tabletLandscapePlayerTopPosition === 0) {
      animationComponentRef.current.measure(
        (x, y, width, height, pageX, pageY) => {
          setTabletLandscapePlayerTopPosition(pageY - 20);
        }
      );
    }
  }, [tabletLandscapePlayerTopPosition]);

  const [defaultValue, setDefaultValue] = useState<any>(
    getAnimationDefaultValue(animationType, bottomTabBarHeight)
  );

  const isAudioItem = React.useMemo(
    () =>
      videoModalItem?.content?.type?.includes?.("audio") ||
      videoModalItem?.type?.value === "audio",
    [videoModalItem]
  );

  const inlineAudioPlayer = additionalData.inlineAudioPlayer;

  const moveUpValue = additionalData.saveArea
    ? -insets.top + (isAudioItem ? 0 : progressBarHeight)
    : isTablet
      ? isTabletLandscape &&
        (!isAudioItem || (isAudioItem && inlineAudioPlayer))
        ? -tabletLandscapePlayerTopPosition + progressBarHeight
        : -130
      : -50 + progressBarHeight;

  const moveComponentHorizontalValue = additionalData.moveValue
    ? isRTL
      ? additionalData.moveValue
      : -additionalData.moveValue
    : 0;

  const animationValueType: string =
    animationType === ComponentAnimationType.player ? "ValueXY" : "Value";

  const getAnimationValue = React.useCallback(
    (animationType, state) => {
      const defaultConfig = {
        toValue: 0,
        duration: state === PlayerAnimationStateEnum.minimize ? 150 : 100,
        useNativeDriver: true,
      };

      switch (animationType) {
        case ComponentAnimationType.bottomBar: {
          if (state === PlayerAnimationStateEnum.minimize) {
            return defaultConfig;
          }

          defaultConfig.toValue = defaultValue;

          return defaultConfig;
        }

        case ComponentAnimationType.player: {
          if (state === PlayerAnimationStateEnum.minimize) {
            const minWidth =
              isAudioItem && !inlineAudioPlayer
                ? minimisedHeight
                : minimisedWidth;

            return {
              ...defaultConfig,
              toValue: { x: minWidth, y: minimisedHeight },
              useNativeDriver: false,
            };
          }

          return {
            ...defaultConfig,
            toValue: defaultValue,
            useNativeDriver: false,
          };
        }

        case ComponentAnimationType.componentFade: {
          if (state === PlayerAnimationStateEnum.minimize) {
            defaultConfig.toValue = 0;

            return defaultConfig;
          }

          defaultConfig.toValue = defaultValue;

          return defaultConfig;
        }

        case ComponentAnimationType.componentAppears: {
          if (state === PlayerAnimationStateEnum.minimize) {
            defaultConfig.toValue = 1;

            return defaultConfig;
          }

          defaultConfig.toValue = defaultValue;

          return defaultConfig;
        }

        case ComponentAnimationType.moveUpComponent: {
          if (state === PlayerAnimationStateEnum.minimize) {
            defaultConfig.toValue = moveUpValue;

            return defaultConfig;
          }

          defaultConfig.toValue = defaultValue;

          return defaultConfig;
        }

        case ComponentAnimationType.moveComponentHorizontal: {
          if (state === PlayerAnimationStateEnum.minimize) {
            defaultConfig.toValue = moveComponentHorizontalValue;

            return defaultConfig;
          }

          defaultConfig.toValue = defaultValue;

          return defaultConfig;
        }

        case ComponentAnimationType.audioPlayerPaddingHorizontal: {
          if (state === PlayerAnimationStateEnum.minimize) {
            defaultConfig.toValue = isRTL
              ? AUDIO_PLAYER_HORIZONTAL_PADDING
              : -AUDIO_PLAYER_HORIZONTAL_PADDING;

            return defaultConfig;
          }

          defaultConfig.toValue = 0;

          return defaultConfig;
        }

        default:
          return defaultConfig;
      }
    },
    [
      defaultValue,
      minimisedWidth,
      minimisedHeight,
      isAudioItem,
      moveUpValue,
      moveComponentHorizontalValue,
    ]
  );

  const getInitialValue = React.useCallback(() => {
    const { mode: videoModalMode, previousMode } = videoModalState;
    const mode = videoModalMode === "PIP" ? previousMode : videoModalMode;

    switch (mode) {
      case "MINIMIZED":
        return getAnimationValue(
          animationType,
          PlayerAnimationStateEnum.minimize
        ).toValue;

      case "MAXIMIZED":
      default:
        return getAnimationValue(
          animationType,
          PlayerAnimationStateEnum.maximaze
        ).toValue;
    }
  }, [defaultValue, videoModalState]);

  const animatedValue = useRef(
    new Animated[animationValueType](getInitialValue())
  ).current;

  const calculationData = React.useMemo(
    () => ({
      isAudioItem,
      bottomTabBarHeight,
      minimisedHeight,
      minimisedWidth,
      defaultValue,
      moveUpValue,
      moveComponentHorizontalValue,
      isTablet,
      isTabletLandscape,
      isRTL,
      fromMiniPlayer: videoModalMode === "MINIMIZED",
      inlineAudioPlayer,
    }),
    [
      bottomTabBarHeight,
      minimisedHeight,
      minimisedWidth,
      defaultValue,
      moveUpValue,
      moveComponentHorizontalValue,
      videoModalMode,
    ]
  );

  React.useEffect(() => {
    if (additionalData.resetAnimationValue) {
      animatedValue.setValue(defaultValue);
    }
  }, [additionalData.resetAnimationValue, defaultValue]);

  React.useEffect(() => {
    if (animationType === ComponentAnimationType.player) {
      let width, height;

      if (additionalData.aspectRatio) {
        width = isTabletLandscape ? additionalData.width : screenWidth;

        height =
          (isTabletLandscape ? additionalData.width : screenWidth) /
          additionalData.aspectRatio;
      } else {
        width =
          typeof additionalData.width === "number" && additionalData.width;

        height =
          typeof additionalData.height === "number" && additionalData.height;
      }

      if (
        videoModalMode === "MAXIMIZED" &&
        defaultValue.x !== screenWidth &&
        width &&
        height
      ) {
        const value = { x: width, y: height };
        setDefaultValue(value);
        animatedValue.setValue(value);
      }
    }
  }, [screenWidth]);

  React.useEffect(() => {
    const { mode, previousMode } = videoModalState;

    if (mode === "MINIMIZED" && previousMode === "MAXIMIZED") {
      // set animation to the minimize values if moving from the player to another screen
      if (playerAnimationState === null) {
        const value = getAnimationValue(
          animationType,
          PlayerAnimationStateEnum.minimize
        ).toValue;

        animatedValue.setValue(value);
      }
    }
  }, [videoModalState]);

  React.useEffect(() => {
    if (
      playerAnimationState === PlayerAnimationStateEnum.minimize &&
      startComponentsAnimation &&
      videoModalMode === "MAXIMIZED"
    ) {
      Animated.timing(
        animatedValue,
        getAnimationValue(animationType, playerAnimationState)
      ).start();
    } else if (playerAnimationState === PlayerAnimationStateEnum.maximaze) {
      animatedValue.setValue(defaultValue);
    }
  }, [playerAnimationState, startComponentsAnimation, defaultValue]);

  React.useEffect(() => {
    const dragVideoPlayerYListenerId = dragVideoPlayerY.addListener(
      ({ value }) => {
        if (playerAnimationState === PlayerAnimationStateEnum.drag_player) {
          const preparedValue = Math.round(Math.abs(value));

          gestureListenerHelper({
            listenerValue: value,
            preparedValue,
            animationType,
            animatedValue,
            calculationData,
            modalSnapPoint: modalSnapPoints[1],
            startComponentsAnimationDistance,
            startComponentsAnimation,
            setStartComponentsAnimation,
            getAnimationValue,
          });
        }
      }
    );

    const dragListenerId = dragScrollY.addListener(({ value }) => {
      if (
        scrollPosition.current === 0 &&
        playerAnimationState === PlayerAnimationStateEnum.drag_scroll
      ) {
        const preparedValue = Math.round(
          Math.abs(value) - lastScrollYValue.current
        );

        gestureListenerHelper({
          listenerValue: value,
          preparedValue,
          animationType,
          animatedValue,
          calculationData,
          modalSnapPoint: modalSnapPoints[1],
          startComponentsAnimationDistance,
          startComponentsAnimation,
          setStartComponentsAnimation,
          getAnimationValue,
        });
      }
    });

    return () => {
      dragScrollY.removeListener(dragListenerId);
      dragVideoPlayerY.removeListener(dragVideoPlayerYListenerId);
    };
  }, [playerAnimationState, calculationData, startComponentsAnimation]);

  const preparedStyles = Array.isArray(style) ? style : [style];

  return (
    <Animated.View
      onLayout={additionalData.useLayoutMeasure && measureView}
      ref={animationComponentRef}
      style={[
        ...preparedStyles,
        (videoModalMode === "MAXIMIZED" ||
          videoModalMode === "MINIMIZED" ||
          (videoModalMode === "PIP" && isAppActive)) &&
          getAnimationStyle(animationType, animatedValue),
      ]}
    >
      {children}
    </Animated.View>
  );
};

export const AnimationComponent = (props: Props) => {
  const { additionalData = {}, style } = props;

  const {
    videoModalState: { visible },
  } = useNavigation();

  const useAnimation =
    visible && !additionalData.disableAnimatedComponent && !isTV();

  const isReactFragment = !useAnimation && !style;

  const Component = useAnimation
    ? AnimationView
    : style
      ? View
      : React.Fragment;

  if (
    additionalData.extraAnimation &&
    typeof additionalData.extraAnimation === "object" &&
    !Array.isArray(additionalData.extraAnimation) &&
    !!Object.keys(additionalData.extraAnimation).length
  ) {
    const animationType = Object.keys(additionalData.extraAnimation)[0];
    const animationProps = additionalData.extraAnimation[animationType] || {};
    delete additionalData.extraAnimation[animationType];

    return (
      // @ts-ignore
      <Component {...(isReactFragment ? null : props)}>
        <AnimationComponent
          {...animationProps}
          animationType={animationType}
          additionalData={{
            ...animationProps.additionalData,
            extraAnimation:
              !!Object.keys(additionalData.extraAnimation).length &&
              additionalData.extraAnimation,
          }}
        >
          {props.children}
        </AnimationComponent>
      </Component>
    );
  }

  if (isReactFragment) {
    // @ts-ignore
    return <Component>{props.children}</Component>;
  }

  return <Component {...props}>{props.children}</Component>;
};
