// Context is used for data passing as there are different launch points (homescreen and multi-token screen),
import { Trans } from "@lingui/react/macro";
import { type ReactNode, 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 { 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 { useFilteredTokens } from "../../hooks/useFilteredTokens";
import { NetworkList } from "./NetworkList";
import { NetworkSearchItem } from "./NetworkSearchItem";
import { NoTokens } from "./NoTokens";
import { SearchBar } from "./SearchBar";
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";

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

	const { accounts, isAnyConnected } = useAccounts();

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

	const isVisible = useSelector(
		(state: ReduxStateType) => state.tokenSelector.isVisible,
	);
	const locale = useSelector((state: ReduxStateType) => state.settings.language);
	const {
		receiver: recieverAddress,
		receiverENS,
		sender: senderAddress,
		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 [, getFallbackToken] = useFallbackToken({});

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

	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 { tokenResult: searchTokenResult, networkResult: searchNetworkResult } =
		useSearchTokens({
			searchValue: searchKeyword?.trim(),
			tokens: formattedTokenList ?? [],
			activeNetwork: typeof activeNetwork === "string" ? undefined : activeNetwork,
		});

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

	// damn, this file needs more comments

	// Filter out tokens found in searchTokenResult from apiTokenResult
	const filteredApiTokenResult = useMemo(() => {
		return (
			apiTokenResult?.filter(
				(apiToken) =>
					!searchTokenResult.some(
						(searchToken) =>
							searchToken.address.toLowerCase() === apiToken.address.toLowerCase() &&
							searchToken.chainId === apiToken.chainId,
					),
			) || []
		);
	}, [apiTokenResult, searchTokenResult]);

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

	// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
	const tokensToDisplay = useMemo(() => {
		if (searchKeyword) {
			const mappedFilteredApiTokenResult = filteredApiTokenResult.map((token) => ({
				...token,
				isImported: true,
			}));
			// Merge searchTokenResult with filteredApiTokenResult without duplicates and sort based off of activeNetwork, token name, token address, token symbol, balanceInUsd (highest priority!), isVerified (lowest priority)
			const sortedTokens = [
				...searchTokenResult,
				...mappedFilteredApiTokenResult,
			].sort((a, b) => {
				// Prioritize tokens with highest balance first
				if (a.balanceInUsd !== b.balanceInUsd)
					return (b.balanceInUsd || 0) - (a.balanceInUsd || 0);
				// Prioritize tokens from the active network by moving them to the top
				if (a.chainId === activeNetwork && b.chainId !== activeNetwork) return -1; // a is from active network, b isn't - move a up
				if (a.chainId !== activeNetwork && b.chainId === activeNetwork) return 1; // b is from active network, a isn't - move b up
				// First compare verification status
				if (a.isVerified !== b.isVerified) return a.isVerified ? -1 : 1;
				// For both verified and unverified tokens, sort by chain first
				if (a.chainId !== b.chainId) return a.chainId - b.chainId;
				// Then sort by name/symbol/address
				if (a.symbol !== b.symbol) return a.symbol.localeCompare(b.symbol);
				if (a.name !== b.name) return a.name.localeCompare(b.name);
				if (a.address !== b.address) return a.address.localeCompare(b.address);
				return 0;
			});

			// reduce to create {activeNetworkTokens: [tokens], remainingTokens: [tokens]}
			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,
		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 = (
		chainId: number,
		actionLabel?: string,
		action?: () => void,
	) => {
		return {
			// biome-ignore format:
			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,
		};
	};

	// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
	const sendOrReceive = useMemo<{
		label: string;
		address: string | null;
		walletIcon: string | undefined;
		actionLabel?: string;
		action?: () => void;
	}>(() => {
		const label = isSourceTokenSelector ? "Send from" : "Receive on";

		// If there's a hovered token, prioritize finding an account from the same chain
		if (hoveredTokenTokenChainId && hoveredTokenTokenChainId !== "all") {
			const accountFromSameChain = accounts.find(
				(account) =>
					isSolana(account.chainId || 0) === isSolana(hoveredTokenTokenChainId),
			);

			// If !isSourceTokenSelector, and recieverAddress exists, and it's from the same chain as the hovered token, return the reciever address
			const receiverAddressChainCompatible = recieverAddress
				? isPublicKey(recieverAddress) === isSolana(hoveredTokenTokenChainId)
				: false;

			if (
				!isSourceTokenSelector &&
				recieverAddress &&
				receiverAddressChainCompatible &&
				recieverAddress.toLowerCase() !== accountFromSameChain?.address?.toLowerCase()
			) {
				return {
					label,
					address: shortenAddress(recieverAddress || ""),
					walletIcon: isSolana(hoveredTokenTokenChainId)
						? SOLANA_WALLET_ICON
						: EVM_WALLET_ICON,
				};
			}

			if (accountFromSameChain) {
				return {
					label,
					address: shortenAddress(accountFromSameChain.address || ""),
					walletIcon: accountFromSameChain.connector?.icon,
				};
			}

			return {
				label,
				address: null,

				...actionItems(hoveredTokenTokenChainId),
			};
		}

		const activeTokenChainId = data?.activeToken?.chainId;
		const chainId =
			// biome-ignore lint/style/noNonNullAssertion: <explanation>
			activeNetwork === "all" ? activeTokenChainId! : activeNetwork;

		const matchingAccount = accounts.find((account) => {
			if (isSolana(chainId)) {
				return account.chainType === "SVM";
			}
			return account.chainType === "EVM";
		});

		// sending
		if (isSourceTokenSelector) {
			// If matching account exists, return it
			if (matchingAccount) {
				return {
					label,
					address: shortenAddress(matchingAccount.address || ""),
					walletIcon: matchingAccount.connector?.icon,
				};
			}
			return {
				label,
				address: null,
				...actionItems(chainId),
			};
		}
		// receiving
		// Check if the receiver address is valid for the current chain context
		const isReceiverAddressChainCompatible = recieverAddress
			? isPublicKey(recieverAddress) === isSolana(chainId)
			: false;

		if (isReceiverAddressChainCompatible) {
			return {
				label,
				address: shortenAddress(recieverAddress || ""),
				walletIcon:
					recieverAddress?.toLowerCase() ===
					(matchingAccount?.address || "").toLowerCase()
						? matchingAccount?.connector?.icon
						: isSolana(chainId)
							? SOLANA_WALLET_ICON
							: EVM_WALLET_ICON,
			};
		}
		// If matching account exists, return it
		if (matchingAccount) {
			return {
				label,
				address: shortenAddress(matchingAccount.address || ""),
				walletIcon: matchingAccount.connector?.icon,
			};
		}

		return {
			label,
			address: null,
			...actionItems(chainId),
		};
	}, [
		isSourceTokenSelector,
		senderAddress,
		recieverAddress,
		accounts,
		hoveredTokenTokenChainId,
		data?.activeToken?.chainId,
		activeNetwork,
	]);

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

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

			if (!isSolana(chainId) && 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;
	}

	function updateTokenWithVerification(
		token: Token,
		existingToken: Token,
		action: (token: Token) => void,
	) {
		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));
			updateTokenWithVerification(
				token,
				destinationTokens[tokenId],
				(verifiedToken) => {
					if (verifiedToken) {
						dispatch(addOutputToken({ id: tokenId, token: verifiedToken }));
					}
				},
			);
		} else {
			dispatch(addOutputToken({ id: tokenId, token }));
			updateTokenWithVerification(token, sourceTokens[tokenId], (verifiedToken) =>
				dispatch(setFromTokens(verifiedToken)),
			);
		}

		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 "Your Tokens";
		}

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

		// biome-ignore format:
		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">
					{sendOrReceive ? (
						<p className="bn-text-base bn-flex bn-items-center bn-space-x-1">
							<span>
								<span className="bn-font-medium bn-text-primary">{sendOrReceive.label}:</span>{" "}
								<span className="bn-font-semibold bn-text-secondary">
									{sendOrReceive.address}
								</span>
								{sendOrReceive.actionLabel && (
									// biome-ignore lint/a11y/useKeyWithClickEvents: <explanation>
									<span
										className="bn-text-theme bn-font-medium bn-underline bn-cursor-pointer"
										onClick={sendOrReceive.action}
									>
										{sendOrReceive.actionLabel}
									</span>
								)}
							</span>
							<span className="bn-size-5">
								{sendOrReceive.walletIcon ? (
									<img
										src={sendOrReceive.walletIcon}
										alt="connector"
										className="bn-rounded-full bn-size-4 bn-mr-1"
									/>
								) : (
									<MIcon name="wallet" classNames="bn-text-xl bn-text-theme" />
								)}
							</span>
						</p>
					) : (
						<div />
					)}
					<CloseButton onClick={closePanel} />
				</div>
				<div className="bn-flex bn-items-center bn-w-full bn-px-4 bn-pt-4">
					<SearchBar setValue={setSearchKeyword} />
				</div>

				{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">
							<Trans>Trending Tokens</Trans>
						</h4>
						<div className="bn-flex bn-w-full bn-overflow-x-auto bn-items-center bn-pb-1">
							{trendingTokens?.map((token) => (
								<TokenChip
									token={token}
									onClick={() => setToken(token)}
									classNames="bn-mx-[2.5px]"
									// biome-ignore format:
									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">
								<Trans>Chains</Trans>
							</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>{getTokenTitle()}</SectionLabel>
								<TokensSection
									tokens={tokensToDisplay}
									activeToken={data?.activeToken}
									isSearching={isSearchingTokens}
									showNoTokensWarning={!tokensToDisplay?.length}
									setToken={(value) => setToken(value, !!value.isImported)}
									onAssetHoverIn={handleAssetHoverIn}
									onAssetHoverOut={handleAssetHoverOut}
									showChainDetails={showChainDetails}
									shouldDisable={shouldDisable}
								/>
							</>
						) : (
							<>
								{!!tokensToDisplay.activeNetworkTokens?.length && (
									<div className="bn-pb-2.5 bn-border-b bn-border-[#1F2023]">
										<SectionLabel>{getTokenTitle()}</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}
											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}
											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 }: { children: ReactNode }) => {
	return <p className="bn-py-3 bn-px-4 bn-text-secondary text-sm">{children}</p>;
};

/**
 * @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
 */
