package com.tangem.features.staking.impl.presentation.model

import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.common.getValidatorsCount
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.api.ParamsInterceptorHolder
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.share.ShareManager
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.GetFeePaidCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.staking.*
import com.tangem.domain.models.staking.action.StakingActionType
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.staking.*
import com.tangem.domain.staking.analytics.StakeScreenSource
import com.tangem.domain.staking.analytics.StakingAnalyticsEvent
import com.tangem.domain.staking.model.*
import com.tangem.domain.staking.model.stakekit.StakingError
import com.tangem.domain.staking.model.stakekit.action.StakingAction
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
import com.tangem.domain.staking.toggles.StakingFeatureToggles
import com.tangem.domain.tokens.*
import com.tangem.domain.transaction.error.GetFeeError
import com.tangem.domain.transaction.usecase.CreateTransferTransactionUseCase
import com.tangem.domain.transaction.usecase.GetAllowanceUseCase
import com.tangem.domain.transaction.usecase.GetFeeUseCase
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
import com.tangem.domain.utils.convertToSdkAmount
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.features.approval.api.GiveApprovalComponent
import com.tangem.features.staking.api.StakingComponent
import com.tangem.features.staking.impl.R
import com.tangem.features.staking.impl.analytics.StakingParamsInterceptor
import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender
import com.tangem.features.staking.impl.navigation.InnerStakingRouter
import com.tangem.features.staking.impl.presentation.state.*
import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType
import com.tangem.features.staking.impl.presentation.state.events.StakingAlertUM
import com.tangem.features.staking.impl.presentation.state.events.StakingEventFactory
import com.tangem.features.staking.impl.presentation.state.helpers.*
import com.tangem.features.staking.impl.presentation.state.transformers.*
import com.tangem.features.staking.impl.presentation.state.transformers.amount.*
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetApprovalInProgressTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.approval.SetConfirmationStateAssentApprovalTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.confirmation.SetUpdatedAllowanceTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.notifications.AddStakingNotificationsTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.notifications.DismissStakingNotificationsStateTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.ton.CompleteInitializeBottomSheetTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.ton.SetFeeErrorToTonInitializeBottomSheetTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.ton.SetFeeToTonInitializeBottomSheetTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.ton.ShowTonInitializeBottomSheetTransformer
import com.tangem.features.staking.impl.presentation.state.transformers.validator.ValidatorSelectChangeTransformer
import com.tangem.features.staking.impl.presentation.state.utils.checkAndCalculateSubtractedAmount
import com.tangem.features.staking.impl.presentation.state.utils.isSingleAction
import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstakeAction
import com.tangem.lib.crypto.BlockchainUtils.isTon
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import com.tangem.utils.extensions.isPositive
import com.tangem.utils.extensions.isSingleItem
import com.tangem.utils.extensions.orZero
import com.tangem.utils.logging.TangemLogger
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.math.BigDecimal
import java.util.concurrent.CopyOnWriteArrayList
import javax.inject.Inject

