import React, { useLayoutEffect, useMemo, useCallback, useRef, useEffect } from 'react';
import Animated, { useAnimatedRef, useSharedValue } from 'react-native-reanimated';
import type { LayoutChangeEvent } from 'react-native';
import { useDragGesture } from './hooks/drag/useDragGesture';
import { useDragShift } from './hooks/drag/useDragShift';
import { useDragAnimatedStyle } from './hooks/drag/useDragAnimatedStyle';
import { useDropCompensation } from './hooks/drag/useDropCompensation';
import { useListExitAnimation } from './hooks/animations/useListExitAnimation';
import { useListEntryAnimation } from './hooks/animations/useListEntryAnimation';
import { useListAnimationOptional } from './contexts/ListAnimationContext';
import { useDragState } from './contexts/DragStateContext';
import type {
  AnimatedListItem,
  AnimatedRenderItemInfo,
  HapticFeedbackType,
} from './types';

interface AnimatedFlashListItemProps<T extends AnimatedListItem> {
  /** The item data */
  item: T;
  /** Current index in list */
  index: number;
  /** Total number of items */
  totalItems: number;
  /** Whether drag is enabled for this item */
  isDragEnabled: boolean;
  /** Render function from parent */
  renderItem: (info: AnimatedRenderItemInfo<T>) => React.ReactElement;
  /** Callback when reorder occurs */
  onReorderByDelta?: (itemId: string, delta: number) => void;
  /** Optional haptic feedback callback */
  onHapticFeedback?: (type: HapticFeedbackType) => void;
}

/**
 * Internal item wrapper that provides all animation functionality.
 *
 * This component:
 * 1. Sets up drag gesture and shift animations
 * 2. Sets up entry/exit animations
 * 3. Combines all animated styles
 * 4. Passes everything to the consumer's renderItem function
 *
 * @internal
 */
