/**
 * Optimistic Updates Utility
 * FE-API-018: Utilities for optimistic UI updates
 * 
 * Provides helpers for implementing optimistic updates in React components
 * with automatic rollback on error
 */

import { QueryClient } from '@tanstack/react-query';

/**
 * Options for optimistic update
 */
export interface OptimisticUpdateOptions<TData = any, TVariables = any> {
  /** QueryClient instance */
  queryClient: QueryClient;
  /** Query keys to update optimistically */
  queryKeys: (string | number)[][];
  /** Function to generate optimistic data from variables */
  optimisticData: (variables: TVariables) => TData;
  /** Function to update query data */
  updateQueryData?: (
    oldData: any,
    variables: TVariables,
    queryClient: QueryClient,
  ) => any;
  /** Function to rollback on error */
  rollback?: (variables: TVariables, queryClient: QueryClient) => void;
}

/**
 * Creates an optimistic update configuration for useMutation
 * 
 * @example
 * ```typescript
 * const queryClient = useQueryClient();
 * const mutation = useMutation({
 *   mutationFn: updatePlaylist,
 *   ...createOptimisticUpdate({
 *     queryClient,
 *     queryKeys: [['playlist', playlistId], ['playlists']],
 *     optimisticData: (variables) => ({
 *       ...currentPlaylist,
 *       ...variables,
 *     }),
 *   }),
 * });
 * ```
 */
export function createOptimisticUpdate<TData = any, TVariables = any>(
  options: OptimisticUpdateOptions<TData, TVariables> & { queryClient: QueryClient },
) {
  const {
    queryClient,
    queryKeys,
    optimisticData,
    updateQueryData,
    rollback,
  } = options;

  return {
    onMutate: async (variables: TVariables) => {
      // Cancel outgoing refetches to avoid overwriting optimistic update
      // Snapshot previous values for rollback
      const previousValues: Array<{ queryKey: (string | number)[]; data: any }> = [];

      // Cancel queries and snapshot previous values
      for (const queryKey of queryKeys) {
        await queryClient.cancelQueries({ queryKey });
        
        const previousData = queryClient.getQueryData(queryKey);
        previousValues.push({ queryKey, data: previousData });

        // Apply optimistic update
        if (updateQueryData) {
          queryClient.setQueryData(queryKey, (old: any) =>
            updateQueryData(old, variables, queryClient),
          );
        } else {
          // Default: replace with optimistic data
          queryClient.setQueryData(queryKey, optimisticData(variables));
        }
      }

      // Return context with previous values for rollback
      return { previousValues };
    },
    onError: (error: any, variables: TVariables, context: any) => {
      if (context?.previousValues) {
        // Restore previous values
        for (const { queryKey, data } of context.previousValues) {
          queryClient.setQueryData(queryKey, data);
        }
      }

      // Call custom rollback if provided
      if (rollback) {
        rollback(variables, queryClient);
      }
    },
    onSettled: () => {
      // Always refetch after error or success to ensure consistency
      for (const queryKey of queryKeys) {
        queryClient.invalidateQueries({ queryKey });
      }
    },
  };
}

/**
 * Helper for optimistic updates with array operations (add, remove, update)
 */
export interface ArrayOptimisticUpdateOptions<TItem = any, TVariables = any> {
  /** QueryClient instance */
  queryClient: QueryClient;
  /** Query keys to update */
  queryKeys: (string | number)[][];
  /** Query key that contains the array */
  arrayQueryKey: (string | number)[];
  /** Function to get the array from query data */
  getArray?: (data: any) => TItem[];
  /** Operation type */
  operation: 'add' | 'remove' | 'update';
  /** Function to generate optimistic item */
  optimisticItem?: (variables: TVariables) => TItem;
  /** Function to match item for remove/update */
  matchItem?: (item: TItem, variables: TVariables) => boolean;
  /** Function to update item */
  updateItem?: (item: TItem, variables: TVariables) => TItem;
}

/**
 * Creates optimistic update for array operations (add, remove, update)
 * 
 * @example
 * ```typescript
 * // Add item
 * const addMutation = useMutation({
 *   mutationFn: addTrackToPlaylist,
 *   ...createArrayOptimisticUpdate({
 *     queryKeys: [['playlist', playlistId]],
 *     arrayQueryKey: ['playlist', playlistId, 'tracks'],
 *     operation: 'add',
 *     optimisticItem: (variables) => ({
 *       id: `temp-${Date.now()}`,
 *       track_id: variables.trackId,
 *       ...variables,
 *     }),
 *   }),
 * });
 * 
 * // Remove item
 * const removeMutation = useMutation({
 *   mutationFn: removeTrackFromPlaylist,
 *   ...createArrayOptimisticUpdate({
 *     queryKeys: [['playlist', playlistId]],
 *     arrayQueryKey: ['playlist', playlistId, 'tracks'],
 *     operation: 'remove',
 *     matchItem: (item, variables) => item.id === variables.trackId,
 *   }),
 * });
 * ```
 */
