import React, {
  createContext,
  useContext,
  useCallback,
  useRef,
  useMemo,
  useEffect,
  type ReactNode,
} from 'react';
import { LayoutAnimation, Platform } from 'react-native';
import { useSharedValue, type SharedValue } from 'react-native-reanimated';
import type {
  AnimationDirection,
  ExitAnimationTrigger,
  PendingEntryAnimation,
  ExitingItemInfo,
} from '../types';

/**
 * Context value for coordinating entry/exit animations across list items.
 *
 * This enables:
 * 1. Direct O(1) animation triggers from subscription handlers
 * 2. Entry animation coordination when items appear in new locations
 * 3. Layout animation coordination when items are removed
 * 4. Decoupled animation state from React render cycle
 */
export interface ListAnimationContextValue {
  /**
   * Register an exit animation trigger for an item.
   * Called by useListExitAnimation on mount.
   */
  registerAnimationTrigger: (itemId: string, trigger: ExitAnimationTrigger) => void;

  /**
   * Unregister an exit animation trigger.
   * Called by useListExitAnimation on unmount.
   */
  unregisterAnimationTrigger: (itemId: string) => void;

  /**
   * Trigger exit animation for a specific item.
   * Returns true if animation was triggered, false if item not found.
   */
  triggerExitAnimation: (
    itemId: string,
    direction: AnimationDirection,
    onComplete: () => void,
  ) => boolean;

  /**
   * Queue an entry animation for an item that's about to appear.
   * Call this before the item is added to the list.
   */
  queueEntryAnimation: (itemId: string, direction: AnimationDirection) => void;

  /**
   * Check and claim a pending entry animation for an item.
   * Returns the animation info if found, null otherwise.
   * Calling this consumes the pending animation.
   */
  claimEntryAnimation: (itemId: string) => PendingEntryAnimation | null;

  /**
   * Register an item that is currently exiting (for layout compensation).
   * Called when exit animation starts.
   */
  registerExitingItem: (itemId: string, index: number, height: number) => void;

  /**
   * Unregister an exiting item (called when removal is complete).
   */
  unregisterExitingItem: (itemId: string) => void;

  /**
   * Get info about items currently exiting above a given index.
   * Returns the total height of exiting items above.
   */
  getExitingHeightAbove: (index: number) => number;

  /**
   * SharedValue that increments when exiting items change.
   * Used to trigger useDerivedValue re-evaluation.
   */
  exitingItemsVersion: SharedValue<number>;

  /**
   * Duration for layout animations (ms).
   */
  layoutAnimationDuration: number;
}

const ListAnimationContext = createContext<ListAnimationContextValue | null>(null);

/**
 * Hook to access list animation context.
 * Throws if used outside ListAnimationProvider.
 */
export const useListAnimation = (): ListAnimationContextValue => {
  const context = useContext(ListAnimationContext);
  if (!context) {
    throw new Error('useListAnimation must be used within ListAnimationProvider');
  }
  return context;
};

/**
 * Hook to optionally access list animation context.
 * Returns null if used outside ListAnimationProvider.
 * Useful for components that can work with or without animations.
 */
export const useListAnimationOptional = (): ListAnimationContextValue | null => {
  return useContext(ListAnimationContext);
};

interface ListAnimationProviderProps {
  children: ReactNode;
  /**
   * How long pending entry animations are valid (ms)
   * @default 5000
   */
  entryAnimationTimeout?: number;
  /**
   * Duration for layout animations when items are removed (ms).
   * This controls how fast remaining items animate into their new positions
   * after an item is removed from the list.
   * Lower values = faster/snappier, higher values = smoother/more gradual
   * @default 300
   */
  layoutAnimationDuration?: number;
  /**
   * Whether to enable native LayoutAnimation for remaining items
   * @default true
   */
  enableLayoutAnimation?: boolean;
}

/**
 * Provider for coordinating entry/exit animations across list items.
 *
 * Features:
 * - O(1) exit animation triggers via Map lookup
 * - Entry animation queuing with automatic expiration
 * - Layout animation coordination when items are removed
 * - Decoupled from React render cycle for performance
 */
