import type {SectionList} from "react-native"
import type Animated from "react-native-reanimated"
import {type FlatList, Gesture, type ScrollView} from "react-native-gesture-handler"
import {
    type AnimatedRef,
    type SharedValue,
    scrollTo,
    useDerivedValue,
    useSharedValue,
} from "react-native-reanimated"

import {useBottomSheetToolboxInternalContext} from "../context"
import {useBottomSheetDragGesture} from "../hooks/use-bottom-sheet-drag-gesture"

export type Scrollable<T> =
    | FlatList<T>
    | ScrollView
    | SectionList<T>
    | Animated.FlatList<T>
    | Animated.ScrollView
    // biome-ignore lint/suspicious/noExplicitAny: not useful to type
    | any

export interface UseBottomSheetScrollGestureArgs<T> {
    scrollRef: AnimatedRef<Scrollable<T>>
    scrollOffsetYSharedValue: SharedValue<number>
    scrollContentHeightSharedValue: SharedValue<number | null>
    scrollLayoutHeightSharedValue: SharedValue<number | null>
    inverted: boolean
    isFrozenSharedValue?: SharedValue<boolean>
}

export function useBottomSheetScrollGesture<T>({
    scrollRef,
    scrollOffsetYSharedValue,
    scrollContentHeightSharedValue,
    scrollLayoutHeightSharedValue,
    inverted,
    isFrozenSharedValue,
}: UseBottomSheetScrollGestureArgs<T>) {
    const {normalizedSnapPointsSharedValue, currentPositionSharedValue} =
        useBottomSheetToolboxInternalContext()

    const isExpandedSharedValue = useDerivedValue(() => {
        const maxSnap = Math.max(...(normalizedSnapPointsSharedValue.get() || []))

        return currentPositionSharedValue.get() === maxSnap
    }, [normalizedSnapPointsSharedValue, currentPositionSharedValue])

    const panFrozenSharedValue = useDerivedValue(() => {
        let isAtTop = false
        const scrollOffsetY = scrollOffsetYSharedValue.get()

        if (inverted) {
            const scrollLayoutHeight = scrollLayoutHeightSharedValue.get()
            const scrollContentHeight = scrollContentHeightSharedValue.get()

            const isReady = scrollLayoutHeight !== null && scrollContentHeight !== null

            const ROUNDING_ERROR_EPSILON = 0.1
            isAtTop =
                isReady &&
                scrollContentHeight - scrollOffsetY - scrollLayoutHeight <= ROUNDING_ERROR_EPSILON
        } else {
            isAtTop = scrollOffsetY <= 0
        }

        return isFrozenSharedValue?.get() === true || (isExpandedSharedValue.get() && !isAtTop)
    }, [
        scrollOffsetYSharedValue,
        isFrozenSharedValue,
        inverted,
        scrollContentHeightSharedValue.get,
        scrollLayoutHeightSharedValue.get,
    ])

    const panGesture = useBottomSheetDragGesture({
        id: "scroll",
        clamped: true,
        frozenSharedValue: panFrozenSharedValue,
    })

    const nativeGesture = Gesture.Native()

    const initialScrollOffsetYSharedValue = useSharedValue(0)
    const scrollControllerGesture = Gesture.Pan()
        .onBegin(() => {
            initialScrollOffsetYSharedValue.set(scrollOffsetYSharedValue.get())
        })
        .onUpdate(() => {
            if (!isExpandedSharedValue.get()) {
                scrollTo(scrollRef, 0, initialScrollOffsetYSharedValue.get(), false)
            } else {
                initialScrollOffsetYSharedValue.set(scrollOffsetYSharedValue.get())
            }
        })
        .failOffsetY(9999999999)
        .failOffsetX(9999999999)

    return Gesture.Simultaneous(nativeGesture, panGesture, scrollControllerGesture)
}
