import { useWindowDimensions } from 'react-native'
import { Gesture } from 'react-native-gesture-handler'
import Animated, {
  Extrapolation,
  WithSpringConfig,
  interpolate,
  scrollTo,
  useAnimatedRef,
  useAnimatedScrollHandler,
  useAnimatedStyle,
  useDerivedValue,
  useSharedValue,
  withSpring,
} from 'react-native-reanimated'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { AnimatedView } from 'src/common/ui/AnimatedPrimitives'
import friction from 'src/utils/friction'
import { Text, View } from 'tamagui'

const springConfig: WithSpringConfig = {
  damping: 50,
  mass: 0.3,
  stiffness: 120,
  overshootClamping: true,
  restSpeedThreshold: 0.3,
  restDisplacementThreshold: 0.3,
}

interface SplitScreenProps {
  borderRadius: number
  handleHeight: number
  topSnapPointHeight: number
  bottomSnapPointHeight: number
  inBetweenSnapPoints: ({ height }: { height: number }) => number[]
  velocityThreshold?: number
  maxVelocityThreshold?: number
}

const useSplitScreen = ({
  handleHeight,
  inBetweenSnapPoints,
  topSnapPointHeight,
  bottomSnapPointHeight,
  velocityThreshold = 400,
  maxVelocityThreshold = 2500,
}: Omit<SplitScreenProps, 'borderRadius'>) => {
  const { height } = useWindowDimensions()
  const snapPoints = [
    bottomSnapPointHeight,
    ...inBetweenSnapPoints({ height }),
    height - topSnapPointHeight - handleHeight,
  ]
  const currentSnapPointIndex = useSharedValue(0)
  const startingHeight = useSharedValue(0)
  const translateY = useSharedValue(0)
  const bottomComponentHeight = useSharedValue(snapPoints[0])
  const isDraggingHandle = useSharedValue(false)

  const scrollViewRef = useAnimatedRef<Animated.ScrollView>()
  const scrollPosition = useSharedValue(0)

  /**
   * Progress of the bottom component between snap points from 0...N to simplify animation calculations
   */
  const snapPointProgress = useDerivedValue(() => {
    const value = interpolate(bottomComponentHeight.value, snapPoints, [
      ...Array(snapPoints.length).keys(),
    ])
    return value
  })

  const panGesture = Gesture.Pan()
    .minDistance(10)
    .onStart((e) => {
      isDraggingHandle.value = true
      startingHeight.value = bottomComponentHeight.value
    })
    .onUpdate((e) => {
      translateY.value = startingHeight.value - e.translationY

      if (translateY.value < snapPoints[0]) {
        const distance = snapPoints[0] - translateY.value
        bottomComponentHeight.value = snapPoints[0] - friction(distance)
      } else if (translateY.value > snapPoints[snapPoints.length - 1]) {
        const distance = snapPoints[snapPoints.length - 1] - translateY.value
        bottomComponentHeight.value =
          snapPoints[snapPoints.length - 1] - friction(distance)
      } else {
        bottomComponentHeight.value = startingHeight.value - e.translationY
      }

      const closestSnapPoint = snapPoints.reduce((prev, curr) => {
        return Math.abs(curr - bottomComponentHeight.value) <
          Math.abs(prev - bottomComponentHeight.value)
          ? curr
          : prev
      })
      currentSnapPointIndex.value = snapPoints.indexOf(closestSnapPoint)
    })
    .onEnd((e) => {
      isDraggingHandle.value = false

      let closestSnapPoint
      if (Math.abs(e.velocityY) > maxVelocityThreshold) {
        // If velocity is high, snap to first or last snap point
        if (e.velocityY < 0) {
          // Swipe up - Move to the last snap point
          closestSnapPoint = snapPoints[snapPoints.length - 1]
        } else {
          // Swipe down - Move to the first snap point
          closestSnapPoint = snapPoints[0]
        }
      } else if (Math.abs(e.velocityY) > velocityThreshold) {
        // Determine direction of swipe
        if (
          e.velocityY < 0 &&
          currentSnapPointIndex.value < snapPoints.length - 1
        ) {
          // Swipe up - Move to the next higher snap point
          closestSnapPoint = snapPoints[currentSnapPointIndex.value + 1]
        } else if (e.velocityY > 0 && currentSnapPointIndex.value > 0) {
          // Swipe down - Move to the next lower snap point
          closestSnapPoint = snapPoints[currentSnapPointIndex.value - 1]
        } else {
          // If velocity is high but we're at the ends, stay at the current snap point
          closestSnapPoint = snapPoints[currentSnapPointIndex.value]
        }
      } else {
        // If velocity is below the threshold, find the closest snap point
        closestSnapPoint = snapPoints.reduce((prev, curr) => {
          return Math.abs(curr - bottomComponentHeight.value) <
            Math.abs(prev - bottomComponentHeight.value)
            ? curr
            : prev
        })
      }

      bottomComponentHeight.value = withSpring(closestSnapPoint, springConfig)
      currentSnapPointIndex.value = snapPoints.indexOf(closestSnapPoint)
    })

  const topComponentStyles = useAnimatedStyle(() => {
    const scale = interpolate(
      snapPointProgress.value,
      [snapPoints.length - 1, snapPoints.length],
      [1, 0.5],
      {
        extrapolateLeft: Extrapolation.CLAMP,
        extrapolateRight: Extrapolation.EXTEND,
      }
    )
    return {
      height: bottomComponentHeight.value,
      transform: [{ scale }],
    }
  })

  const bottomComponentStyles = useAnimatedStyle(() => {
    const scale = interpolate(snapPointProgress.value, [0, -1], [1, 0.5], {
      extrapolateLeft: Extrapolation.CLAMP,
      extrapolateRight: Extrapolation.EXTEND,
    })
    return {
      height: bottomComponentHeight.value,
      transform: [{ scale }],
    }
  })

  const innerTopIdleComponentStyles = useAnimatedStyle(() => {
    const opacity = interpolate(
      snapPointProgress.value,
      [snapPoints.length - 1, snapPoints.length - 1 - 1 / 3],
      [1, 0],
      {
        extrapolateLeft: Extrapolation.CLAMP,
        extrapolateRight: Extrapolation.EXTEND,
      }
    )
    return {
      opacity,
    }
  })

  const innerBottomIdleComponentStyles = useAnimatedStyle(() => {
    const opacity = interpolate(snapPointProgress.value, [1 / 3, 0], [0, 1], {
      extrapolateLeft: Extrapolation.CLAMP,
      extrapolateRight: Extrapolation.EXTEND,
    })
    return {
      opacity,
    }
  })

  // Small test for scrollview - Maybe using a scrollview is not the best idea
  // Simulate ScrollView with PanGesture ?
  const scrollHandler = useAnimatedScrollHandler({
    onEndDrag: (event) => {
      scrollPosition.value = event.contentOffset.y
    },
    onMomentumEnd: (event) => {
      scrollPosition.value = event.contentOffset.y
    },
  })

  useDerivedValue(() => {
    if (!isDraggingHandle.value) return

    scrollTo(
      scrollViewRef,
      0,
      scrollPosition.value + (translateY.value - startingHeight.value) * 0.1,
      false
    )
  })

  return {
    panGesture,
    topComponentStyles,
    bottomComponentStyles,
    innerTopIdleComponentStyles,
    innerBottomIdleComponentStyles,
    scrollViewRef,
    scrollHandler,
  }
}

