// Context is used for data passing as there are different launch points (homescreen and multi-token screen),
import { Trans } from "@lingui/macro";
import type React from "react";
import {
	type MutableRefObject,
	useEffect,
	useMemo,
	useRef,
	useState,
} from "react";
import { useDispatch, useSelector } from "react-redux";
import { AutoSizer, List, type ListRowProps } from "react-virtualized";
import { useAccount } from "wagmi";

import { TokenItem } from "@components/TokenItem";
import { MIcon } from "@components/common/MIcon";
import { SlideInPanel } from "@components/common/SlideInPanel";
import { TokenChip } from "@components/common/TokenChip";
import { useImportToken } from "@hooks/apis/useImportToken";
import { useMappedChainData } from "@hooks/useMappedChainData";
import { useSearchTokens } from "@hooks/useSearchTokens";
import { useTheme } from "@hooks/useTheme";
import {
	addOutputToken,
	setFromChainId,
	setFromTokens,
	setToChainId,
} from "@store/quoteParamSlice";
import { setTokenSelectorData } from "@store/tokenSelectorSlice";
import type { ReduxStateType } from "@store/types";
import { adjustDecimals } from "@utils/adjustDecimals";
import { getBorderRadius } from "@utils/getBorderRadius";
import type { Token } from "../../Providers/ConfigProvider";
import { useFilteredTokens } from "../../hooks/useFilteredTokens";
import { ImportToken } from "./ImportToken";
import { NetworkList } from "./NetworkList";
import { NetworkSearchItem } from "./NetworkSearchItem";
import { NoTokens } from "./NoTokens";
import { SearchBar } from "./SearchBar";
import { useTrendingTokens } from "./useTrendingTokens";

/**
 * Function to convert tokens from {id: Token[]} to Token[] format
 * @returns Token[]
 */
export function getFormattedTokenList(tokens: { [chainId: string]: Token[] }) {
	const _tokens = Object.values(tokens ?? []);
	if (_tokens) {
		return _tokens.reduce((newArr, currentArr) => newArr.concat(...currentArr), []);
	}
	return [];
}

