import React from "react";
import { Animated, StyleSheet, View, Platform } from "react-native";

import { useNavigation } from "@applicaster/zapp-react-native-utils/reactHooks";
import {
  NativeViewGestureHandler,
  PanGestureHandler,
  State,
  TapGestureHandler,
  GestureHandlerRootView,
} from "react-native-gesture-handler";

import { PlayerContainerContext } from "@applicaster/zapp-react-native-ui-components/Components/PlayerContainer/PlayerContainerContext";
import {
  useModalAnimationContext,
  PlayerAnimationStateEnum,
} from "@applicaster/zapp-react-native-ui-components/Components/VideoModal/ModalAnimation";
import { isTV } from "@applicaster/zapp-react-native-utils/reactUtils";
import { usePrevious } from "@applicaster/zapp-react-native-utils/reactHooks/utils";

import {
  setScrollModalAnimatedValue,
  resetScrollAnimatedValues,
} from "./utils";

const getAnimatedConfig = (toValue) => {
  return {
    toValue,
    duration: 200,
    useNativeDriver: true,
  };
};

const generalStyles = StyleSheet.create({
  container: {
    flex: 1,
  },
});

type Props = {
  children: React.ReactNode;
};

export const AnimatedScrollModalComponent = ({ children }: Props) => {
  const {
    isActiveGesture,
    playerAnimationState,
    setPlayerAnimationState,
    resetPlayerAnimationState,
    animatedValues: {
      lastScrollY,
      dragScrollY,
      dragVideoPlayerY,
      translateYOffset,
    },
    lastScrollYValue,
    scrollPosition,
    modalSnapPoints,
    lastSnap,
    setLastSnap,
    setStartComponentsAnimation,
  } = useModalAnimationContext();

  const [enableGesture, setIEnableGesture] = React.useState<boolean>(true);

  const { isLanguageOverlayVisible, isSeekBarTouch } = React.useContext(
    PlayerContainerContext
  );

  const { maximiseVideoModal, minimiseVideoModal, videoModalState } =
    useNavigation();

  const { mode: videoModalMode, item: videoModalItem } = videoModalState;
  const isMaximazedModal = videoModalMode === "MAXIMIZED";
  const isMinimizedModal = videoModalMode === "MINIMIZED";
  const previousItemId = usePrevious(videoModalItem?.id);

  const isNotMinimizeMaximazeAnimation =
    playerAnimationState !== PlayerAnimationStateEnum.minimize &&
    playerAnimationState !== PlayerAnimationStateEnum.maximaze;

  const isEnablePanGesture =
    enableGesture &&
    !isLanguageOverlayVisible &&
    isNotMinimizeMaximazeAnimation &&
    !isSeekBarTouch &&
    (isMaximazedModal || isMinimizedModal);

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

  // Refs for components
  const scrollRef = React.useRef(null);
  const tapHandlerRef = React.useRef(null);
  const panHandlerRef = React.useRef(null);

  const onRegisterLastScroll = Animated.event(
    [{ nativeEvent: { contentOffset: { y: lastScrollY } } }],
    { useNativeDriver: true }
  );

  const onGestureEvent = Animated.event(
    [{ nativeEvent: { translationY: dragScrollY } }],
    { useNativeDriver: true }
  );

  const onHandlerStateChange = React.useCallback(
    ({ nativeEvent }) => {
      if (
        nativeEvent.oldState === State.ACTIVE &&
        scrollPosition.current === 0
      ) {
        // eslint-disable-next-line prefer-const
        const { velocityY, translationY } = nativeEvent;
        const dragToss = 0.05;

        const preparedTranslationY =
          Math.abs(translationY) - lastScrollYValue.current;

        if (videoModalMode === "MAXIMIZED") {
          const endOffsetY =
            lastSnap + preparedTranslationY + dragToss * velocityY + 150;

          let destSnapPoint = modalSnapPoints[0];

          for (const snapPoint of modalSnapPoints) {
            const distFromSnap = Math.abs(snapPoint - endOffsetY);

            if (distFromSnap < Math.abs(destSnapPoint - endOffsetY)) {
              destSnapPoint = snapPoint;
            }
          }

          setLastSnap(destSnapPoint);

          if (destSnapPoint === modalSnapPoints[0]) {
            translateYOffset.extractOffset();
            translateYOffset.setValue(preparedTranslationY);
            translateYOffset.flattenOffset();
            dragScrollY.setValue(0);

            setPlayerAnimationState(PlayerAnimationStateEnum.maximaze);
          } else {
            setPlayerAnimationState(PlayerAnimationStateEnum.minimize);
          }
        } else {
          if (translationY < 0) {
            // from mini to full
            setLastSnap(modalSnapPoints[0]);

            translateYOffset.setValue(
              modalSnapPoints[1] - preparedTranslationY
            );

            dragScrollY.setValue(0);

            setPlayerAnimationState(PlayerAnimationStateEnum.maximaze);
          } else {
            resetPlayerAnimationState();
          }
        }
      } else {
        playerAnimationState === PlayerAnimationStateEnum.drag_scroll &&
          resetPlayerAnimationState();
      }
    },
    [lastSnap, modalSnapPoints, playerAnimationState, videoModalMode]
  );

  const onScroll = React.useCallback(({ nativeEvent }) => {
    scrollPosition.current = nativeEvent.contentOffset.y;
  }, []);

  // Workaround for onMomentumScrollEnd issue
  // https://github.com/facebook/react-native/issues/32696#issuecomment-1104217223
  const canMomentum = React.useRef(false);

  const onMomentumScrollBegin = React.useCallback(() => {
    canMomentum.current = true;
  }, []);

  const onMomentumScrollEnd = React.useCallback(
    ({ nativeEvent }) => {
      if (canMomentum.current && !isActiveGesture) {
        if (nativeEvent.contentOffset.y === 0) {
          resetScrollAnimatedValues(
            lastScrollY,
            lastScrollYValue,
            dragScrollY,
            dragVideoPlayerY
          );

          setIEnableGesture(true);
        } else {
          setIEnableGesture(false);
        }

        canMomentum.current = false;
      }
    },
    [isActiveGesture]
  );

  React.useEffect(() => {
    return () => {
      scrollPosition.current = 0;

      resetScrollAnimatedValues(
        lastScrollY,
        lastScrollYValue,
        dragScrollY,
        dragVideoPlayerY
      );
    };
  }, []);

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

    if (
      mode === "MAXIMIZED" &&
      !enableGesture &&
      scrollPosition.current === 0
    ) {
      setIEnableGesture(true);
    }

    if (mode === "MINIMIZED" && previousMode === "MAXIMIZED") {
      // set animation to the minimize values if moving from the player to another screen
      if (playerAnimationState === null) {
        setScrollModalAnimatedValue(
          translateYOffset,
          modalSnapPoints[1],
          setLastSnap
        );

        resetScrollAnimatedValues(
          lastScrollY,
          lastScrollYValue,
          dragScrollY,
          dragVideoPlayerY
        );
      }
    } else if (
      playerAnimationState === null &&
      ((previousItemId === item?.id &&
        mode === "MAXIMIZED" &&
        (previousMode === "MINIMIZED" || previousMode === "MAXIMIZED")) ||
        (previousItemId !== item?.id && mode !== "FULLSCREEN"))
    ) {
      setPlayerAnimationState(PlayerAnimationStateEnum.maximaze);
    }
  }, [videoModalState]);

  React.useEffect(() => {
    if (playerAnimationState === PlayerAnimationStateEnum.minimize) {
      if (
        (scrollPosition.current === 0 &&
          (lastScrollY as any)._value !== 0 &&
          (dragScrollY as any)._value === 0 &&
          (dragVideoPlayerY as any)._value === 0) ||
        (scrollPosition.current !== 0 &&
          ((dragScrollY as any)._value !== 0 ||
            (dragVideoPlayerY as any)._value !== 0))
      ) {
        resetScrollAnimatedValues(
          lastScrollY,
          lastScrollYValue,
          dragScrollY,
          dragVideoPlayerY
        );
      }

      Animated.timing(
        translateYOffset,
        getAnimatedConfig(modalSnapPoints[1])
      ).start(() => {
        minimiseVideoModal();

        setScrollModalAnimatedValue(
          translateYOffset,
          modalSnapPoints[1],
          setLastSnap
        );

        resetScrollAnimatedValues(
          lastScrollY,
          lastScrollYValue,
          dragScrollY,
          dragVideoPlayerY
        );

        resetPlayerAnimationState();
      });
    } else if (playerAnimationState === PlayerAnimationStateEnum.maximaze) {
      Animated.timing(translateYOffset, getAnimatedConfig(0)).start(() => {
        maximiseVideoModal();
        setScrollModalAnimatedValue(translateYOffset, 0, setLastSnap);

        resetScrollAnimatedValues(
          lastScrollY,
          lastScrollYValue,
          dragScrollY,
          dragVideoPlayerY
        );

        resetPlayerAnimationState();
      });
    }
  }, [playerAnimationState]);

  React.useEffect(() => {
    const lastScrollYListenerId = lastScrollY.addListener(({ value }) => {
      lastScrollYValue.current = value;
    });

    const dragListenerId = dragScrollY.addListener(({ value }) => {
      const preparedValue =
        isMinimizedModal && value >= 0
          ? 0
          : Math.round(isAudioItem ? Math.abs(value) : value);

      if (
        preparedValue > 0 &&
        scrollPosition.current === 0 &&
        playerAnimationState !== PlayerAnimationStateEnum.drag_player &&
        playerAnimationState !== PlayerAnimationStateEnum.drag_scroll
      ) {
        isMinimizedModal && setStartComponentsAnimation(true);
        setPlayerAnimationState(PlayerAnimationStateEnum.drag_scroll);
      }
    });

    return () => {
      lastScrollY.removeListener(lastScrollYListenerId);
      dragScrollY.removeListener(dragListenerId);
    };
  }, [playerAnimationState, isAudioItem, isMinimizedModal]);

  const Wrapper = React.useMemo(
    () => (Platform.OS === "android" ? GestureHandlerRootView : View),
    []
  );

  return (
    <Wrapper style={generalStyles.container}>
      <TapGestureHandler
        maxDurationMs={100000}
        ref={tapHandlerRef}
        maxDeltaY={lastSnap - modalSnapPoints[0]}
        numberOfTaps={1}
      >
        <View pointerEvents="box-none">
          <PanGestureHandler
            enabled={isEnablePanGesture}
            ref={panHandlerRef}
            simultaneousHandlers={[scrollRef, tapHandlerRef]}
            shouldCancelWhenOutside={isMaximazedModal}
            onGestureEvent={onGestureEvent}
            onHandlerStateChange={onHandlerStateChange}
            activeOffsetY={[-5, 5]}
          >
            <Animated.View>
              <NativeViewGestureHandler
                ref={scrollRef}
                waitFor={tapHandlerRef}
                simultaneousHandlers={panHandlerRef}
              >
                <Animated.ScrollView
                  scrollEnabled={
                    isMaximazedModal && isNotMinimizeMaximazeAnimation
                  }
                  bounces={false}
                  onScrollBeginDrag={onRegisterLastScroll}
                  onScroll={onScroll}
                  onMomentumScrollBegin={onMomentumScrollBegin}
                  onMomentumScrollEnd={onMomentumScrollEnd}
                  scrollEventThrottle={1}
                  showsVerticalScrollIndicator={false}
                >
                  {children}
                </Animated.ScrollView>
              </NativeViewGestureHandler>
            </Animated.View>
          </PanGestureHandler>
        </View>
      </TapGestureHandler>
    </Wrapper>
  );
};

export const AnimatedScrollModal = ({ children }: Props) => {
  const {
    videoModalState: { visible },
  } = useNavigation();

  const Component =
    !isTV() && visible ? AnimatedScrollModalComponent : React.Fragment;

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