export const SplitScreen = ({
  borderRadius,
  handleHeight,
  inBetweenSnapPoints,
  topSnapPointHeight,
  bottomSnapPointHeight,
  velocityThreshold,
  maxVelocityThreshold,
}: SplitScreenProps) => {
  const insets = useSafeAreaInsets()

  const {
    panGesture,
    topComponentStyles,
    bottomComponentStyles,
    innerTopIdleComponentStyles,
    innerBottomIdleComponentStyles,
    scrollViewRef,
    scrollHandler,
  } = useSplitScreen({
    handleHeight,
    inBetweenSnapPoints,
    topSnapPointHeight,
    bottomSnapPointHeight,
    velocityThreshold,
    maxVelocityThreshold,
  })

  return (
    <View flex={1} bg="black">
      <AnimatedView
        flex={1}
        bg="white"
        borderBottomStartRadius={borderRadius}
        borderBottomEndRadius={borderRadius}
        zIndex={1}
        style={topComponentStyles}
        overflow="hidden"
      >
        <Animated.ScrollView
          ref={scrollViewRef}
          onScroll={scrollHandler}
          contentContainerStyle={{
            paddingTop: insets.top,
            alignItems: 'center',
            justifyContent: 'center',
            gap: 10,
          }}
        >
          <Text>Some Text</Text>
          {[...Array(70).keys()].map((i) => (
            <Text key={i}>Some Text</Text>
          ))}
        </Animated.ScrollView>
        <AnimatedView
          height={topSnapPointHeight}
          alignItems="center"
          justifyContent="center"
          pos="absolute"
          bottom={0}
          left={0}
          right={0}
          bg="white"
          paddingTop={insets.top}
          style={innerTopIdleComponentStyles}
        >
          <Text>Idle Top</Text>
        </AnimatedView>
      </AnimatedView>
      <AnimatedView
      zIndex={2}
      h={handleHeight}
      bg="black"
      alignItems="center"
      justifyContent="center"
    >
      <View height={7} width={60} bg="white" borderRadius={10} opacity={0.8} />
      <GestureDetector gesture={panGesture}>
        <AnimatedView
          pos="absolute"
          top={-15}
          bottom={-15}
          right={0}
          left={0}
        />
      </GestureDetector>
    </AnimatedView>
      <AnimatedView
        bg="white"
        borderTopStartRadius={borderRadius}
        borderTopEndRadius={borderRadius}
        pos="relative"
        zIndex={1}
        overflow="hidden"
        style={bottomComponentStyles}
      >
        <AnimatedView height={500} alignItems="center" justifyContent="center">
          <Text>Bottom component</Text>
        </AnimatedView>
        <AnimatedView
          height={bottomSnapPointHeight}
          alignItems="center"
          justifyContent="center"
          pos="absolute"
          top={0}
          left={0}
          right={0}
          bg="white"
          paddingBottom={insets.bottom}
          style={innerBottomIdleComponentStyles}
        >
          <Text>Idle Bottom</Text>
        </AnimatedView>
      </AnimatedView>
    </View>
  )
}