export const ListAnimationProvider: React.FC<ListAnimationProviderProps> = ({
  children,
  entryAnimationTimeout = 5000,
  layoutAnimationDuration = 300,
  enableLayoutAnimation = true,
}) => {
  // Map of itemId -> exit animation trigger function
  const animationTriggersRef = useRef<Map<string, ExitAnimationTrigger>>(new Map());

  // Map of itemId -> pending entry animation
  const pendingEntriesRef = useRef<Map<string, PendingEntryAnimation>>(new Map());

  // Map of itemId -> exiting item info (for layout compensation)
  const exitingItemsRef = useRef<Map<string, ExitingItemInfo>>(new Map());

  // Map of itemId -> timeout ID for cleanup tracking (prevents memory leaks)
  const timeoutIdsRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());

  // SharedValue to trigger re-evaluation when exiting items change
  const exitingItemsVersion = useSharedValue(0);

  const registerAnimationTrigger = useCallback(
    (itemId: string, trigger: ExitAnimationTrigger) => {
      animationTriggersRef.current.set(itemId, trigger);
    },
    [],
  );

  const unregisterAnimationTrigger = useCallback((itemId: string) => {
    animationTriggersRef.current.delete(itemId);
  }, []);

  const triggerExitAnimation = useCallback(
    (
      itemId: string,
      direction: AnimationDirection,
      onComplete: () => void,
    ): boolean => {
      const trigger = animationTriggersRef.current.get(itemId);
      if (trigger) {
        trigger(direction, onComplete);
        return true;
      }
      return false;
    },
    [],
  );

  const queueEntryAnimation = useCallback(
    (itemId: string, direction: AnimationDirection) => {
      const entry: PendingEntryAnimation = {
        itemId,
        direction,
        timestamp: Date.now(),
      };
      pendingEntriesRef.current.set(itemId, entry);

      // Clear any existing timeout for this itemId to prevent duplicates
      const existingTimeout = timeoutIdsRef.current.get(itemId);
      if (existingTimeout) {
        clearTimeout(existingTimeout);
      }

      // Auto-expire after timeout (tracked for cleanup on unmount)
      const timeoutId = setTimeout(() => {
        const current = pendingEntriesRef.current.get(itemId);
        if (current && current.timestamp === entry.timestamp) {
          pendingEntriesRef.current.delete(itemId);
        }
        timeoutIdsRef.current.delete(itemId);
      }, entryAnimationTimeout);

      timeoutIdsRef.current.set(itemId, timeoutId);
    },
    [entryAnimationTimeout],
  );

  const claimEntryAnimation = useCallback(
    (itemId: string): PendingEntryAnimation | null => {
      const entry = pendingEntriesRef.current.get(itemId);
      if (entry) {
        pendingEntriesRef.current.delete(itemId);
        // Clear the auto-expire timeout since we're claiming it now
        const timeoutId = timeoutIdsRef.current.get(itemId);
        if (timeoutId) {
          clearTimeout(timeoutId);
          timeoutIdsRef.current.delete(itemId);
        }
        // Check if still valid (not expired)
        if (Date.now() - entry.timestamp < entryAnimationTimeout) {
          return entry;
        }
      }
      return null;
    },
    [entryAnimationTimeout],
  );

  // Register an exiting item for layout compensation
  const registerExitingItem = useCallback(
    (itemId: string, index: number, height: number) => {
      const info: ExitingItemInfo = {
        itemId,
        index,
        height,
        timestamp: Date.now(),
      };
      exitingItemsRef.current.set(itemId, info);
      exitingItemsVersion.value = exitingItemsVersion.value + 1;

      // Configure native LayoutAnimation for remaining items
      if (enableLayoutAnimation) {
        // Use spring animation for smoother, more natural item transitions
        // Spring animations better handle position changes than timed animations
        // and provide a more "organic" feel to remaining items moving up
        LayoutAnimation.configureNext(
          LayoutAnimation.create(
            layoutAnimationDuration,
            LayoutAnimation.Types.easeOut,
            // Use opacity as the trigger property - this works better for
            // position-based animations than scaleY which can cause visual artifacts
            LayoutAnimation.Properties.opacity,
          ),
        );
      }
    },
    [enableLayoutAnimation, layoutAnimationDuration, exitingItemsVersion],
  );

  // Unregister an exiting item
  const unregisterExitingItem = useCallback(
    (itemId: string) => {
      exitingItemsRef.current.delete(itemId);
      exitingItemsVersion.value = exitingItemsVersion.value + 1;
    },
    [exitingItemsVersion],
  );

  // Get total height of exiting items above a given index
  const getExitingHeightAbove = useCallback((index: number): number => {
    let totalHeight = 0;
    exitingItemsRef.current.forEach(info => {
      if (info.index < index) {
        totalHeight += info.height;
      }
    });
    return totalHeight;
  }, []);

  const value = useMemo<ListAnimationContextValue>(
    () => ({
      registerAnimationTrigger,
      unregisterAnimationTrigger,
      triggerExitAnimation,
      queueEntryAnimation,
      claimEntryAnimation,
      registerExitingItem,
      unregisterExitingItem,
      getExitingHeightAbove,
      exitingItemsVersion,
      layoutAnimationDuration,
    }),
    [
      registerAnimationTrigger,
      unregisterAnimationTrigger,
      triggerExitAnimation,
      queueEntryAnimation,
      claimEntryAnimation,
      registerExitingItem,
      unregisterExitingItem,
      getExitingHeightAbove,
      exitingItemsVersion,
      layoutAnimationDuration,
    ],
  );

  // Cleanup all Maps and timers on unmount to prevent memory leaks
  useEffect(() => {
    // Capture ref values at setup time to satisfy react-hooks/exhaustive-deps
    // This ensures cleanup uses the same Map instances that were set up
    const timeouts = timeoutIdsRef.current;
    const triggers = animationTriggersRef.current;
    const pending = pendingEntriesRef.current;
    const exiting = exitingItemsRef.current;

    return () => {
      // Clear all pending timeouts
      timeouts.forEach(timeoutId => clearTimeout(timeoutId));
      timeouts.clear();
      // Clear all Maps
      triggers.clear();
      pending.clear();
      exiting.clear();
    };
  }, []);

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