import { useState, useEffect, useCallback, useRef } from 'react';

export interface UseApiQueryOptions<T> {
  onSuccess?: (data: T) => void;
  onError?: (error: Error) => void;
  retryCount?: number;
  retryDelay?: number;
  cacheTime?: number;
  staleTime?: number;
  enabled?: boolean;
}

export interface UseApiQueryResult<T> {
  data: T | null;
  isLoading: boolean;
  isError: boolean;
  error: Error | null;
  refetch: () => void;
  isStale: boolean;
}

// Cache global para queries
const queryCache = new Map<string, { data: unknown; timestamp: number }>();

/**
 * Hook enterprise para queries con caché, retry y cancelación
 * @param queryKey - Identificador único para el cache
 * @param fetcher - Función que retorna una Promise con los datos
 * @param options - Opciones de configuración
 */
export function useApiQuery<T>(
  queryKey: string,
  fetcher: () => Promise<T>,
  options: UseApiQueryOptions<T> = {}
): UseApiQueryResult<T> {
  const {
    onSuccess,
    onError,
    retryCount = 3,
    retryDelay = 1000,
    cacheTime = 5 * 60 * 1000, // 5 minutos
    staleTime = 0,
    enabled = true,
  } = options;

  const [data, setData] = useState<T | null>(null);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);
  const [error, setError] = useState<Error | null>(null);
  const [isStale, setIsStale] = useState(false);
  
  const abortControllerRef = useRef<AbortController | null>(null);
  const isMountedRef = useRef(true);
  const retryCountRef = useRef(0);

  // Cleanup en desmontaje
  useEffect(() => {
    return () => {
      isMountedRef.current = false;
      if (abortControllerRef.current) {
        abortControllerRef.current.abort();
      }
    };
  }, []);

  const executeQuery = useCallback(async (): Promise<void> => {
    if (!isMountedRef.current) return;

    // Verificar cache
    const cached = queryCache.get(queryKey);
    const now = Date.now();
    
    if (cached && (now - cached.timestamp) < cacheTime) {
      setData(cached.data as T);
      setIsStale((now - cached.timestamp) > staleTime);
      
      if (onSuccess && isMountedRef.current) {
        onSuccess(cached.data as T);
      }
      return;
    }

    // Cancelar petición anterior si existe
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
    }

    abortControllerRef.current = new AbortController();
    
    if (isMountedRef.current) {
      setIsLoading(true);
      setIsError(false);
      setError(null);
    }

    try {
      const result = await fetcher();
      
      if (!isMountedRef.current) return;

      // Guardar en cache
      queryCache.set(queryKey, { data: result, timestamp: now });
      
      setData(result);
      setIsStale(false);
      retryCountRef.current = 0;
      
      if (onSuccess) {
        onSuccess(result);
      }
    } catch (err) {
      if (!isMountedRef.current) return;

      // No actualizar estado si fue cancelado
      if (err instanceof Error && err.name === 'AbortError') {
        return;
      }

      const error = err instanceof Error ? err : new Error(String(err));
      
      // Retry logic
      if (retryCountRef.current < retryCount) {
        retryCountRef.current++;
        setTimeout(() => {
          if (isMountedRef.current) {
            void executeQuery();
          }
        }, retryDelay * retryCountRef.current);
        return;
      }

      setIsError(true);
      setError(error);
      
      if (onError) {
        onError(error);
      }
    } finally {
      if (isMountedRef.current) {
        setIsLoading(false);
      }
    }
  }, [queryKey, fetcher, cacheTime, staleTime, retryCount, retryDelay, onSuccess, onError]);

  useEffect(() => {
    if (enabled) {
      void executeQuery();
    }
  }, [enabled, executeQuery]);

  const refetch = useCallback((): void => {
    queryCache.delete(queryKey);
    retryCountRef.current = 0;
    void executeQuery();
  }, [queryKey, executeQuery]);

  return {
    data,
    isLoading,
    isError,
    error,
    refetch,
    isStale,
  };
}

/**
 * Hook para debounce de valores
 * @param value - Valor a debouncear
 * @param delay - Delay en ms
 */
export function useDebounce<T>(value: T, delay: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);

  useEffect(() => {
    if (timeoutRef.current) {
      clearTimeout(timeoutRef.current);
    }

    timeoutRef.current = setTimeout(() => {
      setDebouncedValue(value);
    }, delay);

    return () => {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
    };
  }, [value, delay]);

  return debouncedValue;
}

/**
 * Hook tipado para localStorage con seguridad
 * @param key - Clave en localStorage
 * @param initialValue - Valor inicial
 */
