import React, {
  createContext,
  useContext,
  useCallback,
  useRef,
  useMemo,
} from "react";
import type { ReactNode } from "react";
import {
  useSharedValue,
  useAnimatedRef,
  type SharedValue,
  type AnimatedRef,
} from "react-native-reanimated";
import type Animated from "react-native-reanimated";
import { scheduleOnUI } from "react-native-worklets";
import type { FlashListRef } from "@shopify/flash-list";
import type { DragConfig } from "../types";
import { DEFAULT_DRAG_CONFIG } from "../constants/drag";

/**
 * Worklet that updates the item index registry on the UI thread.
 * Using scheduleOnUI ensures the SharedValue modification is immediately
 * visible to other worklets (like onStart in useDragGesture).
 */
const updateRegistryOnUI = (
  registry: SharedValue<Record<string, number>>,
  itemId: string,
  index: number,
) => {
  "worklet";
  const existingIndex = registry.value[itemId];
  if (existingIndex === undefined || existingIndex !== index) {
    registry.value = { ...registry.value, [itemId]: index };
  }
};

// In tests or non-worklet environments, scheduleOnUI may be undefined. Provide a safe fallback.
const scheduleOnUIOrImmediate = <T extends unknown[]>(
  fn: (...args: T) => void,
  ...args: T
) => {
  if (typeof scheduleOnUI === "function") {
    scheduleOnUI(fn, ...args);
  } else {
    fn(...args);
  }
};

/**
 * Centralized drag state for coordinating animations across list items.
 * All animation values are Reanimated SharedValues for 60fps UI thread performance.
 *
 * Architecture:
 * - Single source of truth for all drag state (no per-item local state)
 * - Dragged item identified by draggedIndex, reads/writes centralized values
 * - Non-dragged items read draggedIndex/currentTranslateY to calculate shift
 * - Scroll state enables viewport-aware hover calculations and autoscroll
 */
