import { useState, useCallback, useEffect, useRef } from 'react';
import { toast } from 'react-toastify';
import { logger } from '../utils/logger';

interface UseApiCallOptions<T> {
  showSuccessToast?: boolean;
  successMessage?: string;
  showErrorToast?: boolean;
  onSuccess?: (data: T) => void;
  onError?: (error: unknown) => void;
  cancelOnUnmount?: boolean; // Cancel request on component unmount
}

interface UseApiCallReturn<T> {
  execute: (...args: unknown[]) => Promise<T | undefined>;
  isLoading: boolean;
  error: Error | null;
  clearError: () => void;
  cancel: () => void; // Manual cancellation function
}

/**
 * Custom hook for handling API calls with loading states and error handling
 * Includes request cancellation on component unmount to prevent memory leaks
 * 
 * @param apiFunction - The async API function to call
 * @param options - Configuration options
 * @returns Object with execute function, loading state, error state, clearError function, and cancel function
 */
export function useApiCall<T = unknown>(
  apiFunction: (...args: unknown[]) => Promise<T>,
  options: UseApiCallOptions<T> = {}
): UseApiCallReturn<T> {
  const {
    showSuccessToast = false,
    successMessage = 'Operation completed successfully',
    showErrorToast = true,
    onSuccess,
    onError,
    cancelOnUnmount = true, // Default to true for safety
  } = options;

  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  const abortControllerRef = useRef<AbortController | null>(null);
  const mountedRef = useRef(true);

  // Track component mount status
  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
      // Cancel pending request on unmount if enabled
      if (cancelOnUnmount && abortControllerRef.current) {
        abortControllerRef.current.abort();
        abortControllerRef.current = null;
      }
    };
  }, [cancelOnUnmount]);

  const cancel = useCallback(() => {
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
      abortControllerRef.current = null;
      setIsLoading(false);
    }
  }, []);

  const execute = useCallback(
    async (...args: unknown[]): Promise<T | undefined> => {
      // Cancel any previous request
      if (abortControllerRef.current) {
        abortControllerRef.current.abort();
      }

      // Create new AbortController for this request
      const abortController = new AbortController();
      abortControllerRef.current = abortController;

      try {
        setIsLoading(true);
        setError(null);

        // Check if component is still mounted before making request
        if (!mountedRef.current) {
          return undefined;
        }

        // Check if apiFunction supports AbortSignal
        // If the function accepts a signal parameter, pass it
        let result: T;
        if (args.length > 0 && typeof args[args.length - 1] === 'object' && args[args.length - 1] !== null && 'signal' in (args[args.length - 1] as object)) {
          // Function already has signal in arguments
          result = await apiFunction(...args);
        } else {
          // Try to pass signal as last argument if function supports it
          try {
            result = await apiFunction(...args, { signal: abortController.signal });
          } catch (err) {
            const typeErrorSignal = err instanceof TypeError && (err as Error).message?.includes('signal');
            if (typeErrorSignal) {
              result = await apiFunction(...args);
            } else {
              throw err;
            }
          }
        }

        // Check if component is still mounted before updating state
        if (!mountedRef.current) {
          return undefined;
        }

        // Check if request was cancelled
        if (abortController.signal.aborted) {
          return undefined;
        }

        if (showSuccessToast) {
          toast.success(successMessage);
        }

        if (onSuccess) {
          onSuccess(result);
        }

        abortControllerRef.current = null;
        return result;
      } catch (err: unknown) {
        // Don't handle errors if component is unmounted or request was cancelled
        const isAbortError =
          abortController.signal.aborted ||
          (err instanceof DOMException && err.name === 'AbortError') ||
          (typeof err === 'object' && err !== null && 'name' in err && (err as { name?: string }).name === 'AbortError');
        if (!mountedRef.current || isAbortError) {
          return undefined;
        }

        const maybeResponse = (err as { response?: { data?: { message?: string; detail?: string } } }).response;
        const errorMessage =
          maybeResponse?.data?.message ||
          maybeResponse?.data?.detail ||
          (err instanceof Error ? err.message : 'An error occurred. Please try again.');

        const apiError = err instanceof Error ? err : new Error(errorMessage);
        setError(apiError);

        logger.error('API call failed', err);

        const isCancelled = typeof (err as { isCancelled?: boolean }).isCancelled === 'boolean' && (err as { isCancelled?: boolean }).isCancelled;
        if (showErrorToast && !isCancelled) {
          toast.error(errorMessage);
        }

        if (onError && !isCancelled) {
          onError(err);
        }

        throw err;
      } finally {
        // Only update loading state if component is still mounted
        if (mountedRef.current) {
          setIsLoading(false);
        }
        abortControllerRef.current = null;
      }
    },
    [apiFunction, showSuccessToast, successMessage, showErrorToast, onSuccess, onError]
  );

  const clearError = useCallback(() => {
    setError(null);
  }, []);

  return { execute, isLoading, error, clearError, cancel };
}

