import React, {
  ReactNode,
  useCallback,
  useEffect,
  useRef,
  useState,
} from "react";
import {
  Keyboard,
  LayoutChangeEvent,
  StyleSheet,
  TouchableOpacity,
  View,
} from "react-native";
import { PanGestureHandler } from "react-native-gesture-handler";
import Animated, { runOnJS, withTiming } from "react-native-reanimated";
import { Platform_iOS, useReanimatedValue } from "../../commons";
import {
  DEFAULT_ANIMATION_TYPE,
  DEFAULT_MODAL_TYPE,
  DEFAULT_OVERLAY_COLOR,
  DURATION,
  emptyFunc,
} from "./configs";
import { useBack, useGesture, useScroll, useStyle, useTimeout } from "./hooks";
import { LayoutType, ModalProps } from "./types";

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  fullWidth: {
    width: "100%",
  },
  fullHeight: {
    height: "100%",
  },
});

export default (props: ModalProps) => {
  const {
    offset = 0,
    visible = false,
    fullsize = false,
    disableGesture = false,
    disableSwipeDismiss = false,
    disableBackdropDismiss = false,

    children = undefined,
    childrenScroll = false,

    overlayColor = DEFAULT_OVERLAY_COLOR,
    modalType = DEFAULT_MODAL_TYPE,
    animationType = DEFAULT_ANIMATION_TYPE,

    onBack = undefined,
    onOpen = emptyFunc,
    onOpened = emptyFunc,
    onClose = emptyFunc,
    onClosed = emptyFunc,
    renderOverlay,
  } = props;

  const enableFullGesture = Platform_iOS || !childrenScroll;
  const center = modalType === "center";
  const horizontal = modalType === "left" || modalType === "right";

  const visibleRef = useRef(false);
  const [layout, setLayout] = useState<LayoutType>({
    x: 0,
    y: 0,
    width: 0,
    height: 0,
  });

  const gestureX = useReanimatedValue(0);
  const gestureY = useReanimatedValue(0);
  const visibleValue = useReanimatedValue(0);
  const { overlayStyle, containerStyle, contentStyle } = useStyle(
    layout,
    modalType,
    animationType,
    visibleValue,
    gestureX,
    gestureY
  );

  const [startHandle, stopHandle] = useTimeout(DURATION * 2);

  const onLayout = useCallback((ev: LayoutChangeEvent) => {
    setLayout(ev.nativeEvent.layout);
  }, []);

  const onHandle = useCallback(() => {
    stopHandle();
    (visibleRef.current ? onOpened : onClosed)();
  }, [onOpened, onClosed]);

  const visibleHandler = useCallback(
    (val: boolean) => {
      Keyboard.dismiss();
      visibleRef.current = val;
      val ? onOpen() : onClose();
      startHandle(() => onHandle());

      visibleValue.value = withTiming(
        val ? 1 : 0,
        { duration: DURATION },
        (finished) => {
          finished && runOnJS(onHandle)();
        }
      );
    },
    [onOpen, onClose]
  );

  const gestureHandler = useGesture(
    layout,
    modalType,
    gestureX,
    gestureY,
    disableSwipeDismiss ? undefined : visibleHandler
  );

  const scrollHandler = useScroll(
    layout,
    modalType,
    gestureX,
    gestureY,
    disableSwipeDismiss ? undefined : visibleHandler
  );

  const renderGesture = useCallback(
    (content: ReactNode) => {
      return (
        <PanGestureHandler
          onGestureEvent={disableGesture ? undefined : gestureHandler}
        >
          {content}
        </PanGestureHandler>
      );
    },
    [disableGesture]
  );

  const renderDismiss = useCallback(() => {
    return (
      <Animated.View
        style={
          center || !fullsize
            ? styles.container
            : horizontal
            ? { ...styles.fullHeight, width: offset }
            : { ...styles.fullWidth, height: offset }
        }
      >
        <TouchableOpacity
          activeOpacity={1}
          disabled={disableBackdropDismiss}
          style={styles.container}
          onPress={() => visibleHandler(false)}
        />
      </Animated.View>
    );
  }, [center, offset, fullsize, horizontal, disableBackdropDismiss]);

  const renderHandle = useCallback(
    (content: ReactNode) => {
      return enableFullGesture
        ? content
        : renderGesture(<Animated.View>{content}</Animated.View>);
    },
    [enableFullGesture]
  );

  const renderContent = () => {
    return (
      <Animated.View style={StyleSheet.absoluteFill}>
        {/* backdrop */}
        <Animated.View
          style={[
            overlayStyle,
            StyleSheet.absoluteFill,
            { backgroundColor: overlayColor },
          ]}
        >
          {renderOverlay?.()}
        </Animated.View>

        {/* container */}
        <Animated.View style={[containerStyle, styles.container]}>
          {/* dismiss */}
          {(modalType === "bottom" || modalType === "right") &&
            (enableFullGesture
              ? renderDismiss()
              : renderGesture(renderDismiss()))}

          {center && (
            <View style={StyleSheet.absoluteFill}>{renderDismiss()}</View>
          )}

          {/* content */}
          <Animated.View
            style={[
              contentStyle,
              center
                ? {}
                : fullsize
                ? styles.container
                : horizontal
                ? styles.fullHeight
                : styles.fullWidth,
            ]}
            onLayout={onLayout}
          >
            {typeof children === "function"
              ? children(scrollHandler, renderHandle)
              : children}
          </Animated.View>

          {/* dismiss */}
          {(modalType === "top" || modalType === "left") &&
            (enableFullGesture
              ? renderDismiss()
              : renderGesture(renderDismiss()))}
        </Animated.View>
      </Animated.View>
    );
  };

  useBack(() => {
    onBack ? onBack() : visibleHandler(false);
  });

  useEffect(() => {
    visibleHandler(visible);
  }, [visible]);

  return enableFullGesture ? renderGesture(renderContent()) : renderContent();
};
