import { useCallback, useEffect, useMemo, useRef } from "react";
import { useDispatch, useSelector } from "react-redux";
import { isAddress } from "viem";

import { useRouteSelection } from "@hooks/apis/common/quote/useRouteSelection";
import { useAccounts } from "@hooks/useAccounts";
import { usePreFetchCapabilities } from "@hooks/usePreFetchCapabilities";
import { useRegisterEventHandlers } from "@hooks/useRegisterEventHandlers";
import { useSetInitialValues } from "@hooks/useSetInitialValues";
import { ScreenTab, setScreenTab } from "@store/globalSlice";
import type { ReduxStateType } from "@store/types";
import { useHistoryCleanup } from "./History/useHistoryCleanup";
import { Home } from "./Home";
import { InFlight } from "./InFlight";
import { BgGrid } from "./InFlight/layout/BgGrid";
import { RecipientAddressModal } from "./RecipientAddressModal";
import { ActionStatusProvider } from "./Review/Auto/ActionStatusProvider";
import { ToastContainer } from "./Toast/ToastContainer";
import { SwapWrap } from "./common/SwapWrap";

import {
	fetchTokens,
	getQueryKey as getTokenRecordQueryKey,
} from "@hooks/apis/common/useTokens";
import { useConfig } from "@hooks/useConfig";
import { useLogEvent } from "@hooks/useLogEvent";
import { setReceiverAddress, setSenderAddress } from "@store/quoteParamSlice";
import { useQueryClient } from "@tanstack/react-query";
import { isPublicKey } from "@utils/isPublicKey";
import { isSolana } from "@utils/isSolana";
import { onConnectWallet } from "../analytics/events";
import { CompareModal } from "./Compare/CompareModal";