export function createArrayOptimisticUpdate<TItem = any, TVariables = any>(
  options: ArrayOptimisticUpdateOptions<TItem, TVariables>,
) {
  const {
    queryClient,
    queryKeys,
    arrayQueryKey,
    getArray = (data: any) => (Array.isArray(data) ? data : data?.items || []),
    operation,
    optimisticItem,
    matchItem,
    updateItem,
  } = options;

  return {
    onMutate: async (variables: TVariables) => {
      const previousValues: Array<{ queryKey: (string | number)[]; data: any }> = [];

      // Cancel queries and snapshot
      for (const queryKey of queryKeys) {
        await queryClient.cancelQueries({ queryKey });
        const previousData = queryClient.getQueryData(queryKey);
        previousValues.push({ queryKey, data: previousData });
      }

      // Apply optimistic update to array
      for (const queryKey of queryKeys) {
        queryClient.setQueryData(queryKey, (old: any) => {
          if (!old) return old;

          const array = getArray(old);
          let newArray: TItem[];

          switch (operation) {
            case 'add':
              if (optimisticItem) {
                newArray = [...array, optimisticItem(variables)];
              } else {
                newArray = array;
              }
              break;

            case 'remove':
              if (matchItem) {
                newArray = array.filter((item) => !matchItem(item, variables));
              } else {
                newArray = array;
              }
              break;

            case 'update':
              if (matchItem && updateItem) {
                newArray = array.map((item) =>
                  matchItem(item, variables) ? updateItem(item, variables) : item,
                );
              } else {
                newArray = array;
              }
              break;

            default:
              newArray = array;
          }

          // Update the array in the data structure
          if (Array.isArray(old)) {
            return newArray;
          } else if (old.items) {
            return { ...old, items: newArray };
          } else if (old.tracks) {
            return { ...old, tracks: newArray };
          } else {
            return { ...old, [arrayQueryKey[arrayQueryKey.length - 1] as string]: newArray };
          }
        });
      }

      return { previousValues };
    },
    onError: (error: any, variables: TVariables, context: any) => {
      if (context?.previousValues) {
        for (const { queryKey, data } of context.previousValues) {
          queryClient.setQueryData(queryKey, data);
        }
      }
    },
    onSettled: () => {
      for (const queryKey of queryKeys) {
        queryClient.invalidateQueries({ queryKey });
      }
    },
  };
}

/**
 * Helper for toggle operations (like/unlike, follow/unfollow)
 */
export interface ToggleOptimisticUpdateOptions<TVariables = any> {
  /** QueryClient instance */
  queryClient: QueryClient;
  /** Query keys to update */
  queryKeys: (string | number)[][];
  /** Current state value */
  currentValue: boolean;
  /** Function to get the value from query data */
  getValue?: (data: any) => boolean;
  /** Function to get the count from query data */
  getCount?: (data: any) => number;
  /** New state value (opposite of currentValue) */
  newValue?: boolean;
  /** Whether to update count */
  updateCount?: boolean;
  /** Count increment/decrement */
  countDelta?: number;
}

/**
 * Creates optimistic update for toggle operations
 * 
 * @example
 * ```typescript
 * const likeMutation = useMutation({
 *   mutationFn: () => likeTrack(trackId),
 *   ...createToggleOptimisticUpdate({
 *     queryKeys: [['track', trackId], ['tracks']],
 *     currentValue: isLiked,
 *     updateCount: true,
 *     countDelta: 1,
 *   }),
 * });
 * ```
 */
export function createToggleOptimisticUpdate<TVariables = any>(
  options: ToggleOptimisticUpdateOptions<TVariables>,
) {
  const {
    queryClient,
    queryKeys,
    currentValue,
    getValue = (data: any) => data?.is_liked || data?.isLiked || false,
    getCount = (data: any) => data?.like_count || data?.likeCount || 0,
    newValue = !currentValue,
    updateCount = true,
    countDelta = 1,
  } = options;

  return {
    onMutate: async (variables: TVariables) => {
      const previousValues: Array<{ queryKey: (string | number)[]; data: any }> = [];

      for (const queryKey of queryKeys) {
        await queryClient.cancelQueries({ queryKey });
        const previousData = queryClient.getQueryData(queryKey);
        previousValues.push({ queryKey, data: previousData });

        queryClient.setQueryData(queryKey, (old: any) => {
          if (!old) return old;

          const updated = { ...old };

          // Update boolean value
          if ('is_liked' in updated) {
            updated.is_liked = newValue;
          }
          if ('isLiked' in updated) {
            updated.isLiked = newValue;
          }
          if ('is_following' in updated) {
            updated.is_following = newValue;
          }
          if ('isFollowing' in updated) {
            updated.isFollowing = newValue;
          }

          // Update count
          if (updateCount) {
            if ('like_count' in updated) {
              updated.like_count = Math.max(0, (updated.like_count || 0) + countDelta);
            }
            if ('likeCount' in updated) {
              updated.likeCount = Math.max(0, (updated.likeCount || 0) + countDelta);
            }
            if ('follower_count' in updated) {
              updated.follower_count = Math.max(0, (updated.follower_count || 0) + countDelta);
            }
            if ('followerCount' in updated) {
              updated.followerCount = Math.max(0, (updated.followerCount || 0) + countDelta);
            }
          }

          return updated;
        });
      }

      return { previousValues, newValue };
    },
    onError: (error: any, variables: TVariables, context: any) => {
      if (context?.previousValues) {
        for (const { queryKey, data } of context.previousValues) {
          queryClient.setQueryData(queryKey, data);
        }
      }
    },
    onSettled: () => {
      for (const queryKey of queryKeys) {
        queryClient.invalidateQueries({ queryKey });
      }
    },
  };
}