export const TokenSelector = () => {
	const dispatch = useDispatch();
	const { isConnected } = useAccount();
	const isVisible = useSelector(
		(state: ReduxStateType) => state.tokenSelector.isVisible,
	);
	const locale = useSelector((state: ReduxStateType) => state.settings.language);
	const data = useSelector((state: ReduxStateType) => state.tokenSelector.data);
	const mappedChainData = useMappedChainData();

	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 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 filteredTokens = useFilteredTokens({
		allTokens: allTokens ?? {},
		formattedTokenList: formattedTokenList ?? [],
		activeNetwork: activeNetwork,
	});

	// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
	const tokensToDisplay = useMemo(() => {
		if (searchKeyword) return searchTokenResult;
		if (filteredTokens?.length) return filteredTokens;
		return [];
	}, [filteredTokens, searchKeyword, searchTokenResult, isVisible]);

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

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

	const { data: importTokenData, isLoading: isSearchingForToken } =
		useImportToken({
			tokenAddress: searchKeyword,
			shouldFetch: Boolean(searchKeyword && tokensToDisplay?.length === 0),
		});

	// to get usd summation of the network balance
	const getCurrentChainBalance = useMemo(() => {
		if (tokensToDisplay?.length && isConnected) {
			return tokensToDisplay.reduce(
				(balance, token) => balance + (token?.balanceInUsd || 0),
				0,
			);
		}
		return null;
	}, [tokensToDisplay, isConnected]);

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

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

	function setToken(token: Token) {
		if (isSourceTokenSelector) {
			dispatch(setFromTokens(token));
			setChain(token.chainId);
			closePanel();
		} else {
			dispatch(addOutputToken({ id: data?.id || 0, token }));
			setChain(token.chainId);
			closePanel();
		}
	}

	function getTokenTitle() {
		if (!tokensToDisplay?.length) return null;
		if (activeNetwork === "all") return "Tokens";

		// biome-ignore format:
		return `Tokens on ${mappedChainData?.[activeNetwork]?.name}`;
	}

	function renderNoTokensComponents() {
		const _importedToken =
			Boolean(importTokenData) && Object.values(importTokenData).flat();
		if (_importedToken) {
			return (
				<ImportToken
					tokens={_importedToken}
					closePanel={() => {
						setSearchKeyword("");
					}}
					isSourceTokenSelector={Boolean(isSourceTokenSelector)}
				/>
			);
		}

		if (isSearchingForToken)
			return (
				<div className="bn-px-4 bn-items-center bn-mt-3 bn-text-secondary bn-text-[13px] bn-font-medium">
					Searching for tokens...
				</div>
			);
		if (tokensToDisplay?.length === 0 && searchKeyword) return <NoTokens />;
		return null;
	}

	// 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(),
				),
			);
		}
	}

	const rowRenderer = ({ index, style, parent }: ListRowProps) => {
		const token = tokensToDisplay?.[index];
		if (!token) return null;

		const chainName = mappedChainData?.[token?.chainId]?.name;

		return (
			<div
				// biome-ignore format:
				key={`${token.address}-${token.chainId}`}
				style={style}
			>
				<TokenItem
					token={token}
					// biome-ignore format:
					chainName={
						activeNetwork === "all" || searchKeyword ? `on ${chainName}` : undefined
					}
					onClick={() => setToken(token)}
					showChainIcon={Boolean(activeNetwork === "all" || searchKeyword?.length)}
					active={deepTokenComparison(token, data?.activeToken)}
					disabled={shouldDisable(token)}
				/>
			</div>
		);
	};

	const [rowHeight, setRowHeight] = useState<number>(54);
	const dummyTokenRef = useRef() as MutableRefObject<HTMLDivElement>;
	const dummyToken = tokensToDisplay?.[0];

	// biome-ignore lint/correctness/useExhaustiveDependencies: <explanation>
	useEffect(() => {
		const elHeight = dummyTokenRef?.current?.clientHeight;
		if (elHeight) {
			setRowHeight(elHeight);
		}
	}, [dummyTokenRef, isVisible]);

	return (
		<SlideInPanel
			isVisible={isVisible}
			closePanel={closePanel}
			cardClasses="bn-h-full"
		>
			<div className="bn-flex bn-flex-col bn-max-h-full bn-h-full">
				<div className="bn-flex bn-items-center bn-w-full bn-px-4 bn-pt-4">
					<SearchBar setValue={setSearchKeyword} />
					<CloseButton onClick={closePanel} />
				</div>

				{chains && (
					<NetworkList
						chains={chains}
						activeNetwork={activeNetwork}
						setActiveNetwork={setActiveNetwork}
						// hiding all networks when wallet not connected (or wallet is empty)
						showAllNetworksBtn={isSourceTokenSelector && !!multichainBalance}
					/>
				)}

				{isSourceTokenSelector && getCurrentChainBalance ? (
					<div className="bn-px-4 bn-mb-[18px]">
						<p className="bn-text-secondary bn-text-[13px] bn-font-normal">
							<Trans>My Wallet Balance</Trans>
						</p>
						<p className="bn-text-primary bn-text-xl bn-font-medium bn-aeonik-font bn-mt-1">
							${adjustDecimals(getCurrentChainBalance, 2, locale)}
						</p>
					</div>
				) : null}

				{!isSourceTokenSelector && (
					<div className="bn-px-4 bn-mb-3">
						<h4 className="bn-text-[13px] 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-primary bn-border-solid bn-flex bn-flex-col bn-w-full bn-h-full">
					{!!searchNetworkResult?.length && searchKeyword && (
						<>
							<h4 className="bn-text-[13px] 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}
									/>
								);
							})}
						</>
					)}

					{!!tokensToDisplay?.length && (
						<>
							<h4 className="bn-text-[13px] bn-font-normal bn-text-secondary bn-mb-2 bn-px-4 bn-mt-3">
								{getTokenTitle()}
							</h4>

							<div className="bn-flex-1 bn-w-full bn-h-full">
								<AutoSizer>
									{({ width, height }) => (
										<List
											width={width}
											height={height}
											rowCount={tokensToDisplay?.length || 0}
											rowRenderer={rowRenderer}
											rowHeight={rowHeight}
										/>
									)}
								</AutoSizer>
							</div>
						</>
					)}

					{/* dummy item for height */}
					{dummyToken && (
						<div
							ref={dummyTokenRef}
							className="bn-w-fit bn-h-fit bn-invisible bn-absolute bn-bottom-0 bn-right-0"
						>
							<TokenItem token={dummyToken} onClick={() => {}} />
						</div>
					)}

					{renderNoTokensComponents()}
				</div>
			</div>
		</SlideInPanel>
	);
};

function deepTokenComparison(
	tokenA: Token | null | undefined,
	tokenB: Token | null | undefined,
) {
	if (!tokenA || !tokenB || tokenA.chainId !== tokenB.chainId) return false;

	return tokenA.address.toLowerCase() === tokenB.address.toLowerCase();
}

const CloseButton = ({ onClick }: { onClick: () => void }) => {
	const theme = useTheme();
	return (
		<button
			className="bn-w-10 bn-h-10 bn-bg-transparent bn-ml-2 bn-cursor-pointer bn-flex bn-items-center bn-justify-center bn-border bn-border-solid bn-border-primary bn-text-secondary"
			type="button"
			onClick={onClick}
			style={{ borderRadius: getBorderRadius(theme.borderRadius, 8) }}
		>
			<MIcon name="close" />
		</button>
	);
};

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