// Context is used for data passing as there are different launch points (homescreen and multi-token screen),
import {
	type ReactNode,
	useCallback,
	useEffect,
	useMemo,
	useState,
} from "react";
import { useDispatch, useSelector } from "react-redux";
import { useSwitchChain } from "wagmi";

import type { Token } from "@/types/index";
import { useFallbackToken } from "@components/Home/Output/useFallbackToken";
import { MIcon } from "@components/common/MIcon";
import { SlideInPanel } from "@components/common/SlideInPanel";
import { TokenChip } from "@components/common/TokenChip";
import { useAccounts } from "@hooks/useAccounts";
import { useConfig } from "@hooks/useConfig";
import { useFilteredTokens } from "@hooks/useFilteredTokens";
import { useLogEvent } from "@hooks/useLogEvent";
import { useMappedChainData } from "@hooks/useMappedChainData";
import { useSearch } from "@hooks/useSearch";
import { useSearchTokens } from "@hooks/useSearchTokens";
import { importToken } from "@store/globalSlice";
import {
	addOutputToken,
	setFromChainId,
	setFromTokens,
	setToChainId,
} from "@store/quoteParamSlice";
import { setTokenSelectorData } from "@store/tokenSelectorSlice";
import type { ReduxStateType } from "@store/types";
import { getFormattedTokenList } from "@utils/getFormattedTokenList";
import { isPublicKey } from "@utils/isPublicKey";
import { isSolana } from "@utils/isSolana";
import { shortenAddress } from "@utils/shortenAddress";
import { createFilteredTokensSet, sortTokens } from "@utils/sortTokens";
import { twMerge } from "@utils/tailwindMerge";
import { NetworkList } from "./NetworkList";
import { NetworkListSkeleton } from "./NetworkListSkeleton";
import { NetworkSearchItem } from "./NetworkSearchItem";
import { NoTokens } from "./NoTokens";
import { SearchBar } from "./SearchBar";
import { TokenChipSkeleton } from "./TokenChipSkeleton";
import { TokensSection } from "./TokensSection";
import { useTrendingTokens } from "./useTrendingTokens";

const SOLANA_WALLET_ICON = "https://media.socket.tech/networks/solana.svg";
const EVM_WALLET_ICON = "https://media.socket.tech/networks/ethereum.svg";

const TRENDING_SKELETON_IDS = [
	"trending-skeleton-0",
	"trending-skeleton-1",
	"trending-skeleton-2",
	"trending-skeleton-3",
	"trending-skeleton-4",
] as const;

