import React, { useRef } from 'react';
import { PanResponder, ScrollView, View } from 'react-native';
import type {
  NativeScrollEvent,
  NativeSyntheticEvent,
  ScrollViewProps,
} from 'react-native';
import type { ReactElement } from 'react';

import { useDragableDrawerContext } from './DragableDrawerContext';

export type DragableScrollViewProps = ScrollViewProps;

// scrollY tolerance: treat anything within this many pixels of the top as
// "at scroll top" to handle fractional pixels and throttled scroll events.
const SCROLL_TOP_THRESHOLD_PX = 5;
// Minimum downward finger movement before the pull-down collapse is triggered.
const PULL_DOWN_THRESHOLD_PX = 10;
const DEFAULT_SCROLL_EVENT_THROTTLE = 16;

/**
 * A ScrollView that coordinates with a parent DragableDrawer.
 *
 * - Drawer below max height: all gestures move the drawer; scroll is locked.
 * - Drawer at max height: native scrolling works normally.
 * - Drawer at max + scrolled to top + pull down: drawer collapses.
 *
 * The native ScrollView with bounces={false}/overScrollMode="never" will not
 * intercept a downward gesture at scrollY=0 (nothing left to scroll), so
 * onMoveShouldSetPanResponderCapture fires cleanly on both platforms.
 * onScrollEndDrag + onMomentumScrollEnd ensure scrollYRef is up-to-date
 * before the next gesture starts.
 */
const DragableScrollView = ({
  onScroll,
  onScrollEndDrag: onScrollEndDragProp,
  onMomentumScrollEnd: onMomentumScrollEndProp,
  scrollEventThrottle = DEFAULT_SCROLL_EVENT_THROTTLE,
  children,
  ...props
}: DragableScrollViewProps): ReactElement => {
  const {
    isAtMaxHeight,
    scrollYRef,
    onScrollY,
    beginPan,
    movePan,
    releasePan,
  } = useDragableDrawerContext();

  // Mirror context value into a ref so PanResponder closures always read
  // the latest value (PanResponder is created once and closures are frozen).
  const isAtMaxHeightRef = useRef(isAtMaxHeight);
  isAtMaxHeightRef.current = isAtMaxHeight;

  const panResponder = useRef(
    PanResponder.create({
      // Drawer below max: capture on start so the native ScrollView never
      // gets the touch and scroll is completely locked.
      onStartShouldSetPanResponderCapture: () => !isAtMaxHeightRef.current,

      // Drawer at max + scroll at top + pull down: recapture for collapse.
      // bounces={false} / overScrollMode="never" means the native ScrollView
      // has nothing to do here, so it will not intercept the gesture and this
      // capture fires reliably before any scroll activity begins.
      onMoveShouldSetPanResponderCapture: (_, gesture) =>
        isAtMaxHeightRef.current &&
        scrollYRef.current <= SCROLL_TOP_THRESHOLD_PX &&
        gesture.dy > PULL_DOWN_THRESHOLD_PX,

      onPanResponderGrant: () => beginPan(),
      onPanResponderMove: (_, gesture) => movePan(gesture.dy),
      onPanResponderRelease: (_, gesture) => releasePan(gesture.dy, gesture.vy),
    })
  ).current;

  const handleScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
    onScrollY(e.nativeEvent.contentOffset.y);
    onScroll?.(e);
  };

  // Keep scrollYRef accurate at every scroll boundary so the next gesture
  // checks the correct position even when onScroll is throttled.
  const handleScrollEndDrag = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
    onScrollY(e.nativeEvent.contentOffset.y);
    onScrollEndDragProp?.(e);
  };

  const handleMomentumScrollEnd = (
    e: NativeSyntheticEvent<NativeScrollEvent>
  ) => {
    onScrollY(e.nativeEvent.contentOffset.y);
    onMomentumScrollEndProp?.(e);
  };

  return (
    <View {...panResponder.panHandlers} style={{ flex: 1 }}>
      <ScrollView
        {...props}
        scrollEnabled={isAtMaxHeight}
        onScroll={handleScroll}
        onScrollEndDrag={handleScrollEndDrag}
        onMomentumScrollEnd={handleMomentumScrollEnd}
        scrollEventThrottle={scrollEventThrottle}
        overScrollMode="always"
      >
        {children}
      </ScrollView>
    </View>
  );
};

export default DragableScrollView;
