import { useEffect, useMemo, useRef, useState } from "react";
import type { Address, WalletCapabilities } from "viem";
import { useConfig } from "wagmi";
import { getWalletClient } from "wagmi/actions";

import { useAccounts } from "./useAccounts";

const TIMEOUT_MS = 20000;
const CIRCUIT_BREAKER_RESET_MS = 60000; // 1 minute

// optional module-level cache (survives across hook instances)
const cache = new Map<string, WalletCapabilities | null>();
// Circuit breaker to track failure timestamps
const failureTimestamps = new Map<string, number>();

export function useWalletCapabilities(chainId?: number) {
	const config = useConfig();
	const [capabilities, setCapabilities] = useState<
		WalletCapabilities | null | undefined
	>(undefined);
	const [isLoading, setIsLoading] = useState<boolean>(false);
	const calledRef = useRef(false);
	const { accounts } = useAccounts();

	// evm account
	const account = accounts.find((account) => account.chainType === "EVM");
	const address = account?.address as Address;

	useEffect(() => {
		if (!address || calledRef.current || !chainId) return;
		const cacheKey = `${chainId}:${address}`;

		// Check circuit breaker
		const lastFailureTime = failureTimestamps.get(cacheKey);
		if (
			lastFailureTime &&
			Date.now() - lastFailureTime < CIRCUIT_BREAKER_RESET_MS
		) {
			console.log("[Capabilities] Circuit breaker active, skipping request");
			setCapabilities(null);
			calledRef.current = true;
			return;
		}

		if (cache.has(cacheKey)) {
			setCapabilities(cache.get(cacheKey));
			calledRef.current = true;
			return;
		}

		let timeoutId: ReturnType<typeof setTimeout> | undefined;

		const timeoutPromise = new Promise((_, reject) => {
			timeoutId = setTimeout(
				() => reject(new Error("getCapabilities timeout")),
				TIMEOUT_MS,
			);
		});

		async function fetchCapabilities() {
			calledRef.current = true;
			setIsLoading(true);

			try {
				const walletClient = await getWalletClient(config);

				if (!walletClient) {
					throw new Error("No wallet client available");
				}

				// Try to get capabilities with timeout
				const capabilitiesPromise = walletClient.getCapabilities({
					account: address,
				});
				const result = await Promise.race([
					capabilitiesPromise,
					timeoutPromise,
				]);

				if (result && typeof result === "object") {
					cache.set(cacheKey, result as WalletCapabilities);
					setCapabilities(result as WalletCapabilities);
					// Clear any previous failure timestamp on success
					failureTimestamps.delete(cacheKey);
				} else {
					throw new Error(
						`Invalid capabilities format: ${JSON.stringify(result)}`,
					);
				}
			} catch (err) {
				console.warn("[Capabilities]", err);

				// If it's a timeout but we have a wallet client, try to get capabilities with a shorter timeout
				if (err instanceof Error && err.message.includes("timeout")) {
					try {
						const walletClient = await getWalletClient(config);
						if (walletClient) {
							// Create a shorter timeout for retry (half of original)
							const retryTimeoutPromise = new Promise((_, reject) => {
								setTimeout(
									() => reject(new Error("getCapabilities retry timeout")),
									TIMEOUT_MS / 2,
								);
							});

							const result = await Promise.race([
								walletClient.getCapabilities({ account: address }),
								retryTimeoutPromise,
							]);

							console.log("[Capabilities] Got result after timeout:", result);
							if (result && typeof result === "object") {
								cache.set(cacheKey, result as WalletCapabilities);
								setCapabilities(result as WalletCapabilities);
								// Clear any previous failure timestamp on success
								failureTimestamps.delete(cacheKey);
								console.log(
									"[Capabilities] Successfully cached capabilities after timeout",
								);
								return; // Success, don't set to null
							}
						}
					} catch (retryErr) {
						console.warn("[Capabilities] Retry also failed:", retryErr);
					}
					// Cache null for timeout errors to prevent repeated attempts
					cache.set(cacheKey, null);
					// Set failure timestamp for circuit breaker
					failureTimestamps.set(cacheKey, Date.now());
				} else {
					// Cache null for non-timeout errors to prevent repeated attempts
					cache.set(cacheKey, null);
					// Set failure timestamp for circuit breaker
					failureTimestamps.set(cacheKey, Date.now());
				}
				setCapabilities(null);
			} finally {
				if (timeoutId) clearTimeout(timeoutId);
				setIsLoading(false);
			}
		}

		fetchCapabilities();
	}, [config, address, chainId]);

	const supportsAtomicBatching = useMemo(() => {
		if (!chainId) return false;
		const _capabilities = capabilities?.[chainId];
		return (
			_capabilities?.atomicBatch?.supported ||
			_capabilities?.atomic?.status === "supported"
		);
	}, [capabilities, chainId]);

	return {
		capabilities,
		supportsAtomicBatching,
		isLoading,
	};
}