export const Swap = () => {
	useRouteSelection({ retainPreviousRoute: false });
	useSetInitialValues(); // to set values from the config
	useRegisterEventHandlers();
	useHistoryCleanup();
	usePreFetchCapabilities(); // Pre-fetch wallet capabilities in the background

	const { logEvent } = useLogEvent();
	const { affiliateId, apiKey, baseUrl } = useConfig();
	const queryClient = useQueryClient();

	const { accounts } = useAccounts();
	const dispatch = useDispatch();

	const screenTab = useSelector(
		(state: ReduxStateType) => state.global.screenTab,
	);

	const fromChainId = useSelector(
		(state: ReduxStateType) => state.quoteParams.fromChainId,
	);

	const toChainId = useSelector(
		(state: ReduxStateType) => state.quoteParams.toChainId,
	);
	const receiver = useSelector(
		(state: ReduxStateType) => state.quoteParams.receiver,
	);

	// We only need to trigger the home screen on account change & disconnection
	const accountConnectionArray = useMemo(() => {
		return accounts.map((account) => ({
			chainType: account.chainType,
			address: account.address,
			isConnected: account.isConnected,
		}));
	}, [accounts]);

	const getAccountKey = (account: (typeof accountConnectionArray)[number]) =>
		`${account.chainType}:${account.address}:${account.isConnected}`;

	const isAddressCompatibleWithChain = useCallback(
		(address: string, chainId: number) => {
			if (isSolana(chainId)) {
				return isPublicKey(address);
			}
			return isAddress(address);
		},
		[],
	);

	const getReceiverForChain = useCallback(
		(chainId: number) => {
			if (isSolana(chainId)) {
				return accounts.find((account) => account.chainType === "SVM")?.address;
			}
			return accounts.find((account) => account.chainType === "EVM")?.address;
		},
		[accounts],
	);

	const isReceiverAConnectedWallet = useCallback(
		(receiverAddress: string) => {
			return accounts.some(
				(account) =>
					account.isConnected &&
					account.address?.toLowerCase() === receiverAddress.toLowerCase(),
			);
		},
		[accounts],
	);

	const wasReceiverPreviouslyConnected = useCallback(
		(receiverAddress: string) => {
			return Array.from(prevConnectedAccountKeys.current).some((key) => {
				const keyStr = String(key);
				const [chainType, address, isConnected] = keyStr.split(":");
				return (
					isConnected === "true" &&
					address?.toLowerCase() === receiverAddress.toLowerCase()
				);
			});
		},
		[],
	);

	const prevAccountKeys = useRef(new Set());
	const prevConnectedAccountKeys = useRef(new Set());

	const getWalletName = (address: string | undefined) => {
		if (!address) return undefined;
		return accounts.find((account) => account.address === address)?.connector
			?.name;
	};

	const emitWalletConnectEvent = async (
		account: (typeof accountConnectionArray)[number],
	) => {
		const key = `mp_wallet_connect_${account.address}`;
		const last = localStorage.getItem(key);
		const accountAddress = account.address;

		if (
			!accountAddress ||
			// Quit if the last connection was less than 24 hours ago
			(last && Date.now() - Number(last) < 24 * 60 * 60 * 1000)
		) {
			return;
		}
		// Wait for the query to complete and get data
		const tokenRecords = await queryClient.ensureQueryData({
			queryKey: getTokenRecordQueryKey({ address: accountAddress, apiKey }),
			queryFn: () =>
				fetchTokens({
					address: accountAddress,
					baseUrl,
					apiKey,
					affiliateId,
				}),
		});

		const tokens = Object.values(tokenRecords.result)
			.flat()
			.filter((token) => Boolean(token.balanceInUsd));

		logEvent("login", {
			address: accountAddress,
		});

		logEvent("connect_wallet", {
			wallet_type: account.chainType,
			wallet_name: getWalletName(accountAddress) || "",
			total_balance: tokens.reduce(
				(acc, token) => acc + (token.balanceInUsd || 0),
				0,
			),
		});

		localStorage.setItem(key, String(Date.now()));

		// Emit GA event
		onConnectWallet({
			wallet_type: account.chainType,
			wallet_name: getWalletName(accountAddress) || "",
			total_balance: tokens.reduce(
				(acc, token) => acc + (token.balanceInUsd || 0),
				0,
			),
		});
	};

	// biome-ignore lint/correctness/useExhaustiveDependencies: tracking `accountConnectionArray` instead of individual variables
	useEffect(() => {
		const currentKeys = new Set(accountConnectionArray.map(getAccountKey));
		const previousKeys = prevAccountKeys.current;
		const hasChanged =
			currentKeys.size !== previousKeys.size ||
			[...currentKeys].some((key) => !previousKeys.has(key));

		if (hasChanged && dispatch) {
			dispatch(setScreenTab(ScreenTab.HOME));

			// Emit onConnectWallet event for newly connected accounts
			const newlyConnected = accountConnectionArray.filter((account) => {
				const key = getAccountKey(account);
				return account.isConnected && !previousKeys.has(key);
			});

			for (const account of newlyConnected) {
				emitWalletConnectEvent(account);
			}

			// Store the previous state before updating for receiver clearing logic
			prevConnectedAccountKeys.current = previousKeys;
			prevAccountKeys.current = currentKeys;
		}
	}, [accountConnectionArray, dispatch]);

	// Clear receiver when the connected wallet that matches the receiver disconnects
	useEffect(() => {
		if (!receiver) return;

		const wasConnected = wasReceiverPreviouslyConnected(receiver);
		const isCurrentlyConnected = isReceiverAConnectedWallet(receiver);

		// Clear receiver only if it was previously connected but is now disconnected
		if (wasConnected && !isCurrentlyConnected) {
			dispatch(setReceiverAddress(null));
		}
	}, [
		receiver,
		dispatch,
		wasReceiverPreviouslyConnected,
		isReceiverAConnectedWallet,
	]);

	useEffect(() => {
		if (!fromChainId) return;
		let sender: string | undefined;
		if (isSolana(fromChainId)) {
			sender = accounts.find((account) => account.chainType === "SVM")?.address;
		} else {
			sender = accounts.find((account) => account.chainType === "EVM")?.address;
		}
		dispatch(setSenderAddress(sender || null));
	}, [accounts, dispatch, fromChainId]);

	// Auto-select receiver based on destination chain
	useEffect(() => {
		if (!toChainId) return;

		const newReceiver = getReceiverForChain(toChainId);

		// Update receiver if:
		// 1. No receiver exists, OR
		// 2. Current receiver is a connected wallet, OR
		// 3. Current receiver was previously connected (now disconnected), OR
		// 4. Current receiver is incompatible with destination chain
		const shouldUpdate =
			!receiver ||
			isReceiverAConnectedWallet(receiver) ||
			wasReceiverPreviouslyConnected(receiver) ||
			!isAddressCompatibleWithChain(receiver, toChainId);

		if (shouldUpdate) {
			dispatch(setReceiverAddress(newReceiver || null));
		}
	}, [
		dispatch,
		toChainId,
		receiver,
		getReceiverForChain,
		isReceiverAConnectedWallet,
		wasReceiverPreviouslyConnected,
		isAddressCompatibleWithChain,
	]);

	return (
		<SwapWrap>
			{screenTab === ScreenTab.HOME && (
				<ActionStatusProvider>
					<Home />
				</ActionStatusProvider>
			)}
			{screenTab === ScreenTab.INFLIGHT && (
				<div>
					<BgGrid />
					<InFlight />
				</div>
			)}

			{/* Modals - slides in from the widget bottom */}
			<RecipientAddressModal />
			<ToastContainer />
			<CompareModal />
		</SwapWrap>
	);
};
