// @fixme: Check for unnecessary rerenders
import { useDebounce } from "@uidotdev/usehooks";
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useDispatch, useSelector } from "react-redux";

import { Amount } from "@components/common/Amount";
import { Balance } from "@components/common/Balance";
import { UsdValue } from "@components/common/UsdValue";
import { useFetchQuote } from "@hooks/apis/useFetchQuote";
import { useConfig } from "@hooks/useConfig";
import { useMappedChainData } from "@hooks/useMappedChainData";
import { useTokenBalance } from "@hooks/useTokenBalance";
import type { SupportedLocale } from "@providers/LocalizationProvider";
import { setAmount } from "@store/quoteParamSlice";
import type { ReduxStateType } from "@store/types";
import { adjustDecimals } from "@utils/adjustDecimals";
import { NATIVE_TOKEN_ADDRESS } from "@utils/constants";
import { formatValue } from "@utils/formatValue";
import { parseValue } from "@utils/parseValue";
import { truncateInputValue } from "@utils/truncateInputValue";
import { InputToken } from "./InputToken";

const localeUsesComma = (locale: SupportedLocale) => {
	const separator = new Intl.NumberFormat(locale).format(1.1)[1];
	return separator === ",";
};

const formatValueWithLocale = (value: string, locale: SupportedLocale) => {
	if (localeUsesComma(locale)) return value?.toString().replace(/\./g, ",");
	return value?.replace(/,/g, ".");
};

export const Input = () => {
	const dispatch = useDispatch();
	const mappedChainData = useMappedChainData();
	const fromTokens = useSelector(
		(state: ReduxStateType) => state.quoteParams.fromTokens,
	);
	const amountsFromStore = useSelector(
		(state: ReduxStateType) => state.quoteParams.amounts,
	);
	const locale = useSelector((state: ReduxStateType) => state.settings.language);
	const { data: quote } = useFetchQuote();
	const activeToken = useMemo(() => fromTokens?.[0], [fromTokens]);

	const [value, setValue] = useState<string>("");

	const debouncedInputValue = useDebounce(value, 500);
	const valueFormattedWithLocale = formatValueWithLocale(value, locale);

	// biome-ignore lint/correctness/useExhaustiveDependencies: no need to add more deps
	useEffect(() => {
		if (activeToken && debouncedInputValue) {
			const _value =
				truncateInputValue(debouncedInputValue, activeToken.decimals) ??
				debouncedInputValue;

			setValue(_value);

			const parsedValue = parseValue(_value, activeToken?.decimals);
			dispatch(setAmount(parsedValue));
		} else {
			dispatch(setAmount(undefined));
		}
	}, [activeToken, debouncedInputValue]);

	// resetting the amount from store on first render
	// this is done to retain the value between screen switches
	const firstRender = useRef(true);
	useEffect(() => {
		if (amountsFromStore?.length && activeToken && firstRender.current) {
			const _formattedValue = formatValue(amountsFromStore[0], activeToken.decimals);

			setValue(_formattedValue);
			firstRender.current = false;
		}
	}, [amountsFromStore, activeToken]);

	// note: max shouldn't set truncated values
	// This function sets the input value to the maximum allowable amount based on the user's token balance,
	// ensuring that it does not exceed the available balance after accounting for gas fees.
	function setMaxInput() {
		if (!activeToken) return;
		if (!tokenBalance) return;

		if (activeToken.address.toLowerCase() === NATIVE_TOKEN_ADDRESS) {
			const minNativeCurrencyForGas =
				mappedChainData?.[activeToken.chainId]?.currency?.minNativeCurrencyForGas ??
				"0";
			const subtractingGasFee =
				BigInt(tokenBalance) - BigInt(minNativeCurrencyForGas) * 2n;
			const _maxInput = formatValue(subtractingGasFee, activeToken.decimals);

			if (BigInt(subtractingGasFee) < 0n && activeTokenBalanceFormatted) {
				setValue(activeTokenBalanceFormatted?.toString());
			} else {
				setValue(_maxInput); // Set to _maxInput if not negative
			}
		} else if (activeTokenBalanceFormatted) {
			setValue(activeTokenBalanceFormatted.toString());
		}
	}

	function setInputAmount(value: string) {
		if (activeToken) {
			// Restricting input decimals as per token decimals
			const _value = truncateInputValue(value, activeToken.decimals) ?? value;
			setValue(_value);
		} else {
			setValue(value);
		}
	}

	const { balance: tokenBalance, isLoading: isFetchingBalance } = useTokenBalance(
		{ token: activeToken },
	);

	const activeTokenBalanceFormatted = useMemo(() => {
		if (tokenBalance && activeToken && !isFetchingBalance) {
			return formatValue(tokenBalance, activeToken.decimals);
		}
		return null;
	}, [activeToken, tokenBalance, isFetchingBalance]);

	function renderBalance() {
		if (isFetchingBalance)
			return (
				<div className="bn-h-[22px] bn-w-[100px] bn-animate-pulse bn-bg-layer-3" />
			);
		if (activeToken)
			return (
				<Balance
					balance={
						activeTokenBalanceFormatted
							? Number(activeTokenBalanceFormatted).toFixed(4)
							: "0.00"
					}
					setMax={setMaxInput}
				/>
			);
	}

	return (
		<div className="bn-px-[18px] bn-border-0 bn-border-b bn-border-solid bn-border-b-primary bn-pb-[35px]">
			<div className="bn-flex bn-items-center bn-justify-between bn-w-full">
				<Amount amount={valueFormattedWithLocale} setAmount={setInputAmount} />
				<InputToken />
			</div>
			{/* biome-ignore format: */}
			<div
				className={`bn-flex bn-items-center bn-w-full bn-mt-2.5 bn-h-5 ${
				quote ? "bn-justify-between" : "bn-justify-end"
				}`}
			>
				<SourceUsdValue />
				{renderBalance()}
			</div>
		</div>
	);
};

const SourceUsdValue = () => {
	const { data: quoteData } = useFetchQuote();
	const bungeeConfig = useConfig();
	const locale = useSelector((state: ReduxStateType) => state.settings.language);
	if (!quoteData) return null;

	const { primaryInput, primaryOutput } = quoteData;
	if (!primaryInput?.valueInUsd || !bungeeConfig?.maxCap) return;

	const maxCap = bungeeConfig?.maxCap;
	const isSameChainSwap = primaryInput?.chainId === primaryOutput?.chainId;

	const exceedsCap = Boolean(primaryInput.valueInUsd > maxCap); // @note: temporary cap on input amounts

	return (
		<div className="bn-flex bn-items-center bn-space-x-1">
			<UsdValue value={adjustDecimals(primaryInput.valueInUsd, 2, locale)} />
			{exceedsCap && !isSameChainSwap && (
				<span className="bn-text-fail bn-text-sm bn-font-medium bn-aeonik-font bn-leading-4">
					(Max Input: ${maxCap})
				</span>
			)}
		</div>
	);
};
