import React, { ReactNode, useEffect, useRef, useState } from 'react';
import {
  Animated,
  BackHandler,
  Easing,
  Image,
  LayoutChangeEvent,
  PanResponder,
  TouchableOpacity,
  View,
} from 'react-native';
import { CloseIcon } from '../../assets/images';
import { useSafeAreaDimensions } from '../../hooks/useSafeAreaDimensions';
import { getStylesObject } from './BottomSheet.styles';
import { useIsBackFromLongBackground } from '../../hooks/useAppStateListener';
import { BottomSheetNativePlaceType } from '../../types/internal/bottomSheet.types';
import { UNBaseView } from '../../nativeComponents/UNBaseView';

export interface BottomSheetProps {
  isOpen: boolean,
  children: ReactNode,
  onOpen: () => void,
  onClose: () => void,
  height: number,
  animationDuration: number,
  expandToMaxHeightEnabled: boolean,
  handleWebViewScroll: () => void,
  shouldEnableBottomSheetScroll: boolean,
  nativePlace?: BottomSheetNativePlaceType,
  isComponentLoading?: boolean,
  sliderMaxHeight: number
}

const BottomSheet = (props: BottomSheetProps) => {
  const isStatic = props.nativePlace === BottomSheetNativePlaceType.modal;
  const panelHeightValue = useRef(new Animated.Value(props.sliderMaxHeight)).current;
  const fadeAnim = useRef(new Animated.Value(0)).current;
  const styles = getStylesObject(fadeAnim);
  // Initialize to false: BottomSheet mounts fresh each time (conditionally rendered),
  // so useEffect will trigger fadeIn() when isOpen=true on mount.
  const [isPanelOpened, setIsPanelOpened] = useState(false);
  const [contentHeight, setContentHeight] = useState<number | undefined>(undefined);
  const { insets } = useSafeAreaDimensions();

  const [currentHeight, setCurrentHeight] = useState<number>(props.sliderMaxHeight);

  const [dy, setDy] = useState(0);

  const isAppInactive = useIsBackFromLongBackground();

  const _parentPanResponder = PanResponder.create({
    onMoveShouldSetPanResponderCapture: (_, gestureState) => {
      if (isStatic) return false;
      return props.shouldEnableBottomSheetScroll && Math.abs(gestureState.dy) > 10;
    },
    onPanResponderMove(_, gestureState) {
      if (props.shouldEnableBottomSheetScroll && gestureState.dy < 5) {
        props.handleWebViewScroll();
      }
      if (currentHeight + gestureState.dy < 0) return;
      if (!props.expandToMaxHeightEnabled && contentHeight && currentHeight + gestureState.dy < props.sliderMaxHeight - contentHeight) return;
      panelHeightValue.setValue(currentHeight + gestureState.dy);
      setDy(gestureState.dy);
    },
    onPanResponderRelease: () => {
      if (dy > 0) {
        collapse();
      } else if (!props.expandToMaxHeightEnabled) {
        contentHeight && setToContentHeight(contentHeight);
      } else {
        expand();
      }
    },
  });

  useEffect(() => {
    // on App Active State change reset the bottom-sheet
    if (isAppInactive) {
      setContentHeight(0);
      _dismiss(false);
    }
  }, [isAppInactive]);

  useEffect(() => {
    if (props.isOpen && !isPanelOpened) {
      fadeIn();
    } else if (isPanelOpened) {
      setContentHeight(0);
      _dismiss();
    }
  }, [props.isOpen]);

  useEffect(() => {
    const backHandlerSubscription = BackHandler.addEventListener('hardwareBackPress', _onBackPress);
    const id = panelHeightValue.addListener(e => setCurrentHeight(e.value));

    return (() => {
      backHandlerSubscription.remove();
      panelHeightValue.removeListener(id);
    });
  }, []);

  const fadeIn = () => {
    setIsPanelOpened(true);
    Animated.timing(fadeAnim, {
      toValue: 0.3,
      duration: 500,
      useNativeDriver: false,
    }).start();
  };

  const fadeOut = () => {
    /*
      withTimeout - most of the time we declare 'Close' after the animation is end.
      In a case that the app is not active we do it without timeout. (Otherwise a race condition may occur.)
    */
    setIsPanelOpened(false);
    Animated.timing(fadeAnim, {
      toValue: 0,
      duration: 500,
      useNativeDriver: false,
    }).start();
  };

  const setToContentHeight = (contentHeight: number) => {
    Animated.timing(panelHeightValue, {
      duration: props.animationDuration,
      easing: Easing.quad,
      toValue: props.sliderMaxHeight - contentHeight,
      useNativeDriver: false,
    }).start();
  };

  const expand = () => {
    const { animationDuration } = props;
    props.onOpen();
    if (contentHeight && currentHeight > (props.sliderMaxHeight - contentHeight)) {
      setToContentHeight(contentHeight);
      return;
    }

    Animated.timing(panelHeightValue, {
      duration: animationDuration,
      easing: Easing.quad,
      toValue: 0,
      useNativeDriver: false,
    }).start();
  };

  const collapse = () => {
    if (contentHeight && currentHeight > (props.sliderMaxHeight - contentHeight)) {
      _dismiss();
      return;
    }
    contentHeight && setToContentHeight(contentHeight);
  };

  const _onBackPress = () => {
    isPanelOpened && collapse();
    return isPanelOpened;
  };

  // Track the last height we animated to, so we skip duplicate animations
  // (prevents visual jitter when _setSize fires multiple times with the same height)
  // but still re-animate when the height actually changes.
  const lastAnimatedHeight = useRef(0);

  // Reset when new content starts loading, so the next opening can animate.
  useEffect(() => {
    if (props.isComponentLoading) {
      lastAnimatedHeight.current = 0;
    }
  }, [props.isComponentLoading]);

  const _setSize = (e: LayoutChangeEvent) => {
    const newContentHeight = e.nativeEvent.layout.height;
    setContentHeight(newContentHeight);
    if (newContentHeight > 0 && newContentHeight !== lastAnimatedHeight.current) {
      lastAnimatedHeight.current = newContentHeight;
      setToContentHeight(newContentHeight);
    }
  };

  const _dismiss = (withAnimation = true) => {
    const { animationDuration } = props;
    Animated.timing(panelHeightValue, {
      duration: withAnimation ? animationDuration : 0,
      easing: Easing.quad,
      toValue: props.sliderMaxHeight,
      useNativeDriver: false,
    }).start(() => {
      props.onClose();
    });
    fadeOut();
  };

  const {
    children,
  } = props;

  const isModal = props.nativePlace === BottomSheetNativePlaceType.modal;
  return (
    <>
      <TouchableOpacity
        style={styles.outsideContainer} activeOpacity={1}
        onPress={() => _dismiss()}
      >
        <Animated.View
          style={styles.animation}
        >
        </Animated.View>
      </TouchableOpacity>
      <Animated.View
        {..._parentPanResponder.panHandlers}
        style={{
          ...styles.container,
          borderTopLeftRadius: isModal ? 0 : styles.container.borderTopLeftRadius,
          borderTopRightRadius: isModal ? 0 : styles.container.borderTopRightRadius,
          height: props.sliderMaxHeight,
          transform: [
            { translateY: panelHeightValue },
          ],
          paddingTop: isModal ? insets.top : 0,
        }}
      >
        <View
          style={styles.outerContent}
        >
          <TouchableOpacity
            activeOpacity={1}
            style={styles.closeButton}
            onPress={() => _dismiss()}
          >
            <Image source={CloseIcon} />
          </TouchableOpacity>

          <View
            onLayout={_setSize}
          >
            <UNBaseView style={{ height: props.height }}>
              {children}
            </UNBaseView>

          </View>
        </View>
      </Animated.View>
    </>
  );
};

export default BottomSheet;