export const TokenSelector = () => {
	const dispatch = useDispatch();

	const { accounts, isAnyConnected } = useAccounts();

	const isVisible = useSelector(
		(state: ReduxStateType) => state.tokenSelector.isVisible,
	);
	const {
		receiver: recieverAddress,
		toTokens: destinationTokens,
		fromTokens: sourceTokens,
	} = useSelector((state: ReduxStateType) => state.quoteParams);
	const data = useSelector((state: ReduxStateType) => state.tokenSelector.data);
	const mappedChainData = useMappedChainData();
	const { switchChain } = useSwitchChain();
	const { eventHandlers } = useConfig();
	const { logEvent } = useLogEvent();

	const [, getFallbackToken] = useFallbackToken({});

	const chains = data?.chains;
	const isSourceTokenSelector = data?.isSourceTokenSelector;
	const isLoading = data?.isLoading ?? false;

	const [activeNetwork, setActiveNetwork] = useState<number | "all">("all");
	const [searchKeyword, setSearchKeyword] = useState<string>(""); //@fixme: temporary
	const [formattedTokenList, setFormattedTokenList] = useState<Token[]>();
	const [hoveredTokenTokenChainId, setHoveredTokenChainId] = useState<
		"all" | number | null
	>(null);

	const allTokens = useMemo(() => data?.tokens, [data]);

	const trendingTokens = useTrendingTokens({
		tokens: allTokens?.[activeNetwork],
	});

	useEffect(() => {
		if (allTokens) {
			setFormattedTokenList(getFormattedTokenList(allTokens));
		}
	}, [allTokens]);

	const { data: apiTokenResult, isLoading: isSearchingTokens } = useSearch({
		keyword: searchKeyword,
	});

	const filteredTokenResult = useMemo(() => {
		const apiTokenWithImportTag = apiTokenResult?.map((token) => ({
			...token,
			isImported: true,
		}));

		if (!formattedTokenList) {
			return apiTokenWithImportTag || [];
		}

		const mergedTokens = [...formattedTokenList];

		for (const apiToken of apiTokenWithImportTag) {
			const isDuplicate = mergedTokens.some(
				(token) =>
					token.address.toLowerCase() === apiToken.address.toLowerCase() &&
					token.chainId === apiToken.chainId,
			);
			if (!isDuplicate) {
				mergedTokens.push(apiToken);
			}
		}

		return mergedTokens;
	}, [apiTokenResult, formattedTokenList]);

	const { tokenResult: searchTokenResult, networkResult: searchNetworkResult } =
		useSearchTokens({
			searchValue: searchKeyword?.trim(),
			tokens: filteredTokenResult || [],
			activeNetwork:
				typeof activeNetwork === "string" ? undefined : activeNetwork,
		});

	const filteredTokens = useFilteredTokens({
		allTokens: allTokens ?? {},
		formattedTokenList: formattedTokenList ?? [],
		activeNetwork: activeNetwork,
	});

	// Pre-compute filteredTokens Set for O(1) lookup during sorting
	const filteredTokensSet = useMemo(() => {
		if (filteredTokens && filteredTokens.length > 0) {
			return createFilteredTokensSet(filteredTokens);
		}
		return undefined;
	}, [filteredTokens]);

	// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
	const tokensToDisplay = useMemo(() => {
		if (searchKeyword) {
			// Use optimized sort function with pre-computed metadata
			// This eliminates redundant calculations and improves performance
			const sortedTokens = sortTokens(searchTokenResult, {
				searchKeyword,
				activeNetwork,
				filteredTokensSet,
			});

			return sortedTokens.reduce<{
				activeNetworkTokens: Token[];
				remainingTokens: Token[];
			}>(
				(acc, token) => {
					if (activeNetwork === "all" && !!token.balanceInUsd) {
						acc.activeNetworkTokens.push(token);
						return acc;
					}
					if (token.chainId === activeNetwork) {
						acc.activeNetworkTokens.push(token);
					} else {
						acc.remainingTokens.push(token);
					}
					return acc;
				},
				{ activeNetworkTokens: [], remainingTokens: [] },
			);
		}
		if (filteredTokens?.length) return filteredTokens;
		return [];
	}, [
		activeNetwork,
		apiTokenResult,
		filteredTokens,
		searchKeyword,
		searchTokenResult,
		filteredTokensSet,
		isVisible,
	]);

	const multichainBalance = useMemo(() => {
		if (allTokens && isAnyConnected) {
			const _formattedList = getFormattedTokenList(allTokens);
			return _formattedList.reduce(
				(balance, token) => balance + (token?.balanceInUsd || 0),
				0,
			);
		}
		return 0;
	}, [allTokens, isAnyConnected]);

	useEffect(() => {
		if (data?.activeChain === "all") {
			setActiveNetwork("all");
			setHoveredTokenChainId("all");
		} else if (data?.activeChain) {
			setActiveNetwork(data.activeChain.chainId);
			setHoveredTokenChainId(data.activeChain.chainId);
		}
	}, [data?.activeChain]);

	const actionItems = useCallback(
		(chainId: number, actionLabel?: string, action?: () => void) => {
			return {
				actionLabel:
					actionLabel ||
					`Connect ${isSolana(chainId) ? "Solana" : "EVM"} Wallet`,
				action:
					action ||
					(() =>
						eventHandlers?.onOpenWalletConnect?.(
							isSolana(chainId) ? "solana" : "eip155",
						)),

				walletIcon: isSolana(chainId) ? SOLANA_WALLET_ICON : EVM_WALLET_ICON,
			};
		},
		[eventHandlers?.onOpenWalletConnect],
	);

	const sendOrReceive = useMemo<{
		label: string | null;
		address: string | null;
		walletIcon: string | undefined;
		actionLabel?: string;
		action?: () => void;
	}>(() => {
		// Handle "all" states first
		if (hoveredTokenTokenChainId === "all" || activeNetwork === "all") {
			return {
				label: "All Wallets",
				address: null,
				walletIcon: undefined,
			};
		}

		const targetChainId = hoveredTokenTokenChainId || activeNetwork;
		const isTargetSolana = isSolana(targetChainId);

		// Find matching account for the target chain
		const matchingAccount = accounts.find((account) =>
			isTargetSolana
				? account.chainType === "SVM"
				: account.chainType === "EVM",
		);

		// For receiving: check if receiver address is compatible and different from user's account
		if (!isSourceTokenSelector && recieverAddress) {
			const isReceiverCompatible =
				isPublicKey(recieverAddress) === isTargetSolana;
			const isDifferentFromUserAccount =
				recieverAddress.toLowerCase() !==
				matchingAccount?.address?.toLowerCase();

			if (isReceiverCompatible && isDifferentFromUserAccount) {
				return {
					label: shortenAddress(recieverAddress),
					address: recieverAddress,
					walletIcon: isTargetSolana ? SOLANA_WALLET_ICON : EVM_WALLET_ICON,
				};
			}
		}

		// Use matching account if available
		if (matchingAccount) {
			return {
				label: shortenAddress(matchingAccount.address || ""),
				address: matchingAccount.address || "",
				walletIcon: matchingAccount.connector?.icon,
			};
		}

		// Fallback to action items (connect wallet, etc.)
		return {
			label: null,
			address: null,
			...actionItems(targetChainId),
		};
	}, [
		isSourceTokenSelector,
		recieverAddress,
		accounts,
		hoveredTokenTokenChainId,
		activeNetwork,
		actionItems,
	]);

	function closePanel() {
		dispatch(setTokenSelectorData({ isVisible: false, data: null }));
	}

	function setChain(chainId: number) {
		if (isSourceTokenSelector) {
			dispatch(setFromChainId(chainId));

			const evmAccountChain = accounts.find(
				(account) => account.chainType === "EVM",
			)?.chainId;

			if (
				!isSolana(chainId) &&
				evmAccountChain !== undefined &&
				evmAccountChain !== chainId
			) {
				switchChain({ chainId: chainId });
			}
		} else {
			dispatch(setToChainId(chainId));
		}
	}

	function verifyToken(token: Token, compareToken: Token) {
		if (!compareToken || !token) return token;

		const fallbackToken = getFallbackToken(token, compareToken);

		return fallbackToken ?? token;
	}

	/** to update the corresponding (from/to) token if the tokens are same */
	function updateTokenWithVerification(
		token: Token,
		existingToken: Token | undefined,
		action: (token: Token) => void,
	) {
		// Only update if existing token exists
		if (!existingToken) {
			return;
		}

		// Check if tokens are the same (same address and same chain)
		const sameTokens =
			existingToken.address.toLowerCase() === token.address.toLowerCase() &&
			existingToken.chainId === token.chainId;

		// Only update if tokens are the same
		// The fallback token getter will handle same-chain logic internally
		if (sameTokens) {
			const verifiedToken = verifyToken(existingToken, token);
			action(verifiedToken);
		}
	}

	function setToken(token: Token, importedToken?: boolean) {
		const tokenId = data?.id || 0;

		if (importedToken) {
			dispatch(importToken({ token }));
		}

		setChain(token.chainId);

		if (isSourceTokenSelector) {
			dispatch(setFromTokens(token));
			// Check if source token matches destination token (same address and chain)
			// If yes, switch destination token to fallback
			const existingDestinationToken = destinationTokens[tokenId];
			updateTokenWithVerification(
				token,
				existingDestinationToken,
				(verifiedToken) => {
					if (verifiedToken) {
						dispatch(addOutputToken({ id: tokenId, token: verifiedToken }));
					}
				},
			);
		} else {
			dispatch(addOutputToken({ id: tokenId, token }));
			// Check if destination token matches source token (same address and chain)
			// If yes, switch source token to fallback
			const existingSourceToken = sourceTokens[tokenId];
			updateTokenWithVerification(token, existingSourceToken, (verifiedToken) =>
				dispatch(setFromTokens(verifiedToken)),
			);
		}

		// Emit event -- token_selected
		logEvent("token_selected", {
			token_address: token.address,
			token_chain_id: token.chainId,
			token_symbol: token.symbol,
			side: isSourceTokenSelector ? "source" : "destination",
		});

		closePanel();
	}

	const handleAssetHoverIn = (token: Token) => {
		setHoveredTokenChainId(token.chainId);
	};
	const handleAssetHoverOut = () => {
		setHoveredTokenChainId(null);
	};

	function getTokenTitle(otherTokens?: boolean) {
		if (activeNetwork === "all") {
			if (otherTokens) {
				return "Other Tokens";
			}
			return "All Wallets";
		}

		if (otherTokens) {
			return "Other Tokens";
		}

		return `Tokens on ${mappedChainData?.[activeNetwork]?.name}`;
	}

	// To disabled tokens in the token list
	function shouldDisable(token: Token) {
		const tokensForChain = data?.tokensToDisable?.[token.chainId];

		if (tokensForChain) {
			return Boolean(
				tokensForChain.find(
					(address) => address.toLowerCase() === token.address.toLowerCase(),
				),
			);
		}
		return false;
	}

	const showChainDetails =
		activeNetwork === "all" || Boolean(searchKeyword?.length);

	return (
		<SlideInPanel
			isVisible={isVisible}
			closePanel={closePanel}
			cardClasses="bn-h-full"
			trayClasses="bn-border-none"
		>
			<div className="bn-flex bn-flex-col bn-max-h-full bn-h-full">
				<div className="bn-flex bn-items-center bn-justify-between bn-w-full bn-px-4 bn-pt-4 bn-space-x-2">
					<p className="bn-text-base bn-font-medium bn-text-primary">
						Select Token
					</p>
					<CloseButton onClick={closePanel} />
				</div>
				<div className="bn-flex bn-items-center bn-w-full bn-px-4 bn-pt-4">
					<SearchBar setValue={setSearchKeyword} />
				</div>

				{isLoading && (!chains || chains.length === 0) ? (
					<NetworkListSkeleton
						showAllNetworksBtn={isSourceTokenSelector && !!multichainBalance}
					/>
				) : (
					chains && (
						<NetworkList
							chains={chains}
							activeNetwork={activeNetwork}
							setActiveNetwork={(network) => {
								setActiveNetwork(network);
								setHoveredTokenChainId(network);
							}}
							// hiding all networks when wallet not connected (or wallet is empty)
							showAllNetworksBtn={isSourceTokenSelector && !!multichainBalance}
						/>
					)
				)}

				{!isSourceTokenSelector && (
					<div className="bn-px-4 bn-mb-3">
						<h4 className="bn-text-sm bn-font-normal bn-text-secondary bn-mb-2">
							Trending Tokens
						</h4>
						<div className="bn-flex bn-w-full bn-overflow-x-auto bn-items-center bn-pb-1">
							{isLoading && (!trendingTokens || trendingTokens.length === 0) ? (
								<>
									{TRENDING_SKELETON_IDS.map((id) => (
										<TokenChipSkeleton
											key={id}
											showChain
											classNames="bn-mx-[2.5px]"
										/>
									))}
								</>
							) : (
								trendingTokens?.map((token) => (
									<TokenChip
										token={token}
										onClick={() => setToken(token)}
										classNames="bn-mx-[2.5px]"
										key={`${token.address}-${token.chainId}`}
										showChain
										disabled={shouldDisable(token)}
									/>
								))
							)}
						</div>
					</div>
				)}

				<div className="bn-flex-1 bn-overflow-y-hidden bn-border-t bn-border-0 bn-border-subtle bn-border-solid bn-flex bn-flex-col bn-w-full bn-overflow-auto">
					{!!searchNetworkResult?.length && searchKeyword && (
						<>
							<h4 className="bn-text-sm bn-font-normal bn-text-secondary bn-mb-2 bn-px-4 bn-mt-3">
								Chains
							</h4>
							{searchNetworkResult.map((item) => {
								return (
									<NetworkSearchItem
										key={item.chainId}
										chain={item}
										onClick={() => {
											setActiveNetwork(item.chainId);
											setSearchKeyword("");
										}}
										assetsLength={allTokens?.[item.chainId]?.length ?? 0}
									/>
								);
							})}
						</>
					)}
					<div className="bn-flex-1 bn-overflow-auto bn-w-full bn-overflow-x-hidden">
						{Array.isArray(tokensToDisplay) ? (
							<>
								<SectionLabel
									className={twMerge(
										"bn-flex bn-items-center bn-gap-1",
										sendOrReceive.action &&
											"bn-text-theme bn-font-medium bn-underline bn-cursor-pointer",
									)}
									onClick={sendOrReceive.action}
								>
									<span>
										{sendOrReceive.actionLabel || sendOrReceive.label}
									</span>
									{sendOrReceive.walletIcon && (
										<img
											src={sendOrReceive.walletIcon}
											alt="connector"
											className="bn-rounded-full bn-size-4 bn-mr-1"
										/>
									)}
								</SectionLabel>
								<TokensSection
									tokens={tokensToDisplay}
									activeToken={data?.activeToken}
									isSearching={isSearchingTokens}
									showNoTokensWarning={!tokensToDisplay?.length}
									setToken={(value) => setToken(value, !!value.isImported)}
									onAssetHoverIn={handleAssetHoverIn}
									onAssetHoverOut={handleAssetHoverOut}
									showChainDetails={showChainDetails}
									isLoading={isLoading}
									shouldDisable={shouldDisable}
								/>
							</>
						) : (
							<>
								{!!tokensToDisplay.activeNetworkTokens?.length && (
									<div className="bn-pb-2.5 bn-border-b bn-border-[#1F2023]">
										<SectionLabel
											className={twMerge(
												"bn-flex bn-items-center bn-gap-1",
												sendOrReceive.action &&
													"bn-text-theme bn-font-medium bn-underline bn-cursor-pointer",
											)}
											onClick={sendOrReceive.action}
										>
											<span>
												{sendOrReceive.actionLabel || sendOrReceive.label}
											</span>
											{sendOrReceive.walletIcon && (
												<img
													src={sendOrReceive.walletIcon}
													alt="connector"
													className="bn-rounded-full bn-size-4 bn-mr-1"
												/>
											)}
										</SectionLabel>
										<TokensSection
											tokens={tokensToDisplay.activeNetworkTokens}
											activeToken={data?.activeToken}
											isSearching={isSearchingTokens}
											showNoTokensWarning={
												!tokensToDisplay.activeNetworkTokens?.length
											}
											setToken={(value) => setToken(value, !!value.isImported)}
											onAssetHoverIn={handleAssetHoverIn}
											onAssetHoverOut={handleAssetHoverOut}
											showChainDetails={showChainDetails}
											isLoading={isLoading}
											shouldDisable={shouldDisable}
										/>
									</div>
								)}

								{!!tokensToDisplay.remainingTokens?.length && (
									<>
										<SectionLabel>{getTokenTitle(true)}</SectionLabel>
										<TokensSection
											tokens={tokensToDisplay.remainingTokens}
											activeToken={data?.activeToken}
											setToken={(value) => setToken(value, !!value.isImported)}
											onAssetHoverIn={handleAssetHoverIn}
											onAssetHoverOut={handleAssetHoverOut}
											showChainDetails={showChainDetails}
											isLoading={isLoading}
											shouldDisable={shouldDisable}
										/>
									</>
								)}

								{isSearchingTokens ? (
									<SectionLabel>Searching tokens</SectionLabel>
								) : !tokensToDisplay.activeNetworkTokens?.length &&
									!tokensToDisplay.remainingTokens?.length ? (
									<NoTokens />
								) : null}
							</>
						)}
					</div>
				</div>
			</div>
		</SlideInPanel>
	);
};

const CloseButton = ({ onClick }: { onClick: () => void }) => {
	return (
		<button
			className="bn-w-6 bn-h-6 bn-cursor-pointer bn-text-primary"
			type="button"
			onClick={onClick}
		>
			<MIcon name="close" classNames="bn-text-2xl" />
		</button>
	);
};

const SectionLabel = ({
	children,
	className,
	onClick,
}: {
	children: ReactNode;
	className?: string;
	onClick?: () => void;
}) => {
	return (
		<div
			className={twMerge(
				"bn-py-3 bn-px-4 bn-text-secondary text-base",
				className,
			)}
			onKeyUp={() => {}}
			onClick={onClick}
		>
			{children}
		</div>
	);
};

/**
 * @todo
 * 1. Abstract away functions
 * 2. Abstract the search logic
 * 3. Sorting for all balance
 * 4. Check multi-chain balance thing
 * 5. Introduce cards & conditionally render them
 */
