import {
    useImperativeHandle,
    type PointerEvent,
    forwardRef,
    useEffect,
    useState,
    useRef
} from "react";
import {
    type LayoutChangeEvent,
    useWindowDimensions,
    PanResponder,
    ScrollView,
    Platform,
    Animated,
    Easing,
    View
} from "react-native";
import type IBottomSheetProps from "./type";
import type {
    IBottomSheetRef
} from "./type";
import stylesheet from "./stylesheet";
import {
    NCoreUIKitTheme
} from "../../core/hooks";
import {
    useSafeAreaInsets
} from "react-native-safe-area-context";
import {
    type RefForwardingComponent,
    type SafePointerEvent,
    safeGlobal
} from "../../types";
import type {
    IModalRef
} from "../modal/type";
import {
    webStyle,
    uuid
} from "../../utils";
import Modal from "../modal";

const BottomSheet: RefForwardingComponent<IBottomSheetRef, IBottomSheetProps> = ({
    isContentReady: isContentReadyProp = true,
    renderBottom: RenderBottomComponent,
    renderHeader: RenderHeaderComponent,
    isForceFullScreenOnSwipe = false,
    handleContainerBackgroundColor,
    handleHeight: handleHeightProp,
    isCanFullScreenOnSwipe = false,
    isWrapSafeAreaContext = true,
    backgroundColor = "default",
    isWorkAsFullScreen = false,
    scrollEndThreshold = 0.85,
    isCloseOnOverlay = true,
    isWorkWithPortal = true,
    handleContainerSpacing,
    isActive: isActiveProp,
    handleBackgroundColor,
    isAutoHeight = false,
    isShowHandle = true,
    isSwipeClose = true,
    isCanSwipe = true,
    onOverlayPressed,
    scrollViewProps,
    scrollViewStyle,
    customTheme,
    onScrollEnd,
    modalProps,
    portalName,
    customKey,
    id: outID,
    snapPoint,
    children,
    onClosed,
    onOpened,
    onClose,
    onOpen,
    style,
    ...props
}, ref) => {
    const {
        radiuses,
        colors,
        spaces
    } = NCoreUIKitTheme.useContext(customTheme);

    const {
        bottom,
        top
    } = useSafeAreaInsets();

    const [
        isMeasured,
        setIsMeasured
    ] = useState(false);

    const [
        isActive,
        setIsActive
    ] = useState(isActiveProp === undefined ? false : isActiveProp);

    const {
        height: windowHeight,
        width: windowWidth
    } = useWindowDimensions();

    const id = useRef(outID ? outID : uuid());

    const bottomSheetKey = useRef(customKey ? customKey : uuid());

    let bottomSafeArea = isWrapSafeAreaContext ? bottom : 0;
    let topSafeArea = isWrapSafeAreaContext ? top : 0;

    if(isForceFullScreenOnSwipe) {
        topSafeArea = 0;
    }

    if(!isWorkWithPortal) {
        bottomSafeArea = 0;
        topSafeArea = 0;
    }

    const scrollViewRef = useRef<ScrollView>(null);
    const modalRef = useRef<IModalRef>(null);

    const containerHeightRef = useRef(windowHeight);

    const animatedTranslateY = useRef(new Animated.Value(snapPoint && !isWorkAsFullScreen ? snapPoint : containerHeightRef.current)).current;
    const animatedHeight = useRef(new Animated.Value(
        isAutoHeight ? 0 : snapPoint ?? 0
    )).current;

    const TOP_GRAB_AREA = 140;

    const maxHeight = useRef(isWorkAsFullScreen ? containerHeightRef.current - (isWrapSafeAreaContext ? topSafeArea : 0) : containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : isWrapSafeAreaContext ? topSafeArea : 0));
    const heightValue = useRef(isWorkAsFullScreen ? containerHeightRef.current : snapPoint ?? 0);
    const initialTranslateY = useRef(0);
    const translateYValue = useRef(0);
    const contentHeight = useRef(-1);
    const initialHeight = useRef(0);

    const scrollViewContentHeight = useRef(-1);
    const scrollViewLayoutHeight = useRef(-1);
    const initialScrollOffset = useRef(0);
    const scrollOffset = useRef(0);

    const gestureStartY = useRef(0);

    const isCanFullScreenOnSwipeRef = useRef(isCanFullScreenOnSwipe);
    const isWorkAsFullScreenRef = useRef(isWorkAsFullScreen);
    const isSwipeCloseRef = useRef(isSwipeClose);
    const isCanSwipeRef = useRef(isCanSwipe);

    const isContentReadyRef = useRef(isContentReadyProp);

    if(!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
        maxHeight.current = snapPoint;
    }

    useImperativeHandle(
        ref,
        () => ({
            close: (onClosed?) => {
                closeAnimation(undefined, onClosed);
            },
            setIsContentReady: (status) => {
                isContentReadyRef.current = status;
            },
            open: () => {
                setIsActive(true);
            }
        }),
        []
    );

    useEffect(() => {
        isCanSwipeRef.current = isCanSwipe;
    }, [
        isCanSwipe
    ]);

    useEffect(() => {
        isSwipeCloseRef.current = isSwipeClose;
    }, [
        isSwipeClose
    ]);

    useEffect(() => {
        isCanFullScreenOnSwipeRef.current = isCanFullScreenOnSwipe;
    }, [
        isCanFullScreenOnSwipe
    ]);

    useEffect(() => {
        isWorkAsFullScreenRef.current = isWorkAsFullScreen;
    }, [
        isWorkAsFullScreen
    ]);

    useEffect(() => {
        isContentReadyRef.current = isContentReadyProp;
    }, [
        isContentReadyProp
    ]);

    useEffect(() => {
        if (!isMeasured || contentHeight.current === -1) return;

        if (isCanFullScreenOnSwipe && !isWorkAsFullScreen) {
            maxHeight.current = containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : topSafeArea);
        } else if (isAutoHeight || !snapPoint) {
            maxHeight.current = contentHeight.current;
        }

        if (isActive) {
            open();
        }
    }, [
        isActive,
        isMeasured
    ]);

    if(isActiveProp !== undefined) {
        useEffect(() => {
            setIsActive(isActiveProp);
        }, [
            isActiveProp
        ]);
    }

    useEffect(() => {
        if (!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
            maxHeight.current = snapPoint;
        } else if (!isWorkAsFullScreen) {
            maxHeight.current = containerHeightRef.current - (isForceFullScreenOnSwipe ? 0 : isWrapSafeAreaContext ? topSafeArea : 0);
        } else {
            maxHeight.current = containerHeightRef.current - (isWrapSafeAreaContext ? topSafeArea : 0);
        }
    }, [
        isForceFullScreenOnSwipe,
        isCanFullScreenOnSwipe,
        isWrapSafeAreaContext,
        isWorkAsFullScreen,
        topSafeArea,
        snapPoint
    ]);

    useEffect(() => {
        if (!isActive) {
            const newSnapValue = isWorkAsFullScreen
                ? containerHeightRef.current
                : (snapPoint ?? contentHeight.current);

            animatedHeight.setValue(isAutoHeight ? 0 : newSnapValue);
            animatedTranslateY.setValue(newSnapValue);

            heightValue.current = newSnapValue;
            translateYValue.current = newSnapValue;
            initialHeight.current = newSnapValue;
        }

        if (!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
            maxHeight.current = snapPoint;
        } else if (!isWorkAsFullScreen) {
            maxHeight.current = containerHeightRef.current - (
                isForceFullScreenOnSwipe ? 0 : isWrapSafeAreaContext ? topSafeArea : 0
            );
        } else {
            maxHeight.current = containerHeightRef.current - (
                isWrapSafeAreaContext ? topSafeArea : 0
            );
        }
    }, [
        snapPoint,
        isWorkAsFullScreen
    ]);

    useEffect(() => {
        const listenerAHeightId = animatedHeight.addListener(({
            value
        }) => {
            heightValue.current = value;
        });

        const listenerTYId = animatedTranslateY.addListener(({
            value
        }) => {
            translateYValue.current = value;
        });

        return () => {
            animatedHeight.removeListener(listenerAHeightId);
            animatedTranslateY.removeListener(listenerTYId);
        };
    }, []);

    const checkScrollThreshold = () => {
        if (!onScrollEnd) return;

        const maxS = Math.max(0, scrollViewContentHeight.current - scrollViewLayoutHeight.current);

        if (maxS <= 0) return;

        const ratio = scrollOffset.current / maxS;

        if (ratio >= scrollEndThreshold) {
            onScrollEnd();
        }
    };

    const openAnimation = (runIndex?: number) => {
        if(isContentReadyRef.current) {
            Animated.timing(animatedTranslateY, {
                useNativeDriver: false,
                duration: 300,
                toValue: 0
            }).start(({
                finished
            }) => {
                if(finished) {
                    if(onOpened) onOpened();
                }
            });
        } else {
            if(!runIndex || runIndex < 10) {
                setTimeout(() => {
                    openAnimation(runIndex ? runIndex + 1 : 1);
                }, 100);
            }
        }
    };

    const open = () => {
        if (translateYValue.current < 0) {
            const safeValue = snapPoint ?? contentHeight.current;
            animatedTranslateY.setValue(safeValue);
            translateYValue.current = safeValue;
        }

        resetState();

        if (isAutoHeight || !snapPoint) {
            animatedHeight.setValue(contentHeight.current);
        }

        if(onOpen) onOpen();

        setTimeout(() => {
            openAnimation();
        }, 100);
    };

    const closeAnimation = (toValue?: number, _onClosed?: (props: {
        id: string;
    }) => void) => {
        if(onClose) onClose({
            id: id.current
        });

        const currentSnapPoint = isWorkAsFullScreen
            ? containerHeightRef.current
            : (isAutoHeight || !snapPoint ? contentHeight.current : snapPoint);

        Animated.timing(animatedTranslateY, {
            toValue: toValue ?? currentSnapPoint,
            useNativeDriver: false,
            duration: 300
        }).start(({
            finished
        }) => {
            if(finished) {
                resetState();

                setIsActive(false);

                if(onClosed) onClosed({
                    id: id.current
                });

                if(_onClosed) _onClosed({
                    id: id.current
                });
            }
        });
    };

    const onLayout = (event: LayoutChangeEvent) => {
        const {
            height
        } = event.nativeEvent.layout;

        if (height === contentHeight.current) return;

        contentHeight.current = height;

        if(!isMeasured) {
            animatedHeight.setValue(height);
            setIsMeasured(true);
        }
    };

    const resetState = () => {
        animatedHeight.setOffset(0);
        animatedTranslateY.setOffset(0);

        scrollOffset.current = 0;
        initialScrollOffset.current = 0;

        const pivot = isWorkAsFullScreen
            ? containerHeightRef.current
            : (isAutoHeight ? 0 : (snapPoint ?? contentHeight.current));

        initialHeight.current = pivot;
        initialTranslateY.current = 0;

        heightValue.current = pivot;
        translateYValue.current = 0;
    };

    const panResponder = useRef(
        PanResponder.create({
            onPanResponderMove: (_, gestureState) => {
                if(!isCanSwipeRef.current) return;

                const {
                    dy
                } = gestureState;

                const isAtTop = scrollOffset.current <= 0;
                const isAtTavan = heightValue.current >= maxHeight.current - 1;
                const hasScroll = scrollViewContentHeight.current > scrollViewLayoutHeight.current;

                const isFromTopArea = gestureStartY.current < TOP_GRAB_AREA;

                if (dy > 0 && isAtTavan && hasScroll && !isAtTop && !isFromTopArea) {
                    const currentDelta = -dy;

                    const initialS = initialScrollOffset.current;

                    const usedForS = Math.max(currentDelta, -initialS);

                    scrollOffset.current = initialS + usedForS;

                    scrollViewRef.current?.scrollTo({
                        y: scrollOffset.current,
                        animated: false
                    });

                    return;
                }

                const delta = -dy;

                const pivot = snapPoint ?? contentHeight.current;

                const initialH = initialHeight.current;
                const initialS = initialScrollOffset.current;
                const initialT = initialTranslateY.current;

                const maxS = Math.max(0, scrollViewContentHeight.current - scrollViewLayoutHeight.current);

                if (dy < 0) {
                    let currentDelta = delta;

                    const effectiveInitialT = Math.max(0, initialT);
                    const usedForT = Math.min(currentDelta, effectiveInitialT);

                    animatedTranslateY.setValue(-usedForT);
                    currentDelta -= usedForT;

                    if (currentDelta > 0) {
                        if (initialH < pivot) {
                            const spaceToPivot = pivot - initialH;
                            const usedForH = Math.min(currentDelta, spaceToPivot);

                            animatedHeight.setValue(usedForH);

                            currentDelta -= usedForH;
                        }

                        const isRestoringHeight = initialH < pivot;
                        const canScroll = isCanFullScreenOnSwipeRef.current || isWorkAsFullScreenRef.current || !isRestoringHeight;

                        if (currentDelta > 0 && canScroll) {
                            const remainingScroll = maxS - initialS;
                            if (remainingScroll > 0) {
                                const usedForS = Math.min(currentDelta, remainingScroll);
                                scrollOffset.current = initialS + usedForS;

                                scrollViewRef.current?.scrollTo({
                                    y: scrollOffset.current,
                                    animated: false
                                });

                                currentDelta -= usedForS;

                                animatedHeight.setValue(initialH < pivot ? (pivot - initialH) : 0);
                            }
                        }

                        if (currentDelta > 0) {
                            if (isCanFullScreenOnSwipeRef.current) {
                                const totalUsedBefore = (initialH < pivot ? (pivot - initialH) : 0);

                                animatedHeight.setValue(totalUsedBefore + currentDelta);
                            } else {
                                animatedHeight.setValue(initialH < pivot ? (pivot - initialH) : 0);
                            }
                        }
                    }
                } else {
                    let currentDelta = delta;

                    if (initialH > pivot || initialS > 0) {
                        if (initialS > 0) {
                            const usedForS = Math.max(currentDelta, -initialS);
                            scrollOffset.current = initialS + usedForS;

                            scrollViewRef.current?.scrollTo({
                                y: scrollOffset.current,
                                animated: false
                            });

                            currentDelta -= usedForS;

                            animatedHeight.setValue(initialH > pivot ? 0 : pivot - initialH);
                        }

                        if (currentDelta < 0 && initialH > pivot) {
                            const distanceToPivot = pivot - initialH;
                            const usedForH = Math.max(currentDelta, distanceToPivot);

                            animatedHeight.setValue(usedForH);

                            currentDelta -= usedForH;
                        }
                    }

                    if (currentDelta < 0) {
                        animatedHeight.setValue(initialH > pivot ? (pivot - initialH) : 0);
                        animatedTranslateY.setValue(-currentDelta);

                        scrollOffset.current = 0;

                        scrollViewRef.current?.scrollTo({
                            animated: false,
                            y: 0
                        });
                    }
                }
            },
            onPanResponderEnd: (_, gestureState) => {
                if(!isCanSwipeRef.current) return;

                const isAtTop = scrollOffset.current <= 1;
                const isAtTavan = heightValue.current >= maxHeight.current - 1;
                const hasScroll = scrollViewContentHeight.current > scrollViewLayoutHeight.current;

                const isFromTopArea = gestureStartY.current < TOP_GRAB_AREA;

                if (isAtTavan && hasScroll && !isAtTop && !isFromTopArea) {
                    const velocity = gestureState.vy;

                    if (Math.abs(velocity) > 0.1) {
                        const momentum = velocity * -350;

                        const maxS = Math.max(0, scrollViewContentHeight.current - scrollViewLayoutHeight.current);

                        const targetOffset = Math.max(0, Math.min(scrollOffset.current + momentum, maxS));

                        const scrollAnim = new Animated.Value(scrollOffset.current);

                        scrollAnim.addListener(({
                            value
                        }) => {
                            scrollViewRef.current?.scrollTo({
                                animated: false,
                                y: value
                            });
                            scrollOffset.current = value;
                        });

                        Animated.timing(scrollAnim, {
                            duration: Math.min(Math.abs(momentum) * 2, 600),
                            easing: Easing.out(Easing.quad),
                            useNativeDriver: false,
                            toValue: targetOffset
                        }).start(() => {
                            scrollAnim.removeAllListeners();

                            checkScrollThreshold();
                        });
                    } else {
                        checkScrollThreshold();
                    }

                    return;
                }

                const currentH = (animatedHeight as Animated.Value & {
                    __getValue(): number;
                }).__getValue();

                const currentT = (animatedTranslateY as Animated.Value & {
                    __getValue(): number;
                }).__getValue();

                animatedHeight.flattenOffset();
                animatedTranslateY.flattenOffset();

                heightValue.current = currentH;
                translateYValue.current = currentT;

                const pivot = snapPoint ?? contentHeight.current;
                const tavan = maxHeight.current;

                const isFastSwipeDown = gestureState.vy > 0.5;
                const isFastSwipeUp = gestureState.vy < -0.5;

                if (currentT <= Math.max(pivot * 0.2, 60)) {
                    let toValue = pivot;

                    if (isFastSwipeUp && isCanFullScreenOnSwipeRef.current) {
                        toValue = tavan;
                    } else if (isFastSwipeDown) {
                        toValue = pivot;
                    } else {
                        if (!isCanFullScreenOnSwipeRef.current) {
                            toValue = pivot;
                        } else {
                            const totalRange = tavan - pivot;
                            const distFromPivot = currentH - pivot;

                            if (distFromPivot < totalRange * 0.33) {
                                toValue = pivot;
                            } else if (distFromPivot > totalRange * 0.66) {
                                toValue = tavan;
                            } else {
                                toValue = (tavan - currentH) < (currentH - pivot) ? tavan : pivot;
                            }
                        }
                    }

                    Animated.spring(animatedHeight, {
                        useNativeDriver: false,
                        toValue: toValue,
                        friction: 10,
                        tension: 40
                    }).start();

                    Animated.spring(animatedTranslateY, {
                        useNativeDriver: false,
                        friction: 10,
                        tension: 40,
                        toValue: 0
                    }).start();
                } else {
                    let toValueT = 0;
                    let isClosing = false;

                    if (!isSwipeCloseRef.current) {
                        Animated.spring(animatedHeight, {
                            useNativeDriver: false,
                            toValue: pivot,
                            friction: 10,
                            tension: 40
                        }).start();

                        Animated.timing(animatedTranslateY, {
                            useNativeDriver: false,
                            duration: 300,
                            toValue: 0
                        }).start();
                    } else {
                        if (isFastSwipeDown) {
                            toValueT = pivot + 100;

                            isClosing = true;
                        } else {
                            if (currentT > pivot * 0.5) {
                                toValueT = pivot + 100;

                                isClosing = true;
                            } else {
                                toValueT = 0;

                                isClosing = false;
                            }
                        }

                        if(onClose) onClose({
                            id: id.current
                        });

                        Animated.timing(animatedTranslateY, {
                            useNativeDriver: false,
                            toValue: toValueT,
                            duration: 300
                        }).start(({
                            finished
                        }) => {
                            if (finished && isClosing) {
                                setIsActive(false);

                                if (onClosed) onClosed({
                                    id: id.current
                                });
                            }
                        });
                    }
                }
            },
            onMoveShouldSetPanResponderCapture: (_, gestureState) => {
                const {
                    dy
                } = gestureState;

                if(!isCanSwipeRef.current) {
                    return false;
                }

                if(Math.abs(dy) < 20) {
                    return false;
                }

                return true;
            },
            onPanResponderGrant: (evt) => {
                console.log("GRANT", evt.nativeEvent);
                if(!isCanSwipeRef.current) return;

                gestureStartY.current = evt.nativeEvent.pageY;

                animatedTranslateY.stopAnimation((currentY) => {
                    translateYValue.current = currentY;
                });

                animatedHeight.stopAnimation((currentH) => {
                    heightValue.current = currentH;
                });

                animatedTranslateY.flattenOffset();
                animatedHeight.flattenOffset();

                initialTranslateY.current = translateYValue.current;
                initialScrollOffset.current = scrollOffset.current;
                initialHeight.current = heightValue.current;

                animatedHeight.setOffset(heightValue.current);
                animatedHeight.setValue(0);

                animatedTranslateY.setOffset(translateYValue.current);
                animatedTranslateY.setValue(0);
            },
            onPanResponderTerminationRequest: () => false,
            onStartShouldSetPanResponder: () => false,
            onShouldBlockNativeResponder: () => true
        })
    ).current;

    const renderView = () => {
        return <Animated.View
            {...props}
            {...(Platform.OS === "web" ? {} : panResponder.panHandlers)}
            onPointerDown={Platform.OS === "web" ? (evt) => {
                const nativeEvt = evt as unknown as PointerEvent;
                const startY = nativeEvt.clientY;
                gestureStartY.current = nativeEvt.clientY;

                const initialT = translateYValue.current;
                const initialH = heightValue.current;
                const initialS = scrollOffset.current;

                animatedTranslateY.setOffset(initialT);
                animatedTranslateY.setValue(0);

                animatedHeight.setOffset(initialH);
                animatedHeight.setValue(0);

                let lastY = startY;
                let lastTime = Date.now();
                let velocityY = 0;

                const handlePointerMove = (moveEvt: SafePointerEvent) => {
                    if (!isCanSwipeRef.current) return;

                    const dy = moveEvt.clientY - startY;
                    if (Math.abs(dy) < 5) return;

                    const now = Date.now();
                    const dt = now - lastTime;
                    if (dt > 0) {
                        velocityY = (moveEvt.clientY - lastY) / dt;
                    }
                    lastY = moveEvt.clientY;
                    lastTime = now;

                    const delta = -dy;
                    const pivot = snapPoint ?? contentHeight.current;
                    const isAtTavan = heightValue.current >= maxHeight.current - 1;
                    const hasScroll = scrollViewContentHeight.current > scrollViewLayoutHeight.current;
                    const isAtTop = scrollOffset.current <= 0;
                    const isFromTopArea = gestureStartY.current < TOP_GRAB_AREA;
                    const maxS = Math.max(0, scrollViewContentHeight.current - scrollViewLayoutHeight.current);

                    if (dy > 0 && isAtTavan && hasScroll && !isAtTop && !isFromTopArea) {
                        const usedForS = Math.max(-dy, -initialS);
                        scrollOffset.current = initialS + usedForS;
                        scrollViewRef.current?.scrollTo({
                            y: scrollOffset.current,
                            animated: false
                        });
                        return;
                    }

                    if (dy < 0) {
                        let currentDelta = delta;

                        const effectiveInitialT = Math.max(0, initialT);
                        const usedForT = Math.min(currentDelta, effectiveInitialT);
                        animatedTranslateY.setValue(-usedForT);
                        currentDelta -= usedForT;

                        if (currentDelta > 0) {
                            if (initialH < pivot) {
                                const spaceToPivot = pivot - initialH;
                                const usedForH = Math.min(currentDelta, spaceToPivot);
                                animatedHeight.setValue(usedForH);
                                currentDelta -= usedForH;
                            }

                            const isRestoringHeight = initialH < pivot;
                            const canScroll = isCanFullScreenOnSwipeRef.current || isWorkAsFullScreenRef.current || !isRestoringHeight;

                            if (currentDelta > 0 && canScroll) {
                                const remainingScroll = maxS - initialS;
                                if (remainingScroll > 0) {
                                    const usedForS = Math.min(currentDelta, remainingScroll);
                                    scrollOffset.current = initialS + usedForS;
                                    scrollViewRef.current?.scrollTo({
                                        y: scrollOffset.current,
                                        animated: false
                                    });
                                    currentDelta -= usedForS;
                                    animatedHeight.setValue(initialH < pivot ? (pivot - initialH) : 0);
                                }
                            }

                            if (currentDelta > 0) {
                                if (isCanFullScreenOnSwipeRef.current) {
                                    const totalUsedBefore = initialH < pivot ? (pivot - initialH) : 0;
                                    animatedHeight.setValue(totalUsedBefore + currentDelta);
                                } else {
                                    animatedHeight.setValue(initialH < pivot ? (pivot - initialH) : 0);
                                }
                            }
                        }
                    } else {
                        let currentDelta = delta;

                        if (initialH > pivot || initialS > 0) {
                            if (initialS > 0) {
                                const usedForS = Math.max(currentDelta, -initialS);
                                scrollOffset.current = initialS + usedForS;
                                scrollViewRef.current?.scrollTo({
                                    y: scrollOffset.current,
                                    animated: false
                                });
                                currentDelta -= usedForS;
                                animatedHeight.setValue(initialH > pivot ? 0 : pivot - initialH);
                            }

                            if (currentDelta < 0 && initialH > pivot) {
                                const distanceToPivot = pivot - initialH;
                                const usedForH = Math.max(currentDelta, distanceToPivot);
                                animatedHeight.setValue(usedForH);
                                currentDelta -= usedForH;
                            }
                        }

                        if (currentDelta < 0) {
                            animatedHeight.setValue(initialH > pivot ? (pivot - initialH) : 0);
                            animatedTranslateY.setValue(-currentDelta);
                            scrollOffset.current = 0;
                            scrollViewRef.current?.scrollTo({
                                animated: false,
                                y: 0
                            });
                        }
                    }
                };

                const handlePointerUp = () => {
                    safeGlobal.removeEventListener("pointermove", handlePointerMove);
                    safeGlobal.removeEventListener("pointerup", handlePointerUp);

                    animatedHeight.flattenOffset();
                    animatedTranslateY.flattenOffset();

                    const currentT = translateYValue.current;
                    const currentH = heightValue.current;
                    const pivot = snapPoint ?? contentHeight.current;
                    const tavan = maxHeight.current;

                    const isAtTavan = currentH >= tavan - 1;
                    const hasScroll = scrollViewContentHeight.current > scrollViewLayoutHeight.current;
                    const isAtTop = scrollOffset.current <= 1;
                    const isFromTopArea = gestureStartY.current < TOP_GRAB_AREA;

                    if (isAtTavan && hasScroll && !isAtTop && !isFromTopArea) {
                        if (Math.abs(velocityY) > 0.1) {
                            const momentum = velocityY * -350;
                            const maxS = Math.max(0, scrollViewContentHeight.current - scrollViewLayoutHeight.current);
                            const targetOffset = Math.max(0, Math.min(scrollOffset.current + momentum, maxS));

                            const scrollAnim = new Animated.Value(scrollOffset.current);
                            scrollAnim.addListener(({
                                value
                            }) => {
                                scrollViewRef.current?.scrollTo({
                                    animated: false,
                                    y: value
                                });
                                scrollOffset.current = value;
                            });

                            Animated.timing(scrollAnim, {
                                duration: Math.min(Math.abs(momentum) * 2, 600),
                                easing: Easing.out(Easing.quad),
                                toValue: targetOffset,
                                useNativeDriver: false
                            }).start(() => {
                                scrollAnim.removeAllListeners();
                                checkScrollThreshold();
                            });
                        } else {
                            checkScrollThreshold();
                        }
                        return;
                    }

                    const isFastSwipeDown = velocityY > 0.5;
                    const isFastSwipeUp = velocityY < -0.5;

                    if (currentT <= Math.max(pivot * 0.2, 60)) {
                        let toValue = pivot;

                        if (isFastSwipeUp && isCanFullScreenOnSwipeRef.current) {
                            toValue = tavan;
                        } else if (isFastSwipeDown) {
                            toValue = pivot;
                        } else {
                            if (!isCanFullScreenOnSwipeRef.current) {
                                toValue = pivot;
                            } else {
                                const totalRange = tavan - pivot;
                                const distFromPivot = currentH - pivot;

                                if (distFromPivot < totalRange * 0.33) toValue = pivot;
                                else if (distFromPivot > totalRange * 0.66) toValue = tavan;
                                else toValue = (tavan - currentH) < (currentH - pivot) ? tavan : pivot;
                            }
                        }

                        Animated.spring(animatedHeight, {
                            useNativeDriver: false,
                            friction: 10,
                            tension: 40,
                            toValue
                        }).start();
                        Animated.spring(animatedTranslateY, {
                            useNativeDriver: false,
                            friction: 10,
                            tension: 40,
                            toValue: 0
                        }).start();
                    } else {
                        if (!isSwipeCloseRef.current) {
                            Animated.spring(animatedHeight, {
                                useNativeDriver: false,
                                toValue: pivot,
                                friction: 10,
                                tension: 40
                            }).start();
                            Animated.timing(animatedTranslateY, {
                                useNativeDriver: false,
                                duration: 300,
                                toValue: 0
                            }).start();
                        } else {
                            let toValueT = 0;
                            let isClosing = false;

                            if (isFastSwipeDown) {
                                toValueT = pivot + 100;
                                isClosing = true;
                            } else {
                                if (currentT > pivot * 0.5) {
                                    toValueT = pivot + 100;
                                    isClosing = true;
                                } else {
                                    toValueT = 0;
                                    isClosing = false;
                                }
                            }

                            if (onClose) onClose({
                                id: id.current
                            });

                            Animated.timing(animatedTranslateY, {
                                useNativeDriver: false,
                                toValue: toValueT,
                                duration: 300
                            }).start(({
                                finished
                            }) => {
                                if (finished && isClosing) {
                                    setIsActive(false);
                                    if (onClosed) onClosed({
                                        id: id.current
                                    });
                                }
                            });
                        }
                    }
                };

                safeGlobal.addEventListener("pointermove", handlePointerMove);
                safeGlobal.addEventListener("pointerup", handlePointerUp);
            } : undefined}
            style={[
                {
                    borderTopStartRadius: radiuses.lg,
                    borderTopEndRadius: radiuses.lg
                },
                style,
                {
                    height: (isAutoHeight || !snapPoint) && !isMeasured ? "auto" : animatedHeight,
                    backgroundColor: colors.content.container[backgroundColor],
                    paddingBottom: bottomSafeArea + spaces.spacingMd,
                    opacity: isMeasured || !isAutoHeight ? 1 : 0,
                    transform: [
                        {
                            translateY: animatedTranslateY
                        }
                    ],
                    paddingRight: spaces.spacingMd,
                    paddingLeft: spaces.spacingMd,
                    paddingTop: spaces.spacingMd,
                    maxHeight: maxHeight.current
                },
                stylesheet.container,
                webStyle({
                    touchAction: "none",
                    userSelect: "none",
                    cursor: "grab"
                })
            ]}
            onLayout={onLayout}
        >
            {renderHeader()}
            <ScrollView
                {...scrollViewProps}
                onLayout={(e) => {
                    scrollViewLayoutHeight.current = e.nativeEvent.layout.height;
                }}
                style={[
                    scrollViewStyle,
                    webStyle({
                        maxWidth: windowWidth > 500 ? 500 : undefined,
                        width: windowWidth > 500 ? 500 : "100%",
                        alignSelf: "center"
                    })
                ]}
                onStartShouldSetResponderCapture={() => false}
                onMoveShouldSetResponderCapture={() => false}
                onContentSizeChange={(_, h) => {
                    scrollViewContentHeight.current = h;
                }}
                showsHorizontalScrollIndicator={false}
                showsVerticalScrollIndicator={false}
                scrollEnabled={!isCanSwipe}
                scrollEventThrottle={1}
                overScrollMode="never"
                ref={scrollViewRef}
                bounces={false}
            >
                {children}
            </ScrollView>
            {renderBottom()}
            {renderHandle()}
        </Animated.View>;
    };

    const renderHeader = () => {
        if(!RenderHeaderComponent) {
            return null;
        }

        if(Platform.OS === "web") {
            return <View
                style={[
                    webStyle({
                        maxWidth: windowWidth > 500 ? 500 : undefined,
                        width: windowWidth > 500 ? 500 : "100%",
                        alignSelf: "center"
                    })
                ]}
            >
                {RenderHeaderComponent()}
            </View>;
        }

        return RenderHeaderComponent();
    };

    const renderBottom = () => {
        if(!RenderBottomComponent) {
            return null;
        }

        if(Platform.OS === "web") {
            return <View
                style={[
                    webStyle({
                        maxWidth: windowWidth > 500 ? 500 : undefined,
                        width: windowWidth > 500 ? 500 : "100%",
                        alignSelf: "center"
                    })
                ]}
            >
                <RenderBottomComponent/>
            </View>;
        }

        return <RenderBottomComponent/>;
    };

    const renderHandle = () => {
        if(!isShowHandle || isWorkAsFullScreen) {
            return null;
        }

        const handleHeight = handleHeightProp ? handleHeightProp : 8;
        const handleContainerHeight = handleHeight + ((handleContainerSpacing ? spaces[handleContainerSpacing] : spaces.spacingSm) * 2);
        const handleBorderRadius = Math.ceil(handleHeight / 2);

        return <View
            style={[
                stylesheet.handleContainer,
                {
                    transform: [
                        {
                            translateY: -handleContainerHeight * (Platform.OS === "web" ? 1.75 : 1)
                        }
                    ],
                    backgroundColor: handleContainerBackgroundColor
                        ? colors.content.container[handleContainerBackgroundColor]
                        : "transparent",
                    height: handleContainerHeight
                },
                webStyle({
                    touchAction: "none !important",
                    userSelect: "none",
                    cursor: "grab"
                })
            ]}
            onStartShouldSetResponderCapture={() => Platform.OS === "web"}
            onMoveShouldSetResponderCapture={() => Platform.OS === "web"}
        >
            <View
                style={[
                    stylesheet.handle,
                    {
                        backgroundColor: handleBackgroundColor
                            ? colors.content.container[handleBackgroundColor]
                            : colors.content.container.default,
                        borderRadius: handleBorderRadius,
                        width: windowWidth / 10,
                        height: handleHeight
                    }
                ]}
            />
        </View>;
    };

    return <Modal
        {...modalProps}
        onContainerLayout={(e) => {
            const containerLayoutHeight = e.nativeEvent.layout.height;

            if (containerLayoutHeight && containerLayoutHeight !== containerHeightRef.current) {
                containerHeightRef.current = containerLayoutHeight;

                if (!isWorkAsFullScreen && !isCanFullScreenOnSwipe && snapPoint) {
                    maxHeight.current = snapPoint;
                } else if (!isWorkAsFullScreen) {
                    maxHeight.current = containerLayoutHeight - (isForceFullScreenOnSwipe ? 0 : isWrapSafeAreaContext ? topSafeArea : 0);
                } else {
                    maxHeight.current = containerLayoutHeight;
                }
            }
        }}
        portalName={portalName ? portalName : "bottomSheet-system"}
        overlayProps={{
            onStartShouldSetResponderCapture: () => false,
            onMoveShouldSetResponderCapture: () => false
        }}
        onOverlayPress={() => {
            if(onOverlayPressed) onOverlayPressed();

            if(isCloseOnOverlay) {
                closeAnimation();
            }
        }}
        isWorkWithPortal={isWorkWithPortal}
        key={`${bottomSheetKey}-modal`}
        style={[
            {
                paddingTop: topSafeArea
            }
        ]}
        isContentRequired={false}
        isActive={isActive}
        alignContent="free"
        isAnimated={false}
        id={id.current}
        ref={modalRef}
    >
        {renderView()}
    </Modal>;
};
export default forwardRef(BottomSheet);