export interface DragStateContextValue {
  /** Is any item currently being dragged? */
  isDragging: SharedValue<boolean>;
  /** Original index of the item being dragged (-1 if not dragging) */
  draggedIndex: SharedValue<number>;
  /** ID of the item being dragged (for stable identity across FlashList recycling) */
  draggedItemId: SharedValue<string>;
  /** Current Y translation of the dragged item (for calculating hover position) */
  currentTranslateY: SharedValue<number>;
  /** Scale of the dragged item (1.0 = normal, configured = dragging) */
  draggedScale: SharedValue<number>;
  /** Current scroll offset of the list (updated via onScroll) */
  scrollOffset: SharedValue<number>;
  /** Scroll offset when drag started (for scroll delta calculation during autoscroll) */
  dragStartScrollOffset: SharedValue<number>;
  /** Total content height of the list (updated via onContentSizeChange) */
  contentHeight: SharedValue<number>;
  /** Visible viewport height (updated via onLayout) */
  visibleHeight: SharedValue<number>;
  /** Y position of FlashList top on screen (for autoscroll coordinate conversion) */
  listTopY: SharedValue<number>;
  /** X position of FlashList left on screen (for overlay positioning) */
  listLeftX: SharedValue<number>;
  /** Measured height of the dragged item (for dynamic height calculations) */
  measuredItemHeight: SharedValue<number>;
  /** Flag to freeze shift values during drop transition */
  isDropping: SharedValue<boolean>;
  /** Counter incremented on each drag start, used to force shift recalculation */
  dragSequence: SharedValue<number>;
  /** Expected index after drop (for handoff detection) */
  expectedDropIndex: SharedValue<number>;
  /** Frozen mismatch offset computed at drop time to prevent double compensation */
  dropMismatchOffset: SharedValue<number>;
  /** Signal incremented when FlashList commits a layout pass */
  layoutCommitSignal: SharedValue<number>;
  /** UI-thread-only signal incremented when native layout commit is detected */
  uiLayoutCommitSignal: SharedValue<number>;
  /** Layout commit signal value captured when drop starts (for guarding original fade-in) */
  dropStartCommitSignal: SharedValue<number>;
  /** Register the FlashList ref for autoscroll operations */
  setListRef: (ref: FlashListRef<unknown> | null) => void;
  /** Scroll the list to a specific offset (for autoscroll during drag) */
  scrollToOffset: (offset: number, animated?: boolean) => void;
  /** Ask FlashList to prepare cells for layout animation on the next render */
  prepareForLayoutAnimationRender: () => void;
  /** Notify drag system that FlashList committed a layout pass */
  notifyLayoutCommit: () => void;
  /** Reset drag state after drop animation completes */
  resetDragState: () => void;
  /** Current drag configuration */
  config: DragConfig;
  /**
   * Index registry for tracking itemId -> index mapping.
   * Updated on every item render to handle FlashList recycling.
   * Stored on UI thread as SharedValue for worklet access.
   */
  itemIndexRegistry: SharedValue<Record<string, number>>;
  /** Update an item's index in the registry (call from JS thread) */
  updateItemIndex: (itemId: string, index: number) => void;
  /** Get an item's index from the registry */
  getItemIndex: (itemId: string) => number | undefined;
  /**
   * Snapshot of itemIndexRegistry taken at drag start.
   * Used during drag to ensure consistent index lookup regardless of
   * React re-renders or FlashList recycling.
   */
  dragStartIndexSnapshot: SharedValue<Record<string, number>>;
  /**
   * Capture a snapshot of the current itemIndexRegistry for use during drag.
   * Call this at the start of a drag operation.
   */
  snapshotRegistryForDrag: () => void;
  /**
   * Update registry after reorder to handle FlashList not re-rendering.
   * Call this when reorder completes to keep indices accurate.
   */
  applyReorderToRegistry: (
    fromId: string,
    fromIndex: number,
    toIndex: number,
  ) => void;
  /** Total number of items in the list (synced from data.length) */
  totalItems: SharedValue<number>;
  /**
   * Signal incremented after reorder callback completes and React has had
   * time to commit. useDragShift watches this to unfreeze shifted items.
   */
  reorderCompleteSignal: SharedValue<number>;
  /** Signal incremented when a drop is ready to reorder */
  reorderRequestSignal: SharedValue<number>;
  /** True while a reorder has been requested and layout commit is pending */
  reorderInFlight: SharedValue<boolean>;
  /** Layout commit version captured when reorder is requested */
  dropCommitVersion: SharedValue<number>;
  /** UI-thread layout commit version captured when reorder is requested */
  dropCommitVersionUI: SharedValue<number>;
  /** Current hovered index while dragging (UI-thread) */
  hoveredIndex: SharedValue<number>;
  /** True while a drag overlay should be shown */
  overlayActive: SharedValue<boolean>;
  /** Item id currently rendered in overlay */
  overlayItemId: SharedValue<string>;
  /** Overlay base X position (relative to list container) */
  overlayBaseX: SharedValue<number>;
  /** Overlay base Y position (relative to list container) */
  overlayBaseY: SharedValue<number>;
  /** Overlay width */
  overlayWidth: SharedValue<number>;
  /** Overlay height */
  overlayHeight: SharedValue<number>;
  /** Overlay translateY (finger translation) */
  overlayTranslateY: SharedValue<number>;
  /** Overlay ready flag (base position measured) */
  overlayReady: SharedValue<boolean>;
  /** Overlay visible flag (JS overlay rendered) */
  overlayVisible: SharedValue<boolean>;
  /** Global layout delta for drop commit timing */
  layoutDeltaY: SharedValue<number>;
  /** Overlay container X position on screen */
  overlayContainerX: SharedValue<number>;
  /** Overlay container Y position on screen */
  overlayContainerY: SharedValue<number>;
  /** Overlay container Y position from layout (measureInWindow value, for shifted overlay calculations) */
  overlayContainerYLayout: SharedValue<number>;
  /** Overlay container measured flag */
  overlayContainerReady: SharedValue<boolean>;
  /** Finger Y offset within the item at drag start (for absolute positioning) */
  overlayTouchOffset: SharedValue<number>;
  /** Current finger absoluteY during drag (for absolute positioning) */
  overlayAbsoluteY: SharedValue<number>;
  /** Animated ref for the drag container (for UI-thread measure()) */
  dragContainerRef: AnimatedRef<Animated.View>;
  /** Mark that a JS reorder callback just executed */
  markReorderToken: () => void;
  /** Read the current reorder token (JS-only) */
  getReorderToken: () => number;
  /**
   * Flag indicating the dragged item's settle animation has completed.
   * Used to know when it's safe to trigger reorder.
   */
  draggedItemSettled: SharedValue<boolean>;
  /**
   * Shifted item overlay positions captured at drop time.
   * Key is itemId, value is visual position info at capture time.
   * Used to render overlay copies that mask the FlashList repositioning glitch.
   */
  shiftedOverlays: SharedValue<
    Record<
      string,
      {
        baseY: number; // Visual Y position at capture (viewport-relative)
        height: number; // Item height
        shift: number; // Shift value at capture (for validation)
        scrollAtCapture: number; // Scroll offset at capture (for scroll tracking)
      }
    >
  >;
  /**
   * Whether shifted item overlays are currently active.
   * When true, real shifted items are hidden and overlays are shown.
   */
  shiftedOverlaysActive: SharedValue<boolean>;
  /**
   * v40.6: Whether shifted item overlays have actually rendered (JS thread confirmation).
   * Only hide real items when this is true to prevent flicker.
   */
  shiftedOverlaysRendered: SharedValue<boolean>;
  /**
   * Counter for atomic unmask - tracks how many items have captured overlays.
   * Overlays are only cleared when this reaches 0 (all items have snapped).
   */
  pendingOverlayCount: SharedValue<number>;
  /**
   * v40.3: Flag indicating reorder has been requested after settle.
   * Phase gate: cleanup requires BOTH reorderRequested=true AND pendingOverlayCount=0.
   * This prevents premature cleanup before reorder actually executes.
   */
  reorderRequested: SharedValue<boolean>;
  /**
   * Crossfade opacity for drag overlay (0..1).
   * Animated on UI thread to create smooth crossfade between overlay and original.
   * Original item uses (1 - dragOverlayOpacity) for perfect crossfade.
   */
  dragOverlayOpacity: SharedValue<number>;
  /**
   * Frozen item ID that keeps overlay renderable during fade-out.
   * Set when fade-out starts, cleared when fade completes.
   * This prevents overlay from unmounting before crossfade finishes.
   */
  overlayFrozenItemId: SharedValue<string>;
  /**
   * UI-thread signal that is set to true when the overlay has actually rendered.
   * Set from useAnimatedStyle in DragOverlay (UI thread), not from useEffect (JS thread).
   * This prevents the original from fading out before the overlay appears.
   */
  overlayRenderedOnUI: SharedValue<boolean>;
}

