import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react'
import { BackHandler, Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'
import { Gesture, GestureDetector } from 'react-native-gesture-handler'
import Animated, {
  configureReanimatedLogger,
  Extrapolation,
  FadeIn,
  FadeInDown,
  FadeInLeft,
  FadeInRight,
  FadeInUp,
  FadeOut,
  FadeOutDown,
  FadeOutLeft,
  FadeOutRight,
  FadeOutUp,
  interpolate,
  ReanimatedLogLevel,
  runOnJS,
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  ZoomIn,
  ZoomOut,
} from 'react-native-reanimated'

import Overlay from './Overlay'

configureReanimatedLogger({
  level: ReanimatedLogLevel.warn,
  strict: false, // Reanimated runs in strict mode by default
})

export const defaultDirection = 'down'
export const defaultConfig = {
  animationInTiming: 150,
  animationOutTiming: 150,
  hideBackdrop: false,
  backdropColor: 'rgba(0, 0, 0, 0.5)',
  dampingFactor: 0.2,
  swipeDirection: defaultDirection,
  swipeVelocityThreshold: 500,
  onBackButtonPress: undefined,
  onBackdropPress: undefined,
  style: {},

  // 点击穿透
  // pointerEvents: 'auto',
}

const AnimatedPressable = Animated.createAnimatedComponent(Pressable)

const defaultAnimationInMap = {
  fade: FadeIn,
  zoom: ZoomIn,
  up: FadeInUp,
  down: FadeInDown,
  left: FadeInLeft,
  right: FadeInRight,
}

const defaultAnimationOutMap = {
  fade: FadeOut,
  zoom: ZoomOut,
  up: FadeOutUp,
  down: FadeOutDown,
  left: FadeOutLeft,
  right: FadeOutRight,
}

const ModalPortal = forwardRef((props, ref) => {
  const [config, setConfig] = useState(defaultConfig)
  const [modalContent, setModalContent] = useState()

  const translationX = useSharedValue(0)
  const translationY = useSharedValue(0)
  const prevTranslationX = useSharedValue(0)
  const prevTranslationY = useSharedValue(0)

  const onHideRef = useRef(() => {})

  const { width, height } = useWindowDimensions()

  const hide = useCallback(async (props) => {
    setModalContent(undefined)

    await new Promise((resolve) => {
      setTimeout(resolve, config.animationOutTiming)
    })

    translationX.value = 0
    translationY.value = 0

    onHideRef.current(props)
  }, [])

  useImperativeHandle(ref, () => ({
    hide,
    show: async (newComponent, newConfig = {}) => {
      if (modalContent) await hide()

      const mergedConfig = { ...defaultConfig, ...newConfig }

      setConfig(mergedConfig)
      if (React.isValidElement(newComponent) && newComponent) {
        setModalContent(newComponent)
      }

      return new Promise((resolve) => {
        onHideRef.current = resolve
      })
    },
  }))

  const onBackdropPress = useMemo(() => {
    return config.onBackdropPress ?? (() => hide())
  }, [config.onBackdropPress, hide])

  const isHorizontal = config.swipeDirection === 'left' || config.swipeDirection === 'right'

  const rangeMap = useMemo(() => {
    return {
      up: -height,
      down: height,
      left: -width,
      right: width,
    }
  }, [height, width])

  const pan = Gesture.Pan()
    .enabled(!!config.swipeDirection)
    .minDistance(1)
    .onStart(() => {
      prevTranslationX.value = translationX.value
      prevTranslationY.value = translationY.value
    })
    .onUpdate((event) => {
      const translationValue = isHorizontal ? event.translationX : event.translationY

      const prevTranslationValue = isHorizontal ? prevTranslationX.value : prevTranslationY.value

      const shouldDampMap = {
        up: translationValue > 0,
        down: translationValue < 0,
        left: translationValue > 0,
        right: translationValue < 0,
      }

      const shouldDamp = shouldDampMap[config.swipeDirection ?? defaultDirection]

      const dampedTranslation = shouldDamp
        ? prevTranslationValue + translationValue * config.dampingFactor
        : prevTranslationValue + translationValue

      if (isHorizontal) {
        translationX.value = dampedTranslation
      } else {
        translationY.value = dampedTranslation
      }
    })
    .onEnd((event) => {
      const velocityThreshold = config.swipeVelocityThreshold

      const shouldHideMap = {
        up: event.velocityY < -velocityThreshold,
        down: event.velocityY > velocityThreshold,
        right: event.velocityX > velocityThreshold,
        left: event.velocityX < -velocityThreshold,
      }

      const shouldHide = shouldHideMap[config.swipeDirection ?? defaultDirection]

      if (!shouldHide) {
        translationX.value = withSpring(0, {
          velocity: event.velocityX,
          damping: 75,
        })
        translationY.value = withSpring(0, {
          velocity: event.velocityY,
          damping: 75,
        })
        return
      }

      const mainTranslation = isHorizontal ? translationX : translationY

      mainTranslation.value = withSpring(
        rangeMap[config.swipeDirection ?? defaultDirection],
        { velocity: event.velocityX, overshootClamping: true },
        (success) => {
          success && runOnJS(hide)()
        },
      )

      return
    })

  const animatedStyles = useAnimatedStyle(
    () => ({
      transform: [{ translateX: translationX.value }, { translateY: translationY.value }],
    }),
    [translationX.value, translationY.value],
  )

  const animatedBackdropStyles = useAnimatedStyle(() => {
    const translationValue = isHorizontal ? translationX.value : translationY.value

    return {
      opacity: interpolate(
        translationValue,
        [rangeMap[config.defaultDirection ?? defaultDirection], 0],
        [0, 1],
        Extrapolation.CLAMP,
      ),
    }
  }, [config.swipeDirection, translationX.value, translationY.value, rangeMap])

  useEffect(() => {
    if (!modalContent) return
    const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
      if (config.onBackButtonPress) {
        config.onBackButtonPress()
      } else {
        hide()
      }
      return true
    })
    return () => backHandler.remove()
  }, [config.onBackButtonPress, hide, modalContent])

  const isBackdropVisible = modalContent && !config.hideBackdrop

  return (
    <Overlay>
      <View pointerEvents={isBackdropVisible ? 'auto' : 'box-none'} style={StyleSheet.absoluteFill}>
        {modalContent && (
          <>
            <Animated.View
              entering={FadeIn.duration(config.animationInTiming)}
              exiting={FadeOut.duration(config.animationOutTiming)}
              pointerEvents={isBackdropVisible ? 'auto' : 'none'}
              style={styles.backdropContainer}
            >
              <AnimatedPressable
                testID="magic-modal-backdrop"
                style={[
                  styles.backdrop,
                  animatedBackdropStyles,
                  {
                    backgroundColor: isBackdropVisible ? config.backdropColor : 'transparent',
                  },
                ]}
                onPress={onBackdropPress}
              />
            </Animated.View>

            <Animated.View pointerEvents="box-none" style={[styles.overlay, animatedStyles]}>
              <Animated.View
                exiting={
                  config.exiting ??
                  defaultAnimationOutMap[config.swipeDirection ?? defaultDirection].duration(config.animationOutTiming)
                }
                pointerEvents="box-none"
                style={[styles.overlay, config.style]}
                // entering={FadeInUp}
                entering={
                  config.entering ??
                  defaultAnimationInMap[config.swipeDirection ?? defaultDirection].duration(config.animationInTiming)
                }
              >
                <GestureDetector gesture={pan}>
                  <View collapsable={false}>{modalContent}</View>
                </GestureDetector>
              </Animated.View>
            </Animated.View>
          </>
        )}
      </View>
    </Overlay>
  )
})

ModalPortal.displayName = 'ModalPortal'
export default ModalPortal

const styles = StyleSheet.create({
  overlay: {
    flex: 1,
    justifyContent: 'center',
  },
  backdrop: {
    flex: 1,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
  },
  backdropContainer: {
    position: 'absolute',
    width: '100%',
    height: '100%',
  },
})