export function useLocalStorage<T>(
  key: string,
  initialValue: T
): [T, (value: T | ((prev: T) => T)) => void] {
  const [storedValue, setStoredValue] = useState<T>(() => {
    if (typeof window === 'undefined') {
      return initialValue;
    }

    try {
      const item = window.localStorage.getItem(key);
      return item ? (JSON.parse(item) as T) : initialValue;
    } catch (error) {
      console.warn(`Error reading localStorage key "${key}":`, error);
      return initialValue;
    }
  });

  const setValue = useCallback((value: T | ((prev: T) => T)) => {
    try {
      setStoredValue((prev) => {
        const valueToStore = value instanceof Function ? value(prev) : value;
        
        if (typeof window !== 'undefined') {
          window.localStorage.setItem(key, JSON.stringify(valueToStore));
        }
        
        return valueToStore;
      });
    } catch (error) {
      console.warn(`Error setting localStorage key "${key}":`, error);
    }
  }, [key]);

  return [storedValue, setValue];
}

/**
 * Hook para media queries responsive
 * @param query - Media query string
 */
export function useMediaQuery(query: string): boolean {
  const [matches, setMatches] = useState(false);

  useEffect(() => {
    if (typeof window === 'undefined') {
      return;
    }

    const media = window.matchMedia(query);
    
    const updateMatch = () => setMatches(media.matches);
    updateMatch();

    media.addEventListener('change', updateMatch);
    
    return () => {
      media.removeEventListener('change', updateMatch);
    };
  }, [query]);

  return matches;
}

/**
 * Hook para obtener el valor anterior de una variable
 * @param value - Valor actual
 */
export function usePrevious<T>(value: T): T | undefined {
  const [prev, setPrev] = useState<T | undefined>(undefined);
  const currentRef = useRef<T>(value);

  useEffect(() => {
    if (currentRef.current !== value) {
      setPrev(currentRef.current);
      currentRef.current = value;
    }
  }, [value]);

  return prev;
}

/**
 * Hook para detectar clicks fuera de un elemento
 * @param ref - Ref del elemento a monitorear
 * @param handler - Callback cuando se hace click fuera
 */
export function useClickOutside<T extends HTMLElement>(
  ref: React.RefObject<T>,
  handler: () => void
): void {
  useEffect(() => {
    const listener = (event: MouseEvent | TouchEvent) => {
      const el = ref.current;
      if (!el || el.contains(event.target as Node)) {
        return;
      }
      handler();
    };

    document.addEventListener('mousedown', listener);
    document.addEventListener('touchstart', listener);

    return () => {
      document.removeEventListener('mousedown', listener);
      document.removeEventListener('touchstart', listener);
    };
  }, [ref, handler]);
}

/**
 * Hook para intervalos con cleanup automático
 * @param callback - Función a ejecutar
 * @param delay - Delay en ms (null para pausar)
 */
export function useInterval(callback: () => void, delay: number | null): void {
  const savedCallback = useRef(callback);

  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  useEffect(() => {
    if (delay === null) {
      return;
    }

    const id = setInterval(() => {
      savedCallback.current();
    }, delay);

    return () => clearInterval(id);
  }, [delay]);
}

/**
 * Hook para throttle de funciones
 * @param callback - Función a throttle
 * @param limit - Tiempo límite en ms
 */
export function useThrottle<T extends (...args: unknown[]) => unknown>(
  callback: T,
  limit: number
): (...args: Parameters<T>) => void {
  const lastRunRef = useRef<number>(0);
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);

  return useCallback((...args: Parameters<T>) => {
    const now = Date.now();

    if (now - lastRunRef.current >= limit) {
      lastRunRef.current = now;
      callback(...args);
    } else {
      if (timeoutRef.current) {
        clearTimeout(timeoutRef.current);
      }
      
      timeoutRef.current = setTimeout(() => {
        lastRunRef.current = Date.now();
        callback(...args);
      }, limit - (now - lastRunRef.current));
    }
  }, [callback, limit]);
}

/**
 * Hook para manejar formularios de manera tipada
 */
export function useForm<T extends Record<string, unknown>>(
  initialValues: T
): {
  values: T;
  setValue: <K extends keyof T>(key: K, value: T[K]) => void;
  setValues: (values: Partial<T>) => void;
  reset: () => void;
} {
  const [values, setValues] = useState<T>(initialValues);

  const setValue = useCallback(<K extends keyof T>(key: K, value: T[K]) => {
    setValues((prev) => ({ ...prev, [key]: value }));
  }, []);

  const setMultipleValues = useCallback((newValues: Partial<T>) => {
    setValues((prev) => ({ ...prev, ...newValues }));
  }, []);

  const reset = useCallback(() => {
    setValues(initialValues);
  }, [initialValues]);

  return {
    values,
    setValue,
    setValues: setMultipleValues,
    reset,
  };
}

/**
 * Hook para lazy loading de componentes
 * @param factory - Función que importa el componente
 */
export function useLazyComponent<T>(
  factory: () => Promise<{ default: React.ComponentType<T> }>
): React.ComponentType<T> | null {
  const [Component, setComponent] = useState<React.ComponentType<T> | null>(null);

  useEffect(() => {
    let isMounted = true;

    factory().then((module) => {
      if (isMounted) {
        setComponent(() => module.default);
      }
    }).catch(() => {
      // Component failed to load, keep null
    });

    return () => {
      isMounted = false;
    };
  }, [factory]);

  return Component;
}
