import { useState, useEffect, useCallback, useRef } from 'react';
import { getErrorMessage } from '../utils/errorHandling';

/**
 * State for async operations
 */
export interface AsyncState<T> {
  data: T | null;
  loading: boolean;
  error: string | null;
}

/**
 * Options for useAsync hook
 */
export interface UseAsyncOptions {
  /** Whether to execute immediately on mount (default: true) */
  immediate?: boolean;
  /** Dependencies that trigger re-execution */
  deps?: unknown[];
}

/**
 * Return type for useAsync hook
 */
export interface UseAsyncReturn<T> extends AsyncState<T> {
  /** Re-execute the async function */
  execute: () => Promise<T | null>;
  /** Reset state to initial values */
  reset: () => void;
  /** Update data directly without re-fetching */
  setData: (data: T | null) => void;
}

/**
 * Generic hook for managing async operations with loading/error states.
 *
 * @param asyncFn - Async function to execute
 * @param options - Configuration options
 * @returns Async state and control functions
 *
 * @example
 * ```tsx
 * const { data: users, loading, error, execute } = useAsync(
 *   () => fetchUsers(),
 *   { immediate: true }
 * );
 *
 * if (loading) return <Spinner />;
 * if (error) return <Error message={error} />;
 * return <UserList users={users} />;
 * ```
 */
export function useAsync<T>(
  asyncFn: () => Promise<T>,
  options: UseAsyncOptions = {},
): UseAsyncReturn<T> {
  const { immediate = true, deps = [] } = options;

  const [state, setState] = useState<AsyncState<T>>({
    data: null,
    loading: immediate,
    error: null,
  });

  // Track if component is mounted to avoid state updates after unmount
  const mountedRef = useRef(true);
  // Track the async function to avoid stale closures
  const asyncFnRef = useRef(asyncFn);
  asyncFnRef.current = asyncFn;

  const execute = useCallback(async (): Promise<T | null> => {
    setState((prev) => ({ ...prev, loading: true, error: null }));

    try {
      const result = await asyncFnRef.current();
      if (mountedRef.current) {
        setState({ data: result, loading: false, error: null });
      }
      return result;
    } catch (err) {
      if (mountedRef.current) {
        setState({ data: null, loading: false, error: getErrorMessage(err) });
      }
      return null;
    }
  }, []);

  const reset = useCallback(() => {
    setState({ data: null, loading: false, error: null });
  }, []);

  const setData = useCallback((data: T | null) => {
    setState((prev) => ({ ...prev, data }));
  }, []);

  // Execute on mount if immediate is true
  useEffect(() => {
    mountedRef.current = true;

    if (immediate) {
      execute();
    }

    return () => {
      mountedRef.current = false;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [immediate, execute, ...deps]);

  return {
    ...state,
    execute,
    reset,
    setData,
  };
}

/**
 * Global cache for useAsyncWithCache hook
 * Shared across all hook instances for consistent caching behavior
 */
const globalCache = new Map<string, { data: unknown; timestamp: number }>();

/**
 * Hook for managing multiple async operations that should be cached.
 * Useful for data that doesn't change often and should be shared across components.
 * Uses a global cache that persists across component re-renders and hook instances.
 *
 * @param key - Unique cache key
 * @param asyncFn - Async function to execute
 * @param options - Configuration options
 */
export function useAsyncWithCache<T>(
  key: string,
  asyncFn: () => Promise<T>,
  options: UseAsyncOptions & { cacheTime?: number } = {},
): UseAsyncReturn<T> {
  const { cacheTime = 5 * 60 * 1000, ...restOptions } = options; // Default 5 minutes

  // Store the async function in a ref to keep it stable
  const asyncFnRef = useRef(asyncFn);
  asyncFnRef.current = asyncFn;

  const cachedFn = useCallback(async (): Promise<T> => {
    const cached = globalCache.get(key);
    const now = Date.now();

    // Return cached data if still valid
    if (cached && now - cached.timestamp < cacheTime) {
      return cached.data as T;
    }

    // Fetch fresh data
    const data = await asyncFnRef.current();
    globalCache.set(key, { data, timestamp: now });
    return data;
  }, [key, cacheTime]);

  return useAsync(cachedFn, restOptions);
}

/**
 * Clear a specific cache entry or all entries
 * Useful for forcing refetch of data
 */
export function clearAsyncCache(key?: string): void {
  if (key) {
    globalCache.delete(key);
  } else {
    globalCache.clear();
  }
}

export default useAsync;