const DragStateContext = createContext<DragStateContextValue | null>(null);

/**
 * Hook to access shared drag state from context.
 * Must be used within DragStateProvider.
 */
export const useDragState = (): DragStateContextValue => {
  const context = useContext(DragStateContext);
  if (!context) {
    throw new Error("useDragState must be used within DragStateProvider");
  }
  return context;
};

interface DragStateProviderProps {
  children: ReactNode;
  /** Optional drag configuration overrides */
  config?: Partial<DragConfig>;
}

/**
 * Provider that creates shared Reanimated values for drag state.
 *
 * These values are shared across all list items:
 * - The dragged item writes to them during drag gestures
 * - Non-dragged items read them to calculate their shift offset
 * - Scroll state enables viewport-aware hover calculations
 *
 * Using SharedValues ensures animations run on the UI thread at 60fps.
 */
/**
 * Validates drag configuration values to prevent runtime errors.
 * Logs warnings in development for invalid configurations.
 */
function validateConfig(config: DragConfig): void {
  if (__DEV__) {
    if (config.itemHeight <= 0) {
      console.warn(
        "[AnimatedFlashList] Invalid itemHeight: must be positive. Got:",
        config.itemHeight,
      );
    }
    if (config.longPressDuration <= 0) {
      console.warn(
        "[AnimatedFlashList] Invalid longPressDuration: must be positive. Got:",
        config.longPressDuration,
      );
    }
    if (config.dragScale <= 0) {
      console.warn(
        "[AnimatedFlashList] Invalid dragScale: must be positive. Got:",
        config.dragScale,
      );
    }
    if (config.edgeThreshold < 0) {
      console.warn(
        "[AnimatedFlashList] Invalid edgeThreshold: must be non-negative. Got:",
        config.edgeThreshold,
      );
    }
    if (config.maxScrollSpeed <= 0) {
      console.warn(
        "[AnimatedFlashList] Invalid maxScrollSpeed: must be positive. Got:",
        config.maxScrollSpeed,
      );
    }
  }
}

