import type { ReactElement, ReactNode } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';
import type {
  StyleProp,
  ViewStyle,
  KeyboardAvoidingViewProps,
} from 'react-native';
import { Animated, Dimensions, Easing, Modal, Platform } from 'react-native';
import BottomSheetContext from './BottomSheetContext';
import Footer from './Footer';
import Header from './Header';
import ScrollView from './ScrollView';
import {
  StyledBackdrop,
  StyledBottomSheet,
  StyledFloatingBottomSheet,
  StyledFloatingWrapper,
  StyledKeyboardAvoidingView,
  StyledWrapper,
} from './StyledBottomSheet';

export interface BottomSheetProps {
  /**
   * Bottom sheet open state.
   */
  open: boolean;
  /**
   * Bottom sheet's header.
   */
  header?: string | ReactElement;
  /**
   * Bottom sheet's footer.
   */
  footer?: ReactNode;
  /**
   * Bottom sheet's content.
   */
  children?: ReactNode;
  /**
   * Callback is called when the Bottom Sheet finishes running animation.
   */
  onAnimationEnd?: () => void;
  /**
   * Callback is called when the Bottom Sheet is opened.
   */
  onOpen?: () => void;
  /**
   * Callback is called when the user taps the back button on Android or when the bottom sheet
   * is being dismiss by interacting outside of the bottom sheet.
   */
  onRequestClose?: () => void;
  /**
   * Callback that is called once the bottom sheet has been dismissed.
   */
  onDismiss?: () => void;
  /**
   * Displays an X button on bottom sheet header that will invoke onRequestClose callback on press.
   */
  showCloseButton?: boolean;
  /**
   * Enable the bottom sheet's backdrop.
   */
  hasBackdrop?: boolean;
  /**
   * Displays dividers between header, footer and body.
   */
  showDivider?: boolean;
  /**
   * Additional style.
   */
  style?: StyleProp<ViewStyle>;
  /**
   * Testing id of the component.
   */
  testID?: string;
  /**
   * keyboardAvoidingView's props
   * */
  keyboardAvoidingViewProps?: KeyboardAvoidingViewProps;
  /**
   * Supported orientations for the BottomSheet modal, iOS only.
   */
  supportedOrientations?: ('portrait' | 'landscape')[];
  /**
   * Variant of the bottom sheet.
   */
  variant?: 'fixed' | 'floating';
}

const BottomSheet = ({
  open,
  header,
  footer,
  children,
  onAnimationEnd,
  onOpen,
  onRequestClose,
  onDismiss,
  showCloseButton = true,
  hasBackdrop = true,
  showDivider = false,
  style,
  testID,
  keyboardAvoidingViewProps = {},
  supportedOrientations = ['portrait'],
  variant = 'fixed',
}: BottomSheetProps): ReactElement => {
  const { height } = Dimensions.get('window');

  // Internal state to control modal open/close timing with animation
  const [visible, setVisibility] = useState<boolean>(open);
  const animatedValue = useRef(new Animated.Value(open ? 0 : 1));
  const [internalShowDivider, setInternalShowDivider] =
    useState<boolean>(showDivider);
  const canCallOnDismiss = useRef(false);

  useEffect(() => {
    // Prevent calling onDismiss when the component has not yet opened
    if (open && !canCallOnDismiss.current) {
      canCallOnDismiss.current = true;
    }

    // Show the modal before the open animation start
    if (open && !visible) {
      setVisibility(open);
    }
  }, [open]);

  // Animation
  useEffect(() => {
    const animation = Animated.timing(animatedValue.current, {
      toValue: open ? 1 : 0,
      easing: Easing.inOut(Easing.cubic),
      useNativeDriver: true,
    });

    animation.start(({ finished }) => {
      if (finished) {
        if (!open && canCallOnDismiss.current) {
          setVisibility(false);
          onDismiss?.();
        }
        onAnimationEnd?.();
      }
    });

    return () => animation.stop();
  }, [open]);

  const interpolateY = animatedValue.current.interpolate({
    inputRange: [0, 1],
    outputRange: [height, 0],
  });

  // Backdrop opacity
  const interpolateOpacity = hasBackdrop
    ? animatedValue.current.interpolate({
        inputRange: [0, 1],
        outputRange: [0, 0.48],
      })
    : 0;

  const BottomSheetContextValue = useMemo(
    () => ({ setInternalShowDivider }),
    [setInternalShowDivider]
  );

  const BottomSheetWrapperComponent =
    variant === 'fixed' ? React.Fragment : StyledFloatingWrapper;

  const BottomSheetComponent =
    variant === 'fixed' ? StyledBottomSheet : StyledFloatingBottomSheet;

  return (
    <Modal
      visible={visible}
      onRequestClose={onRequestClose}
      transparent
      testID={testID}
      onShow={onOpen}
      supportedOrientations={supportedOrientations}
    >
      <StyledWrapper pointerEvents="box-none">
        <StyledKeyboardAvoidingView
          behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
          {...keyboardAvoidingViewProps}
        >
          <StyledBackdrop
            style={{ opacity: interpolateOpacity }}
            onPress={onRequestClose}
          />
          <BottomSheetWrapperComponent>
            <BottomSheetComponent
              style={[
                style,
                {
                  transform: [
                    { scaleY: height > 0 ? 1 : 0 },
                    {
                      translateY: interpolateY,
                    },
                  ],
                },
              ]}
            >
              {header !== undefined ? (
                <Header
                  variant={variant}
                  content={header}
                  showDivider={internalShowDivider}
                  onRequestClose={onRequestClose}
                  showCloseButton={showCloseButton}
                />
              ) : null}

              <BottomSheetContext.Provider value={BottomSheetContextValue}>
                {children}
              </BottomSheetContext.Provider>

              {footer ? (
                <Footer showDivider={showDivider}>{footer}</Footer>
              ) : null}
            </BottomSheetComponent>
          </BottomSheetWrapperComponent>
        </StyledKeyboardAvoidingView>
      </StyledWrapper>
    </Modal>
  );
};

export default Object.assign(BottomSheet, {
  ScrollView,
});