function AnimatedFlashListItemInner<T extends AnimatedListItem>({
  item,
  index,
  totalItems,
  isDragEnabled,
  renderItem,
  onReorderByDelta,
  onHapticFeedback,
}: AnimatedFlashListItemProps<T>): React.ReactElement | null {
  // Create shared value for index (for UI-thread access in animations)
  // This allows worklets to read the current index without closure stale capture
  const indexShared = useSharedValue(index);

  // Register this item's index in the central registry
  // This handles FlashList's recycling behavior where components may not re-render
  // after data changes, leaving their index props stale
  const {
    updateItemIndex,
    isDragging: globalIsDragging,
    isDropping,
  } = useDragState();

  // Sync index to SharedValue on every render, but skip during active drag/drop
  // This prevents stale React props from corrupting the index during the drop phase
  // when FlashList re-renders with new data order
  useLayoutEffect(() => {
    // Don't update during active drag/drop to prevent race conditions
    // The registry handles correct index lookup during these phases
    if (globalIsDragging.value || isDropping.value) {
      return;
    }
    indexShared.value = index;
  }, [index, indexShared, globalIsDragging, isDropping]);

  // Secondary sync: Catch the case where React re-rendered with new index during drop phase.
  // The primary useLayoutEffect bails out during drop (guards=true), and when guards are
  // released, it doesn't re-run because `index` didn't change. This effect forces the sync
  // on every render when guards are down and there's a mismatch.
  //
  // This effect intentionally has NO dependency array. Adding deps would defeat its purpose:
  // it needs to run on EVERY render to catch the window where guards drop but `index` hasn't
  // changed (so the primary useLayoutEffect won't re-fire). The body is lightweight (three
  // SharedValue reads + one conditional write) so the per-render cost is negligible.
   
  useEffect(() => {
    if (globalIsDragging.value || isDropping.value || indexShared.value === index) return;
    indexShared.value = index;
  });

  // Update item index in useLayoutEffect to avoid side effects during render
  // useLayoutEffect runs synchronously after DOM mutations but before paint,
  // ensuring the registry is up-to-date before any visual updates
  useLayoutEffect(() => {
    updateItemIndex(item.id, index);
  }, [updateItemIndex, item.id, index]);

  // Animated ref for measuring item height on drag start
  const containerRef = useAnimatedRef<Animated.View>();

  // Track measured height for layout compensation
  const measuredHeightRef = useRef<number>(0);

  // List animation context for subscription-triggered animations and layout compensation
  const animationContext = useListAnimationOptional();

  // === DRAG HOOKS ===

  // Pan gesture for drag-to-reorder
  // Note: totalItems is now accessed via SharedValue from DragStateContext
  const { panGesture, isDragging, translateY } = useDragGesture(
    {
      itemId: item.id,
      index,
      enabled: isDragEnabled,
      containerRef,
    },
    {
      onReorderByDelta,
      onHapticFeedback,
    },
  );

  // Shift animation for non-dragged items
  const { shiftY } = useDragShift({ itemId: item.id, index, dragEnabled: isDragEnabled });

  // Handle index changes after cache updates (drop compensation)
  useDropCompensation({ itemId: item.id, index, translateY, dragEnabled: isDragEnabled });

  // Animated style for drag transforms
  const { dragAnimatedStyle } = useDragAnimatedStyle(
    item.id,
    isDragging,
    translateY,
    shiftY,
    containerRef,
    isDragEnabled,
  );

  // === ANIMATION HOOKS ===

  // Callbacks for layout compensation (register/unregister exiting items)
  const onExitStart = useCallback(
    (exitIndex: number, height: number) => {
      animationContext?.registerExitingItem(item.id, exitIndex, height);
    },
    [animationContext, item.id],
  );

  const onExitComplete = useCallback(() => {
    animationContext?.unregisterExitingItem(item.id);
  }, [animationContext, item.id]);

  // Exit animation for smooth slide-out (with layout compensation callbacks)
  const { exitAnimatedStyle, triggerExit, resetAnimation } =
    useListExitAnimation(item.id, {
      index,
      measuredHeight: measuredHeightRef.current,
      onExitStart,
      onExitComplete,
    });

  // Entry animation for items appearing
  const { entryAnimatedStyle } = useListEntryAnimation(item.id);

  // Register exit animation trigger (O(1) direct calls from subscriptions)
  useLayoutEffect(() => {
    if (!animationContext) return;
    animationContext.registerAnimationTrigger(item.id, triggerExit);
    return () => animationContext.unregisterAnimationTrigger(item.id);
  }, [item.id, triggerExit, animationContext]);

  // Track measured height for layout compensation
  const handleLayout = useCallback((event: LayoutChangeEvent) => {
    const { height } = event.nativeEvent.layout;
    measuredHeightRef.current = height;
  }, []);

  // Create drag handle props
  const dragHandleProps = useMemo(
    () =>
      isDragEnabled
        ? {
            gesture: panGesture,
            isDragging,
          }
        : null,
    [isDragEnabled, panGesture, isDragging],
  );

  // Trigger exit animation wrapper
  const triggerExitAnimation = useCallback(
    (
      direction: 1 | -1,
      onComplete: () => void,
      preset?: 'default' | 'fast',
    ) => {
      triggerExit(direction, onComplete, preset);
    },
    [triggerExit],
  );

  // Create render info
  // Note: animatedStyle is empty because animations are applied to the wrapper View automatically.
  // This field is kept for backward compatibility with the AnimatedRenderItemInfo interface.
  const renderInfo = useMemo<AnimatedRenderItemInfo<T>>(
    () => ({
      item,
      index,
      totalItems,
      animatedStyle: {},
      dragHandleProps,
      isDragging: false, // This is a SharedValue, consumer should use dragHandleProps.isDragging
      isDragEnabled,
      triggerExitAnimation,
      resetExitAnimation: resetAnimation,
    }),
    [
      item,
      index,
      totalItems,
      dragHandleProps,
      isDragEnabled,
      triggerExitAnimation,
      resetAnimation,
    ],
  );

  // Render the item with animations applied
  // The consumer's renderItem gets wrapped in our animated container
  const renderedItem = renderItem(renderInfo);

  return (
    <Animated.View
      ref={containerRef}
      onLayout={handleLayout}
      style={[
        exitAnimatedStyle,
        entryAnimatedStyle,
        isDragEnabled && dragAnimatedStyle,
      ]}
    >
      {renderedItem}
    </Animated.View>
  );
}

// Memoize to prevent unnecessary re-renders
export const AnimatedFlashListItem = React.memo(
  AnimatedFlashListItemInner,
) as typeof AnimatedFlashListItemInner;