export const DragStateProvider: React.FC<DragStateProviderProps> = ({
  children,
  config: configOverrides,
}) => {
  // Merge config with defaults and validate
  const config = useMemo<DragConfig>(() => {
    const mergedConfig = { ...DEFAULT_DRAG_CONFIG, ...configOverrides };
    validateConfig(mergedConfig);
    return mergedConfig;
  }, [configOverrides]);

  // Shared values are created once and persist for the lifetime of the provider
  const isDragging = useSharedValue(false);
  const draggedIndex = useSharedValue(-1);
  const draggedItemId = useSharedValue("");
  const currentTranslateY = useSharedValue(0);
  const draggedScale = useSharedValue(1);

  // Scroll state for viewport-aware calculations and autoscroll
  const scrollOffset = useSharedValue(0);
  const dragStartScrollOffset = useSharedValue(0);
  const contentHeight = useSharedValue(0);
  const visibleHeight = useSharedValue(0);
  const listTopY = useSharedValue(0);
  const listLeftX = useSharedValue(0);

  // Measured height of dragged item (0 = use fallback from config)
  const measuredItemHeight = useSharedValue(0);

  // Flag to freeze shift values during drop transition
  const isDropping = useSharedValue(false);

  // Counter incremented on each drag start, used to force shift recalculation
  const dragSequence = useSharedValue(0);

  // Expected index after drop (for handoff detection)
  const expectedDropIndex = useSharedValue(-1);

  // Frozen mismatch offset computed at drop time to prevent double compensation
  // This ensures mismatchOffset + currentTranslateY remains constant even if
  // useAnimatedStyle evaluates between registry update and currentTranslateY adjustment
  const dropMismatchOffset = useSharedValue(0);
  const layoutCommitSignal = useSharedValue(0);
  const uiLayoutCommitSignal = useSharedValue(0);
  const dropStartCommitSignal = useSharedValue(0);

  // Index registry for tracking itemId -> index mapping
  // This handles FlashList's recycling behavior where components may not re-render
  // after data changes, leaving their index props stale
  const itemIndexRegistry = useSharedValue<Record<string, number>>({});

  // Snapshot of registry taken at drag start for consistent index lookup during drag
  const dragStartIndexSnapshot = useSharedValue<Record<string, number>>({});

  // Total number of items in the list (synced from data.length by InnerFlashList)
  const totalItems = useSharedValue(0);

  // Signal incremented after reorder callback completes and React has had
  // time to commit. useDragShift watches this to unfreeze shifted items.
  const reorderCompleteSignal = useSharedValue(0);
  // Signal incremented when a drop is ready to reorder (UI thread)
  const reorderRequestSignal = useSharedValue(0);
  const reorderInFlight = useSharedValue(false);
  const dropCommitVersion = useSharedValue(0);
  const dropCommitVersionUI = useSharedValue(0);
  const hoveredIndex = useSharedValue(-1);
  const overlayActive = useSharedValue(false);
  const overlayItemId = useSharedValue("");
  const overlayBaseX = useSharedValue(0);
  const overlayBaseY = useSharedValue(0);
  const overlayWidth = useSharedValue(0);
  const overlayHeight = useSharedValue(0);
  const overlayTranslateY = useSharedValue(0);
  const overlayReady = useSharedValue(false);
  const overlayVisible = useSharedValue(false);
  const layoutDeltaY = useSharedValue(0);
  const overlayContainerX = useSharedValue(0);
  const overlayContainerY = useSharedValue(0);
  const overlayContainerYLayout = useSharedValue(0);
  const overlayContainerReady = useSharedValue(false);
  const overlayTouchOffset = useSharedValue(0);
  const overlayAbsoluteY = useSharedValue(0);
  const dragContainerRef = useAnimatedRef<Animated.View>();
  const reorderTokenRef = useRef(0);
  // Flag indicating dragged item's settle animation has completed
  const draggedItemSettled = useSharedValue(false);
  // Shifted item overlay positions captured at drop time
  const shiftedOverlays = useSharedValue<
    Record<
      string,
      {
        baseY: number;
        height: number;
        shift: number;
        scrollAtCapture: number;
      }
    >
  >({});
  // Whether shifted item overlays are currently active
  const shiftedOverlaysActive = useSharedValue(false);
  // v40.6: Whether shifted item overlays have actually rendered (JS thread confirmation)
  const shiftedOverlaysRendered = useSharedValue(false);
  // Counter for atomic unmask - tracks pending overlay captures
  const pendingOverlayCount = useSharedValue(0);
  // v40.3: Flag indicating reorder has been requested after settle
  const reorderRequested = useSharedValue(false);
  // Crossfade opacity for drag overlay (0 = hidden, 1 = fully visible)
  const dragOverlayOpacity = useSharedValue(0);
  // Frozen item ID to keep overlay renderable during fade-out
  const overlayFrozenItemId = useSharedValue("");
  // UI-thread signal that overlay has actually rendered
  const overlayRenderedOnUI = useSharedValue(false);

  // NOTE: The overlay crossfade reaction has been moved to useOverlayCrossfade hook.
  // It is called from the DragOverlay component for better separation of concerns.
  // The hook only uses UI-thread signals (overlayActive, overlayReady, overlayItemId)
  // and NOT overlayVisible which has JS-thread scheduling delays.

  // Ref to FlashList for autoscroll operations
  const listRef = useRef<FlashListRef<unknown> | null>(null);

  // Register the FlashList ref for autoscroll operations
  const setListRef = useCallback((ref: FlashListRef<unknown> | null) => {
    listRef.current = ref;
  }, []);

  // Scroll to offset (called via scheduleOnRN from worklet for autoscroll)
  const scrollToOffset = useCallback((offset: number, animated = false) => {
    listRef.current?.scrollToOffset({ offset, animated });
  }, []);

  // Request FlashList to prepare cells for layout animation on next render
  const prepareForLayoutAnimationRender = useCallback(() => {
    listRef.current?.prepareForLayoutAnimationRender?.();
  }, []);

  const notifyLayoutCommit = useCallback(() => {
    layoutCommitSignal.value += 1;
  }, [layoutCommitSignal]);

  const markReorderToken = useCallback(() => {
    reorderTokenRef.current += 1;
  }, []);

  const getReorderToken = useCallback(() => {
    return reorderTokenRef.current;
  }, []);

  // Update an item's index in the registry
  // Uses scheduleOnUI to ensure SharedValue modifications are immediately visible
  // to UI thread worklets (fixing the registry sync bug).
  // Uses state-based guards (isDragging/isDropping) to prevent stale React props
  // from overwriting correct indices during active drag/drop operations.
  const updateItemIndex = useCallback(
    (itemId: string, index: number) => {
      // Read current value (may be slightly stale from JS thread, but OK for guard logic)
      const existingIndex = itemIndexRegistry.value[itemId];

      // Add new items always - schedule on UI thread for immediate visibility (or synchronous fallback in tests)
      if (existingIndex === undefined) {
        scheduleOnUIOrImmediate(
          updateRegistryOnUI,
          itemIndexRegistry,
          itemId,
          index,
        );
        return;
      }

      // Skip updates during active DRAG (not drop!)
      // During drag: stale React props could corrupt indices
      // During drop: we NEED updates to detect when FlashList repositions items
      // This is critical for flicker-free drop compensation
      if (isDragging.value) {
        return;
      }

      // Outside active drag/drop, update if value changed - schedule on UI thread
      if (existingIndex !== index) {
        scheduleOnUIOrImmediate(
          updateRegistryOnUI,
          itemIndexRegistry,
          itemId,
          index,
        );
      }
    },
    [itemIndexRegistry, isDragging],
  );

  // Get an item's index from the registry
  const getItemIndex = useCallback(
    (itemId: string): number | undefined => {
      return itemIndexRegistry.value[itemId];
    },
    [itemIndexRegistry],
  );

  // Capture a snapshot of the registry at drag start
  // This ensures consistent index lookup during the entire drag operation,
  // regardless of React re-renders or FlashList recycling
  const snapshotRegistryForDrag = useCallback(() => {
    dragStartIndexSnapshot.value = { ...itemIndexRegistry.value };
  }, [dragStartIndexSnapshot, itemIndexRegistry]);

  // Update registry after reorder to handle FlashList not re-rendering
  const applyReorderToRegistry = useCallback(
    (fromId: string, fromIndex: number, toIndex: number) => {
      const registry = itemIndexRegistry.value;
      const newRegistry = { ...registry };

      if (toIndex > fromIndex) {
        // Moving down: items between from and to shift UP (index decreases)
        for (const [id, idx] of Object.entries(registry)) {
          if (idx > fromIndex && idx <= toIndex) {
            newRegistry[id] = idx - 1;
          }
        }
      } else if (toIndex < fromIndex) {
        // Moving up: items between to and from shift DOWN (index increases)
        for (const [id, idx] of Object.entries(registry)) {
          if (idx >= toIndex && idx < fromIndex) {
            newRegistry[id] = idx + 1;
          }
        }
      }
      newRegistry[fromId] = toIndex;

      itemIndexRegistry.value = newRegistry;
    },
    [itemIndexRegistry],
  );

  // Reset drag state after drop
  // Note: We intentionally do NOT clear itemIndexRegistry here.
  // The registry persists across drags and is updated by:
  // 1. applyReorderToRegistry() in useDragGesture.onEnd after each reorder
  // 2. updateItemIndex() when items render
  // Clearing it would cause subsequent drags to fall back to stale React props.
  const resetDragState = useCallback(() => {
    isDragging.value = false;
    draggedIndex.value = -1;
    draggedItemId.value = "";
    currentTranslateY.value = 0;
    draggedScale.value = 1;
    dragStartScrollOffset.value = 0;
    measuredItemHeight.value = 0;
    isDropping.value = false;
    expectedDropIndex.value = -1;
    dropMismatchOffset.value = 0;
    reorderInFlight.value = false;
    dropCommitVersion.value = 0;
    dropCommitVersionUI.value = 0;
    hoveredIndex.value = -1;
    overlayActive.value = false;
    overlayItemId.value = "";
    overlayBaseX.value = 0;
    overlayBaseY.value = 0;
    overlayWidth.value = 0;
    overlayHeight.value = 0;
    overlayTranslateY.value = 0;
    overlayReady.value = false;
    overlayVisible.value = false;
    layoutDeltaY.value = 0;
    overlayContainerX.value = 0;
    overlayContainerY.value = 0;
    overlayContainerYLayout.value = 0;
    overlayContainerReady.value = false;
    overlayTouchOffset.value = 0;
    overlayAbsoluteY.value = 0;
    draggedItemSettled.value = false;
    shiftedOverlays.value = {};
    shiftedOverlaysActive.value = false;
    shiftedOverlaysRendered.value = false;
    pendingOverlayCount.value = 0;
    reorderRequested.value = false;
    dragOverlayOpacity.value = 0;
    overlayFrozenItemId.value = "";
    overlayRenderedOnUI.value = false;
    dropStartCommitSignal.value = 0;
  }, [
    isDragging,
    draggedIndex,
    draggedItemId,
    currentTranslateY,
    draggedScale,
    dragStartScrollOffset,
    measuredItemHeight,
    isDropping,
    expectedDropIndex,
    dropMismatchOffset,
    reorderInFlight,
    dropCommitVersion,
    dropCommitVersionUI,
    hoveredIndex,
    overlayActive,
    overlayItemId,
    overlayBaseX,
    overlayBaseY,
    overlayWidth,
    overlayHeight,
    overlayTranslateY,
    overlayReady,
    overlayVisible,
    layoutDeltaY,
    overlayContainerX,
    overlayContainerY,
    overlayContainerYLayout,
    overlayContainerReady,
    overlayTouchOffset,
    overlayAbsoluteY,
    draggedItemSettled,
    shiftedOverlays,
    shiftedOverlaysActive,
    shiftedOverlaysRendered,
    pendingOverlayCount,
    reorderRequested,
    dragOverlayOpacity,
    overlayFrozenItemId,
    overlayRenderedOnUI,
    dropStartCommitSignal,
  ]);

  // Context value is stable since SharedValue references don't change
  const value = useMemo<DragStateContextValue>(
    () => ({
      isDragging,
      draggedIndex,
      draggedItemId,
      currentTranslateY,
      draggedScale,
      scrollOffset,
      dragStartScrollOffset,
      contentHeight,
      visibleHeight,
      listTopY,
      listLeftX,
      measuredItemHeight,
      isDropping,
      dragSequence,
      expectedDropIndex,
      dropMismatchOffset,
      layoutCommitSignal,
      uiLayoutCommitSignal,
      setListRef,
      scrollToOffset,
      prepareForLayoutAnimationRender,
      notifyLayoutCommit,
      resetDragState,
      config,
      itemIndexRegistry,
      updateItemIndex,
      getItemIndex,
      dragStartIndexSnapshot,
      snapshotRegistryForDrag,
      applyReorderToRegistry,
      totalItems,
      reorderCompleteSignal,
      reorderRequestSignal,
      reorderInFlight,
      dropCommitVersion,
      dropCommitVersionUI,
      hoveredIndex,
      overlayActive,
      overlayItemId,
      overlayBaseX,
      overlayBaseY,
      overlayWidth,
      overlayHeight,
      overlayTranslateY,
      overlayReady,
      overlayVisible,
      layoutDeltaY,
      overlayContainerX,
      overlayContainerY,
      overlayContainerYLayout,
      overlayContainerReady,
      overlayTouchOffset,
      overlayAbsoluteY,
      dragContainerRef,
      markReorderToken,
      getReorderToken,
      draggedItemSettled,
      shiftedOverlays,
      shiftedOverlaysActive,
      shiftedOverlaysRendered,
      pendingOverlayCount,
      reorderRequested,
      dragOverlayOpacity,
      overlayFrozenItemId,
      overlayRenderedOnUI,
      dropStartCommitSignal,
    }),
    [
      isDragging,
      draggedIndex,
      draggedItemId,
      currentTranslateY,
      draggedScale,
      scrollOffset,
      dragStartScrollOffset,
      contentHeight,
      visibleHeight,
      listTopY,
      listLeftX,
      measuredItemHeight,
      isDropping,
      dragSequence,
      expectedDropIndex,
      dropMismatchOffset,
      layoutCommitSignal,
      uiLayoutCommitSignal,
      setListRef,
      scrollToOffset,
      prepareForLayoutAnimationRender,
      notifyLayoutCommit,
      resetDragState,
      config,
      itemIndexRegistry,
      updateItemIndex,
      getItemIndex,
      dragStartIndexSnapshot,
      snapshotRegistryForDrag,
      applyReorderToRegistry,
      totalItems,
      reorderCompleteSignal,
      reorderRequestSignal,
      reorderInFlight,
      dropCommitVersion,
      dropCommitVersionUI,
      hoveredIndex,
      overlayActive,
      overlayItemId,
      overlayBaseX,
      overlayBaseY,
      overlayWidth,
      overlayHeight,
      overlayTranslateY,
      overlayReady,
      overlayVisible,
      layoutDeltaY,
      overlayContainerX,
      overlayContainerY,
      overlayContainerYLayout,
      overlayContainerReady,
      overlayTouchOffset,
      overlayAbsoluteY,
      dragContainerRef,
      markReorderToken,
      getReorderToken,
      draggedItemSettled,
      shiftedOverlays,
      shiftedOverlaysActive,
      shiftedOverlaysRendered,
      pendingOverlayCount,
      reorderRequested,
      dragOverlayOpacity,
      overlayFrozenItemId,
      overlayRenderedOnUI,
      dropStartCommitSignal,
    ],
  );

  return (
    <DragStateContext.Provider value={value}>
      {children}
    </DragStateContext.Provider>
  );
};