@Suppress("LargeClass", "TooManyFunctions", "LongParameterList")
@Stable
@ModelScoped
internal class StakingModel @Inject constructor(
    paramsContainer: ParamsContainer,
    private val stateController: StakingStateController,
    override val dispatchers: CoroutineDispatcherProvider,
    private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
    private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
    private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase,
    getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
    private val getUserWalletUseCase: GetUserWalletUseCase,
    private val sendTransactionUseCase: SendTransactionUseCase,
    private val getAllowanceUseCase: GetAllowanceUseCase,
    private val vibratorHapticManager: VibratorHapticManager,
    private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
    private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
    private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
    private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
    private val getEffectiveStakingFee: GetEffectiveStakingFee,
    private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase,
    private val isAnyTokenStakedUseCase: IsAnyTokenStakedUseCase,
    private val invalidatePendingTransactionsUseCase: InvalidatePendingTransactionsUseCase,
    private val stakingOperationsFactory: StakingOperationsFactory,
    private val stakingBalanceUpdater: StakingBalanceUpdater.Factory,
    private val analyticsEventHandler: AnalyticsEventHandler,
    private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
    private val getActionsUseCase: GetActionsUseCase,
    private val getYieldUseCase: GetYieldUseCase,
    private val p2pEthPoolRepository: P2PEthPoolRepository,
    private val checkAccountInitializedUseCase: CheckAccountInitializedUseCase,
    private val createTransferTransactionUseCase: CreateTransferTransactionUseCase,
    private val getFeeUseCase: GetFeeUseCase,
    private val getNetworkAddressesUseCase: GetNetworkAddressesUseCase,
    private val getActionRequirementAmountUseCase: GetActionRequirementAmountUseCase,
    private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
    private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
    private val paramsInterceptorHolder: ParamsInterceptorHolder,
    private val shareManager: ShareManager,
    private val urlOpener: UrlOpener,
    private val coroutineScope: AppCoroutineScope,
    private val innerRouter: InnerStakingRouter,
    private val messageSender: UiMessageSender,
    private val stakingFeatureToggles: StakingFeatureToggles,
    appRouter: AppRouter,
) : Model(), StakingClickIntents {

    val uiState: StateFlow<StakingUiState> = stateController.uiState
    val value: StakingUiState get() = uiState.value

    val approvalSlotNavigation = SlotNavigation<Unit>()

    private val params = paramsContainer.require<StakingComponent.Params>()

    private val stakingStateRouter: StakingStateRouter = StakingStateRouter(
        appRouter = appRouter,
        stateController = stateController,
        analyticsEventsHandler = analyticsEventHandler,
    )

    private val cryptoCurrencyId: CryptoCurrency.ID = params.cryptoCurrency.id
    private val userWalletId: UserWalletId = params.userWalletId
    private val integration: StakingIntegration = runBlocking {
        when (val integrationId = params.integrationId) {
            is StakingIntegrationID.StakeKit -> {
                val yield = getYieldUseCase(integrationId.value).getOrElse {
                    error("yield must be not null")
                }
                StakeKitIntegration(integrationId, yield)
            }
            StakingIntegrationID.P2PEthPool -> {
                val vaults = p2pEthPoolRepository.getVaultsSync()
                val limits = p2pEthPoolRepository.getVaultLimitsSyncOrNull().orEmpty()
                P2PEthPoolIntegration(integrationId, vaults, limits)
            }
        }
    }

    private var isAccountInitialized: Boolean = true
    private var isTonHeatupCase: Boolean = false
    private lateinit var cryptoCurrencyStatus: CryptoCurrencyStatus
    private var stakingActions: List<StakingAction> = emptyList()
    private var feeCryptoCurrencyStatus: CryptoCurrencyStatus? = null
    private var minimumTransactionAmount: EnterAmountBoundary? = null
    private val isBalanceHiddenFlow: StateFlow<Boolean>
        field = MutableStateFlow(false)

    private var tonAccountInitializeTransaction: TransactionData.Uncompiled? = null

    private val userWallet by lazy {
        requireNotNull(
            getUserWalletUseCase(userWalletId).getOrNull(),
        ) { "No wallet found for id: $userWalletId" }
    }
    private val currentAppCurrency = getSelectedAppCurrencyUseCase().map { maybeAppCurrency ->
        maybeAppCurrency.getOrElse { AppCurrency.Default }
    }.stateIn(
        scope = modelScope,
        started = SharingStarted.Eagerly,
        initialValue = AppCurrency.Default,
    )

    private val balancesToShow: List<StakingBalanceEntry>
        get() {
            val stakingBalance = cryptoCurrencyStatus.value.stakingBalance
            return when (integration) {
                is StakeKitIntegration -> {
                    val balanceItems = (stakingBalance as? StakingBalance.Data.StakeKit)
                        ?.balance?.items.orEmpty()
                    invalidatePendingTransactionsUseCase(
                        balanceItems = balanceItems,
                        stakingActions = stakingActions,
                        token = integration.token,
                    ).getOrElse { emptyList() }.toStakingBalanceEntries()
                }
                is P2PEthPoolIntegration -> {
                    (stakingBalance as? StakingBalance.Data.P2PEthPool)?.entries.orEmpty()
                }
            }
        }

    private var isInitialInfoAnalyticSent: Boolean = false
    private var isBalanceUpdatedAfterStart: Boolean = false

    private val balanceUpdater by lazy(LazyThreadSafetyMode.NONE) {
        stakingBalanceUpdater.create(
            cryptoCurrencyStatus,
            userWallet,
            integration,
        )
    }

    private val feeLoader: StakingFeeLoader by lazy(LazyThreadSafetyMode.NONE) {
        stakingOperationsFactory.createFeeLoader(
            cryptoCurrencyStatus = cryptoCurrencyStatus,
            userWallet = userWallet,
            integration = integration,
        )
    }

    @Suppress("PropertyUsedBeforeDeclaration")
    private val transactionSender: StakingTransactionSender by lazy(LazyThreadSafetyMode.NONE) {
        stakingOperationsFactory.createTransactionSender(
            cryptoCurrencyStatus = cryptoCurrencyStatus,
            userWallet = userWallet,
            integration = integration,
            isAmountSubtractAvailable = isAmountSubtractAvailable,
        )
    }

    private val stakingEventFactory: StakingEventFactory
        get() = StakingEventFactory(
            messageSender = messageSender,
            popBackStack = ::onBackClick,
            onFailedTxEmailClick = ::onFailedTxEmailClick,
        )

    private val stakingAnalyticSender = StakingAnalyticSender(
        analyticsEventHandler = analyticsEventHandler,
    )

    private val approvalCallback = object : GiveApprovalComponent.Callback {
        override fun onApproveClick() {
        }

        override fun onApproveDone() {
            approvalSlotNavigation.dismiss()
            stakingAnalyticSender.sendTransactionApprovalAnalytics(
                cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return,
            )
            stateController.update(SetApprovalInProgressTransformer)
            awaitForAllowance()
        }

        override fun onApproveFailed() {
            approvalSlotNavigation.dismiss()
            stateController.update(
                SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus = cryptoCurrencyStatus),
            )
        }

        override fun onCancelClick() {
            approvalSlotNavigation.dismiss()
        }
    }

    private var stakingApproval: StakingApproval = StakingApproval.Empty
    private var stakingAllowance: BigDecimal = BigDecimal.ZERO
    private var isAmountSubtractAvailable: Boolean = false
    private var isAnyTokenStaked: Boolean = false
    private val allowanceTaskScheduler = SingleTaskScheduler<BigDecimal>()

    private var isAccountsModeEnabled: Boolean = true
    private var account: Account.CryptoPortfolio? = null

    private val transactionsInProgress: CopyOnWriteArrayList<StakingTransaction> = CopyOnWriteArrayList()

    private val actionsJobHolder: JobHolder = JobHolder()
    private val feeJobHolder: JobHolder = JobHolder()
    private val sendTransactionJobHolder = JobHolder()
    private val stepChangesJobHolder = JobHolder()
    private val balanceHidingJobHolder = JobHolder()

    init {
        subscribeOnCurrencyStatusUpdates()
        stateController.initializeWithUserWallet(userWallet)
    }

    override fun onDestroy() {
        super.onDestroy()
        paramsInterceptorHolder.removeParamsInterceptor(StakingParamsInterceptor.ID)
        feeJobHolder.cancel()
        sendTransactionJobHolder.cancel()
        stepChangesJobHolder.cancel()
        balanceHidingJobHolder.cancel()
    }

    override fun onBackClick() {
        stakingStateRouter.onBackClick()
    }

    override fun onNextClick(balanceState: BalanceState?) {
        modelScope.launch {
            val isInitialInfoStep = value.currentStep == StakingStep.InitialInfo
            val isBalanceAbsent = balanceState == null
            val hasNoYieldBalanceData = cryptoCurrencyStatus.value.stakingBalance !is StakingBalance.Data.StakeKit

            when {
                isInitialInfoStep && isBalanceAbsent && integration.areAllTargetsFull && hasNoYieldBalanceData -> {
                    stakingEventFactory.createStakingValidatorsUnavailableAlert()
                    return@launch
                }
                isInitialInfoStep && isBalanceAbsent -> {
                    val list = buildList {
                        val setConfirmationStateInitTransformer = SetConfirmationStateInitTransformer(
                            isEnter = true,
                            isExplicitExit = false,
                            balanceState = null,
                            cryptoCurrencyStatus = cryptoCurrencyStatus,
                            stakingApproval = stakingApproval,
                            stakingAllowance = stakingAllowance,
                            integration = integration,
                        )

                        add(setConfirmationStateInitTransformer)

                        if (integration.isPartialAmountDisabled) {
                            val validatorSelectChangeTransformer = ValidatorSelectChangeTransformer(
                                selectedTarget = integration.preferredTargets.firstOrNull(),
                                integration = integration,
                            )

                            val setAmountDataTransformer = SetAmountDataTransformer(
                                clickIntents = this@StakingModel,
                                cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
                                userWalletProvider = Provider { userWallet },
                                appCurrencyProvider = Provider { currentAppCurrency.value },
                                isBalanceHidden = isBalanceHiddenFlow.value,
                                isAccountsModeEnabled = isAccountsModeEnabled,
                                account = account,
                            )

                            val amountMaxValueStateTransformer = AmountMaxValueStateTransformer(
                                cryptoCurrencyStatus = cryptoCurrencyStatus,
                                minimumTransactionAmount = minimumTransactionAmount,
                                actionType = uiState.value.actionType,
                                integration = integration,
                                isSolanaUnstakeValidationEnabled = stakingFeatureToggles
                                    .isSolanaUnstakeValidationEnabled(),
                            )

                            addAll(
                                listOf(
                                    validatorSelectChangeTransformer,
                                    setAmountDataTransformer,
                                    amountMaxValueStateTransformer,
                                ),
                            )
                        }
                    }

                    stateController.updateAll(*list.toTypedArray())
                }
            }
            stakingStateRouter.onNextClick()
        }
    }

    override fun getFee() {
        stateController.update(
            SetConfirmationStateLoadingTransformer(
                integration = integration,
                appCurrency = currentAppCurrency.value,
                cryptoCurrency = cryptoCurrencyStatus.currency,
            ),
        )
        modelScope.launch {
            feeLoader.getFee(
                onStakingFee = { gasEstimate, isFeeApproximate ->
                    stateController.update(
                        SetConfirmationStateAssentTransformer(
                            appCurrencyProvider = Provider { currentAppCurrency.value },
                            feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
                            fee = gasEstimate,
                            isFeeApproximate = isFeeApproximate,
                            cryptoCurrencyStatus = cryptoCurrencyStatus,
                        ),
                    )
                    updateNotifications()
                },
                onStakingFeeError = { stakingFeeError ->
                    stateController.update(AddStakingErrorTransformer)
                    updateNotifications(stakingError = stakingFeeError)
                },
                onFeeError = { error ->
                    analyticsEventHandler.send(
                        StakingAnalyticsEvent.TransactionError(
                            errorCode = "GetFeeError",
                        ),
                    )
                    stateController.update(AddStakingErrorTransformer)
                    updateNotifications(error)
                },
                onApprovalFee = { fee ->
                    stateController.update(
                        SetConfirmationStateAssentApprovalTransformer(
                            appCurrencyProvider = Provider { currentAppCurrency.value },
                            feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
                            fee = fee,
                            cryptoCurrencyStatus = cryptoCurrencyStatus,
                        ),
                    )
                    updateNotifications()
                },
            )
        }.saveIn(feeJobHolder)
    }

    override fun onActionClick() {
        if (isAssentState()) {
            modelScope.launch {
                stakingAnalyticSender.sendTransactionStakingClickedAnalytics(value)
                stateController.update(SetConfirmationStateInProgressTransformer())

                when (integration) {
                    is P2PEthPoolIntegration -> checkFeeAndSendP2PTransaction()
                    is StakeKitIntegration -> sendTransaction()
                }
            }.saveIn(sendTransactionJobHolder)
        }
    }

    private suspend fun checkFeeAndSendP2PTransaction() {
        val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
        val currentFeeState = confirmationState?.feeState as? FeeState.Content
        val currentFee = currentFeeState?.fee

        var actualFee: Fee? = null
        feeLoader.getFee(
            onStakingFee = { fee, _ ->
                actualFee = fee
            },
            onStakingFeeError = { error ->
                stakingEventFactory.createStakingErrorAlert(error)
                stateController.update(SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus))
            },
            onApprovalFee = { },
            onFeeError = { error ->
                stateController.update(SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus))
                updateNotifications(error)
            },
        )

        val newFee = actualFee ?: return

        val currentFeeAmount = when (currentFee) {
            is Fee.Common -> currentFee.amount.value ?: BigDecimal.ZERO
            is Fee.Ethereum -> currentFee.amount.value ?: BigDecimal.ZERO
            else -> BigDecimal.ZERO
        }

        val newFeeAmount = when (newFee) {
            is Fee.Common -> newFee.amount.value ?: BigDecimal.ZERO
            is Fee.Ethereum -> newFee.amount.value ?: BigDecimal.ZERO
            else -> BigDecimal.ZERO
        }

        if (newFeeAmount > currentFeeAmount) {
            stateController.update(SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus))
            stakingEventFactory.createNetworkFeeUpdatedAlert(
                onConfirm = {
                    modelScope.launch {
                        stateController.update(UpdateConfirmationFeeTransformer(newFee))
                        stateController.update(SetConfirmationStateInProgressTransformer())
                        sendTransaction()
                    }
                },
            )
        } else {
            sendTransaction()
        }
    }

    private suspend fun sendTransaction() {
        transactionSender.send(
            StakingTransactionSender.Callbacks(
                onConstructSuccess = { constructedTransactions ->
                    transactionsInProgress.addAll(constructedTransactions)
                },
                onConstructError = { error ->
                    stakingEventFactory.createStakingErrorAlert(error)
                    stateController.update(SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus))
                },
                onSendSuccess = { txUrl ->
                    stakingAnalyticSender.sendTransactionStakingAnalytics(
                        stateController.value,
                        cryptoCurrencyStatus,
                    )
                    transactionsInProgress.clear()
                    stateController.update(
                        SetConfirmationStateCompletedTransformer(txUrl, cryptoCurrencyStatus),
                    )
                },
                onSendError = { error ->
                    analyticsEventHandler.send(
                        StakingAnalyticsEvent.TransactionError(
                            errorCode = error.getAnalyticsDescription(),
                        ),
                    )
                    stakingEventFactory.createSendTransactionErrorAlert(error)
                    stateController.update(SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus))
                },
                onFeeIncreased = { increasedFee, isFeeApproximate ->
                    stateController.updateAll(
                        SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus),
                        SetConfirmationStateAssentTransformer(
                            appCurrencyProvider = Provider { currentAppCurrency.value },
                            feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
                            fee = increasedFee,
                            isFeeApproximate = isFeeApproximate,
                            cryptoCurrencyStatus = cryptoCurrencyStatus,
                        ),
                    )
                    messageSender.send(StakingAlertUM.feeIncreased {})
                    updateNotifications()
                },
                onTransactionExpired = {
                    stateController.update(SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus))
                    getFee()
                },
            ),
        )
    }

    override fun onPrevClick() {
        if (value.currentStep == StakingStep.Confirmation) {
            val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
            when (confirmationState?.innerState) {
                InnerConfirmationStakingState.ASSENT -> {
                    val isApprovalInProgress = confirmationState.notifications.any {
                        it is StakingNotification.Warning.TransactionInProgress
                    }
                    if (!isApprovalInProgress) {
                        stakingStateRouter.onPrevClick()
                        stateController.update(
                            SetConfirmationStateResetAssentTransformer(cryptoCurrencyStatus = cryptoCurrencyStatus),
                        )
                    }
                }
                null,
                InnerConfirmationStakingState.IN_PROGRESS,
                -> {
                    // do nothing while transaction is in progress
                }
                InnerConfirmationStakingState.COMPLETED -> {
                    onNextClick()
                }
            }
        } else {
            stakingStateRouter.onPrevClick()
        }
    }

    override fun onRefreshSwipe(isRefreshing: Boolean) {
        stateController.update(SetInitialLoadingStateTransformer(isRefreshing))
        coroutineScope.launch {
            balanceUpdater.partialUpdate()
        }.invokeOnCompletion {
            stateController.update(SetInitialLoadingStateTransformer(false))
        }
    }

    override fun onInitialInfoBannerClick() {
        analyticsEventHandler.send(StakingAnalyticsEvent.WhatIsStaking())
        modelScope.launch {
            innerRouter.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.HowToStake))
        }
    }

    override fun onInfoClick(infoType: InfoType) {
        stateController.update(
            ShowInfoBottomSheetStateTransformer(infoType) {
                stateController.update(DismissBottomSheetStateTransformer)
            },
        )
    }

    override fun onAmountEnterClick() {
        val actionType = uiState.value.actionType
        if (actionType !is StakingActionCommonType.Exit && integration.preferredTargets.isEmpty()) {
            messageSender.send(StakingAlertUM.noAvailableValidators())
        } else {
            if (actionType is StakingActionCommonType.Enter) {
                stateController.updateAll(
                    ValidatorSelectChangeTransformer(
                        selectedTarget = null,
                        integration = integration,
                    ),
                )
            }
            onNextClick()
        }
    }

    override fun onAmountValueChange(value: String) {
        stateController.update(
            AmountChangeStateTransformer(
                cryptoCurrencyStatus = cryptoCurrencyStatus,
                minimumTransactionAmount = minimumTransactionAmount,
                value = value,
                integration = integration,
                isSolanaUnstakeValidationEnabled = stakingFeatureToggles.isSolanaUnstakeValidationEnabled(),
            ),
        )
        checkSumLimitExceeded()
    }

    private fun checkSumLimitExceeded() {
        val maxLimit = (integration as? P2PEthPoolIntegration)
            ?.enterArgs?.amountRequirement?.maximum
            ?.takeIf { it.isPositive() }
            ?: return

        val enteredAmount = (uiState.value.amountState as? AmountState.Data)
            ?.amountTextField?.cryptoAmount?.value
            ?: return

        if (enteredAmount > maxLimit) {
            // Pass the max limit as a stable crypto-formatted value (e.g. "0.15 ETH"); the event
            // builds the hardcoded English "Error Message" from it, so the model needs no resources.
            val formattedMax = maxLimit.format { crypto(cryptoCurrencyStatus.currency) }
            analyticsEventHandler.send(
                StakingAnalyticsEvent.SumLimitError(
                    token = cryptoCurrencyStatus.currency.symbol,
                    blockchain = cryptoCurrencyStatus.currency.network.name,
                    maxAmount = formattedMax,
                ),
            )
        }
    }

    override fun onAmountPasteTriggerDismiss() {
        stateController.update(AmountPasteDismissStateTransformer())
    }

    override fun onMaxValueClick() {
        analyticsEventHandler.send(StakingAnalyticsEvent.ButtonMax())
        stateController.update(
            AmountMaxValueStateTransformer(
                cryptoCurrencyStatus = cryptoCurrencyStatus,
                minimumTransactionAmount = minimumTransactionAmount,
                actionType = uiState.value.actionType,
                integration = integration,
                isSolanaUnstakeValidationEnabled = stakingFeatureToggles.isSolanaUnstakeValidationEnabled(),
            ),
        )
        checkSumLimitExceeded()
    }

    override fun onCurrencyChangeClick(isFiat: Boolean) {
        analyticsEventHandler.send(
            StakingAnalyticsEvent.AmountSelectCurrency(isFiat),
        )
        stateController.update(AmountCurrencyChangeStateTransformer(cryptoCurrencyStatus, isFiat))
    }

    override fun openValidators() {
        analyticsEventHandler.send(
            StakingAnalyticsEvent.ButtonValidator(
                source = StakeScreenSource.Confirmation,
            ),
        )
        stakingStateRouter.showValidators()
    }

    override fun onTargetSelect(target: StakingTarget) {
        analyticsEventHandler.send(
            StakingAnalyticsEvent.ValidatorChosen(
                validator = target.name,
            ),
        )
        stateController.update(
            ValidatorSelectChangeTransformer(
                selectedTarget = target,
                integration = integration,
            ),
        )
    }

    override fun openRewardsValidators() {
        analyticsEventHandler.send(StakingAnalyticsEvent.ButtonRewards())
        val rewardsValidators =
            stateController.value.rewardsValidatorsState as? StakingStates.RewardsValidatorsState.Data

        val initialInfoState = stateController.value.initialInfoState as? StakingStates.InitialInfoState.Data
        val yieldBalance = initialInfoState?.yieldBalance as? InnerYieldBalanceState.Data
        val rewardBlockType = yieldBalance?.reward?.rewardBlockType
        val rewardPendingActionConstraints = yieldBalance?.reward?.rewardConstraints

        if (rewardBlockType is RewardBlockType.RewardsRequirementsError) {
            val minimumAmount = rewardPendingActionConstraints?.amountArg?.minimum
            // Temporary fix, until StakeKit adds minimum requirement amount to balance response
            val minimumAmountValue = if (minimumAmount == null && yieldBalance.integrationId != null) {
                getActionRequirementAmountUseCase.invoke(
                    integrationId = yieldBalance.integrationId,
                    actionType = StakingActionType.CLAIM_REWARDS,
                )
            } else {
                minimumAmount
            }

            stakingEventFactory.createStakingRewardsMinimumRequirementsErrorAlert(
                cryptoCurrencyName = cryptoCurrencyStatus.currency.name,
                cryptoAmountValue = minimumAmountValue?.format {
                    crypto(cryptoCurrencyStatus.currency)
                }.orEmpty(),
            )
        } else {
            val rewards = rewardsValidators?.rewards
            if (rewards != null && rewards.isSingleItem()) {
                onActiveStake(rewards.first())
            } else {
                analyticsEventHandler.send(
                    StakingAnalyticsEvent.ButtonValidator(
                        source = StakeScreenSource.Info,
                    ),
                )
                stateController.update {
                    value.copy(actionType = StakingActionCommonType.Pending.Rewards)
                }
                stakingStateRouter.showRewardsValidators()
            }
        }
    }

    override fun onActiveStake(activeStake: BalanceState) {
        val networkId = cryptoCurrencyStatus.currency.network.rawId
        val preferredValidators = (integration as? StakeKitIntegration)?.targets
            ?.filterIsInstance<StakingTarget.Validator>()
            ?.filter { it.delegate.preferred }
            .orEmpty()
        val pendingActions = activeStake.pendingActions.mapNotNull { action ->
            if (action.type in listOf(StakingActionType.RESTAKE, StakingActionType.STAKE) &&
                preferredValidators.isSingleItem()
            ) {
                null
            } else {
                action
            }
        }.toImmutableList()
        if (isSingleAction(networkId, pendingActions)) {
            prepareForConfirmation(
                balanceType = activeStake.type,
                pendingActions = pendingActions,
                balanceState = activeStake,
                target = activeStake.target,
                amountValue = activeStake.cryptoValue,
            )
            onNextClick(activeStake)
        } else {
            stateController.update(
                ShowActionSelectorBottomSheetTransformer(
                    pendingActions = withStubUnstakeAction(networkId, pendingActions, activeStake),
                    onActionSelect = { action ->
                        prepareForConfirmation(
                            balanceType = activeStake.type,
                            pendingAction = action,
                            balanceState = activeStake,
                            target = activeStake.target,
                            amountValue = activeStake.cryptoValue,
                        )
                        stateController.update(DismissBottomSheetStateTransformer)
                        onNextClick(activeStake)
                    },
                    onDismiss = { stateController.update(DismissBottomSheetStateTransformer) },
                ),
            )
        }
    }

    override fun onActiveStakeAnalytic() {
        analyticsEventHandler.send(
            StakingAnalyticsEvent.ButtonValidator(
                source = StakeScreenSource.Info,
            ),
        )
    }

    override fun showApprovalBottomSheet() {
        approvalSlotNavigation.activate(Unit)
    }

    private fun updateNotifications(feeError: GetFeeError? = null, stakingError: StakingError? = null) {
        modelScope.launch {
            val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data
            val feeState = confirmationState?.feeState as? FeeState.Content
            val amountState = value.amountState as? AmountState.Data

            val amount = amountState?.amountTextField?.cryptoAmount?.value
            val fee = feeState?.fee?.amount?.value
            val feeStatus = feeCryptoCurrencyStatus
            val currencyWarning = if (feeStatus != null && fee != null) {
                getBalanceNotEnoughForFeeWarningUseCase(
                    fee = fee,
                    userWalletId = userWalletId,
                    tokenStatus = cryptoCurrencyStatus,
                    feeStatus = feeStatus,
                ).getOrNull()
            } else {
                null
            }

            val effectiveFee = getEffectiveStakingFee(
                stakeKitFee = fee,
                amount = amount,
                userWallet = userWallet,
                feeCurrencyStatus = feeStatus,
            )

            val balanceAfterTransaction = calculateBalanceAfterTransaction(
                amount = amount.orZero(),
                fee = effectiveFee.orZero(),
                reduceAmountBy = confirmationState?.reduceAmountBy.orZero(),
                actionType = value.actionType,
            )
            val currencyStatus = getCurrencyCheckUseCase(
                userWalletId = userWalletId,
                currencyStatus = cryptoCurrencyStatus,
                feeCurrencyStatus = feeCryptoCurrencyStatus,
                amount = amount,
                fee = effectiveFee,
                feeCurrencyBalanceAfterTransaction = balanceAfterTransaction,
            )

            val hasRentWarning = currencyStatus.rentWarning != null

            stateController.update(
                AddStakingNotificationsTransformer(
                    cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
                    appCurrencyProvider = Provider { currentAppCurrency.value },
                    isAccountInitializedProvider = Provider { isAccountInitialized },
                    feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
                    currencyWarning = currencyWarning,
                    currencyCheck = currencyStatus,
                    isSubtractAvailable = isAmountSubtractAvailable,
                    feeError = if (hasRentWarning) null else feeError,
                    stakingError = if (hasRentWarning) null else stakingError,
                    integration = integration,
                ),
            )
        }
    }

    override fun onAmountReduceByClick(
        reduceAmountBy: BigDecimal,
        reduceAmountByDiff: BigDecimal,
        notification: Class<out NotificationUM>,
    ) {
        val transformer = AmountReduceByStateTransformer(
            cryptoCurrencyStatus = cryptoCurrencyStatus,
            minimumTransactionAmount = minimumTransactionAmount,
            value = ReduceByData(
                reduceAmountBy = reduceAmountBy,
                reduceAmountByDiff = reduceAmountByDiff,
            ),
        )
        stateController.update(transformer)
        onNotificationCancel(notification)
    }

    override fun onAmountReduceToClick(reduceAmountTo: BigDecimal, notification: Class<out NotificationUM>) {
        stateController.update(
            AmountReduceToStateTransformer(
                cryptoCurrencyStatus = cryptoCurrencyStatus,
                minimumTransactionAmount = minimumTransactionAmount,
                value = reduceAmountTo,
            ),
        )
        onNotificationCancel(notification)
    }

    override fun onNotificationCancel(notification: Class<out NotificationUM>) {
        stateController.update(DismissStakingNotificationsStateTransformer(notification))
    }

    private fun calculateBalanceAfterTransaction(
        amount: BigDecimal,
        fee: BigDecimal,
        reduceAmountBy: BigDecimal,
        actionType: StakingActionCommonType,
    ): BigDecimal? {
        // TODO split for different networks
        val subtractedBalanceAmount = when (actionType) {
            is StakingActionCommonType.Enter -> checkAndCalculateSubtractedAmount(
                isAmountSubtractAvailable = isAmountSubtractAvailable,
                cryptoCurrencyStatus = cryptoCurrencyStatus,
                amountValue = amount.orZero(),
                feeValue = fee.orZero(),
                reduceAmountBy = reduceAmountBy,
            )
            is StakingActionCommonType.Exit,
            is StakingActionCommonType.Pending,
            -> BigDecimal.ZERO
        }
        val statusValue = cryptoCurrencyStatus.value as? CryptoCurrencyStatus.Loaded
        return statusValue?.let { it.amount - subtractedBalanceAmount - fee.orZero() }
    }

    private fun awaitForAllowance() {
        val approval = stakingApproval as? StakingApproval.Needed ?: return
        allowanceTaskScheduler.scheduleTask(
            scope = modelScope,
            task = PeriodicTask(
                delay = ALLOWANCE_UPDATE_DELAY,
                task = {
                    runSuspendCatching {
                        getAllowanceUseCase(
                            userWalletId = userWalletId,
                            cryptoCurrency = cryptoCurrencyStatus.currency,
                            spenderAddress = approval.spenderAddress,
                        ).getOrElse { BigDecimal.ZERO }
                    }
                },
                onSuccess = { allowance ->
                    stakingAllowance = allowance
                    val amount = (value.amountState as? AmountState.Data)?.amountTextField?.cryptoAmount?.value
                        ?: error("No amount provided")
                    if (allowance >= amount) {
                        stateController.update(SetUpdatedAllowanceTransformer(allowance))
                        getFee()
                        allowanceTaskScheduler.cancelTask()
                    }
                },
                onError = { /* no-op */ },
            ),
        )
    }

    override fun onExploreClick() {
        analyticsEventHandler.send(StakingAnalyticsEvent.ButtonExplore())
        val confirmationDataState = uiState.value.confirmationState as? StakingStates.ConfirmationState.Data
        val transactionDoneState = confirmationDataState?.transactionDoneState as? TransactionDoneState.Content
        val txUrl = transactionDoneState?.txUrl
        if (txUrl != null) {
            innerRouter.openUrl(txUrl)
        }
    }

    override fun onShareClick() {
        val confirmationDataState = uiState.value.confirmationState as? StakingStates.ConfirmationState.Data
        val transactionDoneState = confirmationDataState?.transactionDoneState as? TransactionDoneState.Content
        val txUrl = transactionDoneState?.txUrl

        analyticsEventHandler.send(StakingAnalyticsEvent.ButtonShare())
        if (txUrl != null) {
            vibratorHapticManager.performOneTime(TangemHapticEffect.OneTime.Click)
            shareManager.shareText(txUrl)
        }
    }

    override fun onFailedTxEmailClick(errorMessage: String) {
        modelScope.launch {
            val network = cryptoCurrencyStatus.currency.network

            val metaInfo = getWalletMetaInfoUseCase(userWalletId = userWallet.walletId).getOrElse {
                error("CardInfo must be not null")
            }
            val amountState = uiState.value.amountState as? AmountState.Data
            val confirmationState = uiState.value.confirmationState as? StakingStates.ConfirmationState.Data
            val validatorState = uiState.value.validatorState as? StakingStates.ValidatorState.Data
            val feeState = confirmationState?.feeState as? FeeState.Content

            val target = validatorState?.chosenTarget
            val feeAmount = feeState?.fee?.amount
            val amount = amountState?.amountTextField?.cryptoAmount
            saveBlockchainErrorUseCase(
                error = BlockchainErrorInfo(
                    errorMessage = errorMessage,
                    networkId = network.id,
                    destinationAddress = target?.address.orEmpty(),
                    tokenSymbol = (cryptoCurrencyStatus.currency as? CryptoCurrency.Token)?.symbol,
                    amount = amount?.run { value?.toPlainString() + currencySymbol }.orEmpty(),
                    fee = feeAmount?.run { value?.toPlainString() + currencySymbol }.orEmpty(),
                ),
            )

            val email = FeedbackEmailType.StakingProblem(
                walletMetaInfo = metaInfo,
                validatorName = target?.name,
                transactionTypes = transactionsInProgress.map { it.type.name },
                unsignedTransactions = transactionsInProgress.map { it.unsignedTransaction },
            )

            analyticsEventHandler.send(Basic.ButtonSupport(source = AnalyticsParam.ScreensSources.Staking))
            sendFeedbackEmailUseCase(email)
        }
    }

    override fun openTokenDetails(cryptoCurrency: CryptoCurrency) {
        innerRouter.openTokenDetails(userWalletId, cryptoCurrency)
    }

    override fun showPrimaryClickAlert() {
        val message = if (integration is P2PEthPoolIntegration) {
            StakingAlertUM.stakeMoreClickUnavailableNoTargets()
        } else {
            StakingAlertUM.stakeMoreClickUnavailable(cryptoCurrencyStatus.currency)
        }
        messageSender.send(message)
    }

    override fun onOpenLearnMoreAboutApproveClick() {
        modelScope.launch {
            urlOpener.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.GiveRevokePermission))
        }
    }

    override fun onActivateTonAccountNotificationClick() {
        analyticsEventHandler.send(
            StakingAnalyticsEvent.UninitializedAddressScreen(
                token = cryptoCurrencyStatus.currency.symbol,
            ),
        )
        modelScope.launch {
            stateController.update(
                ShowTonInitializeBottomSheetTransformer(
                    onDismiss = { stateController.update(DismissBottomSheetStateTransformer) },
                ),
            )

            val tonAmount = BigDecimal(1)
            val destination = getNetworkAddressesUseCase.invokeSync(
                userWalletId = userWalletId,
                network = cryptoCurrencyStatus.currency.network,
            ).getOrNull(0)?.address ?: return@launch

            val transaction = createTransferTransactionUseCase(
                amount = tonAmount.convertToSdkAmount(cryptoCurrencyStatus),
                destination = destination,
                userWalletId = userWalletId,
                network = cryptoCurrencyStatus.currency.network,
                memo = null,
            )

            val initialTransaction = transaction.getOrElse {
                stateController.update(
                    SetFeeErrorToTonInitializeBottomSheetTransformer(),
                )
                return@launch
            }

            tonAccountInitializeTransaction = initialTransaction

            val transactionFee = getFeeUseCase(
                userWallet = userWallet,
                network = cryptoCurrencyStatus.currency.network,
                transactionData = initialTransaction,
            )

            transactionFee.fold(
                ifLeft = {
                    stateController.update(
                        SetFeeErrorToTonInitializeBottomSheetTransformer(),
                    )
                },
                ifRight = { fee ->
                    stateController.update(
                        SetFeeToTonInitializeBottomSheetTransformer(
                            appCurrencyProvider = Provider { currentAppCurrency.value },
                            feeCryptoCurrencyStatus = feeCryptoCurrencyStatus,
                            fee = fee.normal,
                            isFeeApproximate = false,
                        ),
                    )
                },
            )
        }
    }

    override fun onActivateTonAccountNotificationShow() {
        analyticsEventHandler.send(
            StakingAnalyticsEvent.UninitializedAddress(
                token = cryptoCurrencyStatus.currency.symbol,
            ),
        )
    }

    override fun onNotEnoughFeeNotificationShow() {
        analyticsEventHandler.send(
            StakingAnalyticsEvent.NotEnoughFee(
                token = cryptoCurrencyStatus.currency.symbol,
                blockchain = cryptoCurrencyStatus.currency.network.name,
            ),
        )
    }

    override fun onActivateTonAccountClick() {
        analyticsEventHandler.send(
            StakingAnalyticsEvent.ButtonActivate(
                token = cryptoCurrencyStatus.currency.symbol,
            ),
        )
        modelScope.launch {
            tonAccountInitializeTransaction?.let { transaction ->
                sendTransactionUseCase.invoke(
                    txData = transaction,
                    userWallet = userWallet,
                    network = cryptoCurrencyStatus.currency.network,
                ).fold(
                    ifLeft = {
                        messageSender.send(
                            DialogMessage(
                                title = resourceReference(id = R.string.send_alert_transaction_failed_title),
                                message = resourceReference(id = R.string.common_unknown_error),
                            ),
                        )
                    },
                    ifRight = {
                        stateController.update(
                            CompleteInitializeBottomSheetTransformer(
                                cryptoCurrencyStatus = cryptoCurrencyStatus,
                                minimumTransactionAmount = minimumTransactionAmount,
                            ),
                        )

                        balanceUpdater.partialUpdateWithDelay()
                        stateController.update(DismissBottomSheetStateTransformer)
                    },
                )
            }
        }
    }

    override fun onAmountReduceByFeeClick(reduceAmount: BigDecimal, notification: Class<out NotificationUM>) {
        stateController.update(
            AmountReduceByStateTransformer(
                cryptoCurrencyStatus = cryptoCurrencyStatus,
                value = ReduceByData(reduceAmount, reduceAmount),
                minimumTransactionAmount = null,
            ),
        )
        onNotificationCancel(notification)
    }

    private suspend fun setupApprovalNeeded() {
        val approval = StakingIntegrationID.create(currencyId = cryptoCurrencyStatus.currency.id)?.approval
            ?: StakingApproval.Empty

        stakingApproval = approval

        if (approval is StakingApproval.Needed) {
            stakingAllowance = getAllowanceUseCase(
                userWalletId = userWalletId,
                cryptoCurrency = cryptoCurrencyStatus.currency,
                spenderAddress = approval.spenderAddress,
            ).getOrElse { BigDecimal.ZERO }
        }
    }

    private suspend fun setupIsAnyTokenStaked() {
        isAnyTokenStaked = isAnyTokenStakedUseCase(userWalletId).getOrNull() == true
    }

    private fun subscribeOnCurrencyStatusUpdates() {
        getAccountCurrencyStatusUseCase(
            userWalletId = params.userWalletId,
            currency = params.cryptoCurrency,
        )
            .conflate()
            .distinctUntilChanged()
            .filter { value.currentStep == StakingStep.InitialInfo || isTopHeatupCase() }
            .onEach { (maybeAccount, maybeStatus) ->
                isAccountsModeEnabled = isAccountsModeEnabledUseCase.invokeSync()
                account = maybeAccount
                onDataLoaded(maybeStatus)
            }
            .flowOn(dispatchers.main)
            .launchIn(modelScope)
    }

    private suspend fun onDataLoaded(status: CryptoCurrencyStatus) {
        if (!isInitialInfoAnalyticSent) {
            isInitialInfoAnalyticSent = true
            val balances = status.value.stakingBalance as? StakingBalance.Data.StakeKit
            paramsInterceptorHolder.addParamsInterceptor(
                interceptor = StakingParamsInterceptor(status.currency.symbol),
            )
            analyticsEventHandler.send(
                StakingAnalyticsEvent.StakingInfoScreenOpened(
                    validatorsCount = balances?.getValidatorsCount() ?: 0,
                ),
            )
        }

        feeCryptoCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(userWalletId, status).getOrNull()
        minimumTransactionAmount = getMinimumTransactionAmountSyncUseCase(userWalletId, status).getOrNull()
            ?.let { amount ->
                EnterAmountBoundary(
                    amount = amount,
                    fiatRate = status.value.fiatRate.orZero(),
                )
            }
        cryptoCurrencyStatus = status

        checkForTonHeatupCase()
        setupApprovalNeeded()
        setupIsAnyTokenStaked()
        checkIfSubtractAvailable()
        subscribeOnActionsUpdates(status)
        subscribeOnStepChanges(status)
        subscribeOnBalanceHiding()
    }

    private fun subscribeOnBalanceHiding() {
        getBalanceHidingSettingsUseCase()
            .conflate()
            .distinctUntilChanged()
            .onEach { settings ->
                isBalanceHiddenFlow.value = settings.isBalanceHidden
                stateController.update(
                    transformer = HideBalanceStateTransformer(
                        isBalanceHidden = settings.isBalanceHidden,
                        cryptoCurrencyStatus = cryptoCurrencyStatus,
                        appCurrency = currentAppCurrency.value,
                    ),
                )
            }
            .flowOn(dispatchers.main)
            .launchIn(modelScope)
            .saveIn(balanceHidingJobHolder)
    }

    private fun subscribeOnStepChanges(status: CryptoCurrencyStatus) {
        uiState
            .distinctUntilChangedBy { it.currentStep }
            .onEach {
                when {
                    isInitState() -> {
                        updateInitialData(status)

                        if (!isBalanceUpdatedAfterStart) {
                            isBalanceUpdatedAfterStart = true
                            balanceUpdater.partialUpdate()
                        }
                    }
                    isAssentState() -> {
                        getFee()
                        val amountState = value.amountState as? AmountState.Data
                        if (amountState?.amountTextField?.isWarning == true) {
                            stateController.update(
                                AmountRoundToIntegerTransformer(
                                    cryptoCurrencyStatus = cryptoCurrencyStatus,
                                ),
                            )
                        }
                    }
                }
            }
            .flowOn(dispatchers.main)
            .launchIn(modelScope)
            .saveIn(stepChangesJobHolder)
    }

    private fun subscribeOnActionsUpdates(status: CryptoCurrencyStatus) {
        getActionsUseCase(userWalletId = userWalletId, cryptoCurrencyId = cryptoCurrencyId)
            .conflate()
            .distinctUntilChanged()
            .onEach { result ->
                result.getOrNull()?.let { actions ->
                    stakingActions = actions
                    if (isInitState()) {
                        updateInitialData(status)
                    }
                }
            }
            .flowOn(dispatchers.main)
            .launchIn(modelScope)
            .saveIn(actionsJobHolder)
    }

    private fun updateInitialData(status: CryptoCurrencyStatus) {
        stateController.updateAll(
            SetInitialDataStateTransformer(
                clickIntents = this@StakingModel,
                integration = integration,
                isAnyTokenStaked = isAnyTokenStaked,
                cryptoCurrencyStatus = status,
                userWalletProvider = Provider { userWallet },
                appCurrencyProvider = Provider { currentAppCurrency.value },
                balancesToShowProvider = Provider { balancesToShow },
                isAccountsModeEnabled = isAccountsModeEnabled,
                account = account,
                isBalanceHidden = isBalanceHiddenFlow.value,
            ),
            SetConfirmationStateEmptyTransformer,
        )
    }

    private fun prepareForConfirmation(
        balanceType: BalanceType,
        balanceState: BalanceState,
        pendingActions: ImmutableList<PendingAction> = persistentListOf(),
        pendingAction: PendingAction? = pendingActions.firstOrNull(),
        target: StakingTarget?,
        amountValue: String,
    ) {
        stateController.updateAll(
            SetConfirmationStateInitTransformer(
                isEnter = false,
                isExplicitExit = isExplicitExit(balanceType, pendingAction),
                balanceState = balanceState,
                cryptoCurrencyStatus = cryptoCurrencyStatus,
                stakingApproval = stakingApproval,
                pendingActions = pendingActions,
                pendingAction = pendingAction,
                stakingAllowance = stakingAllowance,
                integration = integration,
            ),
            ValidatorSelectChangeTransformer(
                selectedTarget = target,
                integration = integration,
            ),
            SetAmountDataTransformer(
                clickIntents = this,
                cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
                userWalletProvider = Provider { userWallet },
                appCurrencyProvider = Provider { currentAppCurrency.value },
                isAccountsModeEnabled = isAccountsModeEnabled,
                isBalanceHidden = isBalanceHiddenFlow.value,
                account = account,
            ),
            AmountChangeStateTransformer(
                cryptoCurrencyStatus = cryptoCurrencyStatus,
                value = amountValue,
                minimumTransactionAmount = minimumTransactionAmount,
                integration = integration,
                isSolanaUnstakeValidationEnabled = stakingFeatureToggles.isSolanaUnstakeValidationEnabled(),
            ),
        )
    }

    private suspend fun checkForTonHeatupCase() {
        val isAccountInitializedNewValue = checkAccountInitializedUseCase.invoke(
            userWalletId = userWalletId,
            network = cryptoCurrencyStatus.currency.network,
        ).getOrElse { throwable ->
            TangemLogger.e("Error", throwable)
            false
        }

        if (!isAccountInitializedNewValue && isAccountInitialized) {
            isTonHeatupCase = true
        }

        isAccountInitialized = isAccountInitializedNewValue

        if (isTopHeatupCase()) {
            updateNotifications()
        }
    }

    private fun isExplicitExit(balanceType: BalanceType, pendingAction: PendingAction?): Boolean {
        return balanceType == BalanceType.STAKED && pendingAction?.type?.isRestake == false
    }

    private fun isAssentState(): Boolean {
        return value.currentStep == StakingStep.Confirmation &&
            (value.confirmationState as? StakingStates.ConfirmationState.Data)?.innerState ==
            InnerConfirmationStakingState.ASSENT
    }

    private fun isInitState(): Boolean {
        return value.currentStep == StakingStep.InitialInfo
    }

    private suspend fun checkIfSubtractAvailable() {
        isAmountSubtractAvailable = isAmountSubtractAvailableUseCase(
            userWalletId = userWalletId,
            currency = cryptoCurrencyStatus.currency,
        ).getOrElse { false }
    }

    private fun isTopHeatupCase(): Boolean {
        if (!::cryptoCurrencyStatus.isInitialized) return false
        return value.currentStep == StakingStep.Confirmation &&
            isTon(cryptoCurrencyStatus.currency.network.rawId) &&
            isTonHeatupCase
    }

    fun getApprovalParams(): GiveApprovalComponent.Params? {
        val amountState = value.amountState as? AmountState.Data ?: return null
        val approval = stakingApproval as? StakingApproval.Needed ?: return null
        val feeCurrencyStatus = feeCryptoCurrencyStatus ?: return null

        return GiveApprovalComponent.Params(
            userWalletId = userWallet.walletId,
            cryptoCurrencyStatus = cryptoCurrencyStatus,
            feeCryptoCurrencyStatus = feeCurrencyStatus,
            amount = amountState.amountTextField.value,
            spenderAddress = approval.spenderAddress,
            amountFooter = resourceReference(
                id = R.string.give_permission_staking_subtitle,
                formatArgs = wrappedList(cryptoCurrencyStatus.currency.symbol),
            ),
            feeFooter = resourceReference(R.string.staking_give_permission_fee_footer),
            isHoldToConfirm = value.shouldShowHoldToConfirmButton,
            callback = approvalCallback,
        )
    }

    private companion object {
        const val ALLOWANCE_UPDATE_DELAY = 10_000L
    }
}