import { useCallback, useEffect, useState, useMemo, useRef } from 'react';
import debounce from 'lodash/debounce';

interface NetworkStatus {
  loading: boolean;
  connected: boolean;
  blockNumber?: bigint;
  error?: string;
}

let cachedWeb3Promise: Promise<typeof import('web3').default> | null = null;

const loadWeb3 = () => {
  if (!cachedWeb3Promise) {
    cachedWeb3Promise = import('web3')
      .then((module) => module.default)
      .catch((error) => {
        cachedWeb3Promise = null;
        throw error;
      });
  }
  return cachedWeb3Promise;
};

/**
 * Manages the network status of a Web3 connection with optional debouncing.
 *
 * @param apiHost - Optional URL of the Ethereum RPC endpoint
 * @param options - Configuration options for connection checking
 * @param options.debounce - Delay in milliseconds between connection checks (default: 500ms)
 * @param options.pollInterval - Interval in milliseconds between connection checks (default: 2000ms)
 *
 * @returns An object containing the current network status and a manual connection check function
 *
 * @remarks
 * This hook provides real-time monitoring of Web3 network connectivity, including:
 * - Loading state tracking
 * - Connection status detection
 * - Block number retrieval
 * - Error handling for connection attempts
 *
 * @example
 * const { status, checkConnection } = useRpcStatus('https://mainnet.infura.io/v3/YOUR-PROJECT-ID');
 * // status will contain { loading, connected, blockNumber?, error? }
 */
export function useRpcStatus(apiHost?: string, options = { debounce: 500, pollInterval: 2000 }) {
  const [status, setStatus] = useState<NetworkStatus>({
    loading: false,
    connected: false,
  });

  const initializedRef = useRef(false);
  const checkingRef = useRef(false);
  const checkConnection = useCallback(
    async (isPolling = false) => {
      if (!apiHost) {
        setStatus({ loading: false, connected: false });
        return;
      }

      if (checkingRef.current) {
        return;
      }
      checkingRef.current = true;

      if (!isPolling && !initializedRef.current) {
        setStatus((prev) => ({ ...prev, loading: true }));
      }
      try {
        const Web3 = await loadWeb3();
        const web3 = new Web3(apiHost);

        const timeoutPromise = new Promise<never>((_, reject) => {
          setTimeout(() => reject(new Error('Connection timeout')), 5000);
        });
        const blockNumber = await Promise.race([web3.eth.getBlockNumber(), timeoutPromise]);

        setStatus({
          loading: false,
          connected: true,
          blockNumber,
        });
        initializedRef.current = true;
      } catch (error) {
        setStatus({
          loading: false,
          connected: false,
          error: error.message,
        });
      } finally {
        checkingRef.current = false;
      }
    },
    [apiHost]
  );

  const debouncedCheck = useMemo(
    () => debounce(checkConnection, options.debounce),
    [checkConnection, options.debounce]
  );

  useEffect(() => {
    const check = options.debounce > 0 ? debouncedCheck : checkConnection;
    initializedRef.current = false;
    check();

    let pollTimer: NodeJS.Timeout | null = null;
    if (options.pollInterval > 0) {
      pollTimer = setInterval(() => checkConnection(true), options.pollInterval);
    }

    return () => {
      if (options.debounce > 0) {
        debouncedCheck.cancel();
      }
      if (pollTimer) {
        clearInterval(pollTimer);
      }
      checkingRef.current = false;
    };
  }, [apiHost, options.debounce, options.pollInterval, checkConnection, debouncedCheck]);

  return { status, checkConnection };
}
