// src/hooks/useApi.ts
import { useState, useCallback, useRef, useEffect } from 'react';
import { logger } from '../services/logger.client';
import { notifyError } from '../services/notificationService';

/**
 * A custom React hook to simplify API calls, including loading and error states.
 * It is designed to work with apiClient functions that return a `Promise<Response>`.
 *
 * @template T The expected data type from the API's JSON response.
 * @template A The type of the arguments array for the API function.
 * @param apiFunction The API client function to execute.
 * @returns An object containing:
 *  - `execute`: A function to trigger the API call.
 *  - `loading`: A boolean indicating if the request is in progress.
 *  - `isRefetching`: A boolean indicating if a non-initial request is in progress.
 *  - `error`: An `Error` object if the request fails, otherwise `null`.
 *  - `data`: The data returned from the API, or `null` initially.
 *  - `reset`: A function to manually reset the hook's state to its initial values.
 */
export function useApi<T, TArgs extends unknown[]>(
  apiFunction: (...args: [...TArgs, AbortSignal?]) => Promise<Response>
) {
  const [data, setData] = useState<T | null>(null);
  const [loading, setLoading] = useState<boolean>(false);
  const [isRefetching, setIsRefetching] = useState<boolean>(false);
  const [error, setError] = useState<Error | null>(null);
  const hasBeenExecuted = useRef(false);
  const abortControllerRef = useRef<AbortController>(new AbortController());

  // This effect ensures that when the component using the hook unmounts,
  // any in-flight request is cancelled.
  useEffect(() => {
    const controller = abortControllerRef.current;
    return () => {
      controller.abort();
    };
  }, []);

  /**
   * Resets the hook's state to its initial values.
   * This is useful for clearing data when dependencies change.
   */
  const reset = useCallback(() => {
    setData(null);
    setLoading(false);
    setIsRefetching(false);
    setError(null);
  }, []);

  const execute = useCallback(async (...args: TArgs): Promise<T | null> => {
    setLoading(true);
    setError(null);
    if (hasBeenExecuted.current) {
      setIsRefetching(true);
    }
    
    try {
      const response = await apiFunction(...args, abortControllerRef.current.signal);

      if (!response.ok) {
        // Attempt to parse a JSON error response. This is aligned with ADR-003,
        // which standardizes on structured Zod errors.
        let errorMessage = `Request failed with status ${response.status}: ${response.statusText}`;
        try {
          const errorData = await response.json();
          // If the backend sends a Zod-like error array, format it.
          if (Array.isArray(errorData.issues) && errorData.issues.length > 0) {
            errorMessage = errorData.issues
              .map((issue: { path?: string[], message: string }) => `${issue.path?.join('.') || 'Error'}: ${issue.message}`)
              .join('; ');
          } else if (errorData.message) {
            errorMessage = errorData.message;
          }
        } catch { /* Ignore JSON parsing errors and use the default status text message. */ }
        
        throw new Error(errorMessage);
      }

      // Handle successful responses with no content (e.g., HTTP 204).
      if (response.status === 204) {
        setData(null);
        return null;
      }

      const result: T = await response.json();
      setData(result);
      if (!hasBeenExecuted.current) {
        hasBeenExecuted.current = true;
      }
      return result;
    } catch (e) {
      const err = e instanceof Error ? e : new Error('An unknown error occurred.');
      // If the error is an AbortError, it's an intentional cancellation, so we don't set an error state.
      if (err.name === 'AbortError') {
        logger.info('API request was cancelled.', { functionName: apiFunction.name });
        return null;
      }
      logger.error('API call failed in useApi hook', { error: err.message, functionName: apiFunction.name });
      setError(err);
      notifyError(err.message); // Optionally notify the user automatically.
      return null; // Return null on failure.
    } finally {
      setLoading(false);
      setIsRefetching(false);
    }
  }, [apiFunction]); // abortControllerRef is stable

  return { execute, loading, isRefetching, error, data, reset };
}