/* 
 * This file is part of Stack Wallet.
 * 
 * Copyright (c) 2023 Cypher Stack
 * All Rights Reserved.
 * The code is distributed under GPLv3 license, see LICENSE file for details.
 * Generated by Cypher Stack on 2023-05-26
 *
 */

import 'dart:async';
import 'dart:io';

import 'package:cw_core/monero_transaction_priority.dart';
import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:tuple/tuple.dart';

import '../../models/isar/models/isar_models.dart';
import '../../models/paynym/paynym_account_lite.dart';
import '../../models/send_view_auto_fill_data.dart';
import '../../providers/providers.dart';
import '../../providers/ui/fee_rate_type_state_provider.dart';
import '../../providers/ui/preview_tx_button_state_provider.dart';
import '../../providers/wallet/public_private_balance_state_provider.dart';
import '../../route_generator.dart';
import '../../themes/coin_icon_provider.dart';
import '../../themes/stack_colors.dart';
import '../../utilities/address_utils.dart';
import '../../utilities/amount/amount.dart';
import '../../utilities/amount/amount_formatter.dart';
import '../../utilities/amount/amount_input_formatter.dart';
import '../../utilities/amount/amount_unit.dart';
import '../../utilities/assets.dart';
import '../../utilities/barcode_scanner_interface.dart';
import '../../utilities/clipboard_interface.dart';
import '../../utilities/constants.dart';
import '../../utilities/enums/fee_rate_type_enum.dart';
import '../../utilities/extensions/extensions.dart';
import '../../utilities/logger.dart';
import '../../utilities/prefs.dart';
import '../../utilities/text_styles.dart';
import '../../utilities/util.dart';
import '../../wallets/crypto_currency/crypto_currency.dart';
import '../../wallets/crypto_currency/intermediate/nano_currency.dart';
import '../../wallets/isar/providers/wallet_info_provider.dart';
import '../../wallets/models/tx_data.dart';
import '../../wallets/wallet/impl/firo_wallet.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
import '../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
import '../../widgets/animated_text.dart';
import '../../widgets/background.dart';
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
import '../../widgets/custom_buttons/blue_text_button.dart';
import '../../widgets/dialogs/firo_exchange_address_dialog.dart';
import '../../widgets/fee_slider.dart';
import '../../widgets/icon_widgets/addressbook_icon.dart';
import '../../widgets/icon_widgets/clipboard_icon.dart';
import '../../widgets/icon_widgets/qrcode_icon.dart';
import '../../widgets/icon_widgets/x_icon.dart';
import '../../widgets/rounded_white_container.dart';
import '../../widgets/stack_dialog.dart';
import '../../widgets/stack_text_field.dart';
import '../../widgets/textfield_icon_button.dart';
import '../address_book_views/address_book_view.dart';
import '../coin_control/coin_control_view.dart';
import 'confirm_transaction_view.dart';
import 'sub_widgets/building_transaction_dialog.dart';
import 'sub_widgets/firo_balance_selection_sheet.dart';
import 'sub_widgets/transaction_fee_selection_sheet.dart';

class SendView extends ConsumerStatefulWidget {
  const SendView({
    super.key,
    required this.walletId,
    required this.coin,
    this.autoFillData,
    this.clipboard = const ClipboardWrapper(),
    this.barcodeScanner = const BarcodeScannerWrapper(),
    this.accountLite,
  });

  static const String routeName = "/sendView";

  final String walletId;
  final CryptoCurrency coin;
  final SendViewAutoFillData? autoFillData;
  final ClipboardInterface clipboard;
  final BarcodeScannerInterface barcodeScanner;
  final PaynymAccountLite? accountLite;

  @override
  ConsumerState<SendView> createState() => _SendViewState();
}

class _SendViewState extends ConsumerState<SendView> {
  late final String walletId;
  late final CryptoCurrency coin;
  late final ClipboardInterface clipboard;
  late final BarcodeScannerInterface scanner;

  late TextEditingController sendToController;
  late TextEditingController cryptoAmountController;
  late TextEditingController baseAmountController;
  late TextEditingController noteController;
  late TextEditingController onChainNoteController;
  late TextEditingController feeController;
  late TextEditingController memoController;

  late final SendViewAutoFillData? _data;

  final _addressFocusNode = FocusNode();
  final _noteFocusNode = FocusNode();
  final _onChainNoteFocusNode = FocusNode();
  final _cryptoFocus = FocusNode();
  final _baseFocus = FocusNode();
  final _memoFocus = FocusNode();

  late final bool isStellar;
  late final bool isFiro;

  Amount? _cachedAmountToSend;
  String? _address;

  bool _addressToggleFlag = false;

  bool _isFiroExWarningDisplayed = false;

  bool _cryptoAmountChangeLock = false;
  late VoidCallback onCryptoAmountChanged;

  Set<UTXO> selectedUTXOs = {};

  Future<void> _scanQr() async {
    try {
      // ref
      //     .read(
      //         shouldShowLockscreenOnResumeStateProvider
      //             .state)
      //     .state = false;
      if (FocusScope.of(context).hasFocus) {
        FocusScope.of(context).unfocus();
        await Future<void>.delayed(const Duration(milliseconds: 75));
      }

      final qrResult = await scanner.scan();

      // Future<void>.delayed(
      //   const Duration(seconds: 2),
      //   () => ref
      //       .read(
      //           shouldShowLockscreenOnResumeStateProvider
      //               .state)
      //       .state = true,
      // );

      Logging.instance.log(
        "qrResult content: ${qrResult.rawContent}",
        level: LogLevel.Info,
      );

      final results = AddressUtils.parseUri(qrResult.rawContent);

      Logging.instance.log("qrResult parsed: $results", level: LogLevel.Info);

      if (results.isNotEmpty && results["scheme"] == coin.uriScheme) {
        // auto fill address
        _address = (results["address"] ?? "").trim();
        sendToController.text = _address!;

        // autofill notes field
        if (results["message"] != null) {
          noteController.text = results["message"]!;
        } else if (results["label"] != null) {
          noteController.text = results["label"]!;
        }

        // autofill amount field
        if (results["amount"] != null) {
          final Amount amount = Decimal.parse(results["amount"]!).toAmount(
            fractionDigits: coin.fractionDigits,
          );
          cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
                amount,
                withUnitName: false,
              );
          ref.read(pSendAmount.notifier).state = amount;
        }

        _setValidAddressProviders(_address);
        setState(() {
          _addressToggleFlag = sendToController.text.isNotEmpty;
        });

        // now check for non standard encoded basic address
      } else if (ref
          .read(pWallets)
          .getWallet(walletId)
          .cryptoCurrency
          .validateAddress(qrResult.rawContent)) {
        _address = qrResult.rawContent.trim();
        sendToController.text = _address ?? "";

        _setValidAddressProviders(_address);
        setState(() {
          _addressToggleFlag = sendToController.text.isNotEmpty;
        });
      }
    } on PlatformException catch (e, s) {
      // ref
      //     .read(
      //         shouldShowLockscreenOnResumeStateProvider
      //             .state)
      //     .state = true;
      // here we ignore the exception caused by not giving permission
      // to use the camera to scan a qr code
      Logging.instance.log(
        "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s",
        level: LogLevel.Warning,
      );
    }
  }

  void _fiatFieldChanged(String baseAmountString) {
    final baseAmount = Amount.tryParseFiatString(
      baseAmountString,
      locale: ref.read(localeServiceChangeNotifierProvider).locale,
    );
    final Amount? amount;
    if (baseAmount != null) {
      final Decimal _price =
          ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1;

      if (_price == Decimal.zero) {
        amount = 0.toAmountAsRaw(fractionDigits: coin.fractionDigits);
      } else {
        amount = baseAmount <= Amount.zero
            ? 0.toAmountAsRaw(fractionDigits: coin.fractionDigits)
            : (baseAmount.decimal / _price)
                .toDecimal(
                  scaleOnInfinitePrecision: coin.fractionDigits,
                )
                .toAmount(fractionDigits: coin.fractionDigits);
      }
      if (_cachedAmountToSend != null && _cachedAmountToSend == amount) {
        return;
      }
      _cachedAmountToSend = amount;
      Logging.instance
          .log("it changed $amount $_cachedAmountToSend", level: LogLevel.Info);

      final amountString = ref.read(pAmountFormatter(coin)).format(
            amount,
            withUnitName: false,
          );

      _cryptoAmountChangeLock = true;
      cryptoAmountController.text = amountString;
      _cryptoAmountChangeLock = false;
    } else {
      amount = 0.toAmountAsRaw(fractionDigits: coin.fractionDigits);
      _cryptoAmountChangeLock = true;
      cryptoAmountController.text = "";
      _cryptoAmountChangeLock = false;
    }
    // setState(() {
    //   _calculateFeesFuture = calculateFees(
    //       Format.decimalAmountToSatoshis(
    //           _amountToSend!));
    // });
    ref.read(pSendAmount.notifier).state = amount;
  }

  void _cryptoAmountChanged() async {
    if (!_cryptoAmountChangeLock) {
      final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse(
            cryptoAmountController.text,
          );
      final Amount? amount;
      if (cryptoAmount != null) {
        amount = cryptoAmount;
        if (_cachedAmountToSend != null && _cachedAmountToSend == amount) {
          return;
        }
        _cachedAmountToSend = amount;
        Logging.instance.log(
          "it changed $amount $_cachedAmountToSend",
          level: LogLevel.Info,
        );

        final price =
            ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1;

        if (price > Decimal.zero) {
          baseAmountController.text = (amount!.decimal * price)
              .toAmount(
                fractionDigits: 2,
              )
              .fiatString(
                locale: ref.read(localeServiceChangeNotifierProvider).locale,
              );
        }
      } else {
        amount = null;
        baseAmountController.text = "";
      }

      ref.read(pSendAmount.notifier).state = amount;

      _cryptoAmountChangedFeeUpdateTimer?.cancel();
      _cryptoAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () {
        if (coin is! Epiccash && !_baseFocus.hasFocus) {
          setState(() {
            _calculateFeesFuture = calculateFees(
              amount == null
                  ? 0.toAmountAsRaw(fractionDigits: coin.fractionDigits)
                  : amount!,
            );
          });
        }
      });
    }
  }

  final updateFeesTimerDuration = const Duration(milliseconds: 500);

  Timer? _cryptoAmountChangedFeeUpdateTimer;
  Timer? _baseAmountChangedFeeUpdateTimer;

  void _baseAmountChanged() {
    _baseAmountChangedFeeUpdateTimer?.cancel();
    _baseAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () {
      if (coin is! Epiccash && !_cryptoFocus.hasFocus) {
        setState(() {
          _calculateFeesFuture = calculateFees(
            ref.read(pSendAmount) == null
                ? 0.toAmountAsRaw(fractionDigits: coin.fractionDigits)
                : ref.read(pSendAmount)!,
          );
        });
      }
    });
  }

  late Amount _currentFee;

  void _setCurrentFee(String fee, bool shouldSetState) {
    fee = fee.trim();

    if (fee.startsWith("~")) {
      fee = fee.substring(1);
    }
    if (fee.contains(" ")) {
      fee = fee.split(" ").first;
    }

    final value = fee.contains(",")
        ? Decimal.parse(fee.replaceFirst(",", "."))
            .toAmount(fractionDigits: coin.fractionDigits)
        : Decimal.parse(fee).toAmount(fractionDigits: coin.fractionDigits);

    if (shouldSetState) {
      setState(() => _currentFee = value);
    } else {
      _currentFee = value;
    }
  }

  String? _updateInvalidAddressText(String address) {
    if (_data != null && _data!.contactLabel == address) {
      return null;
    }

    if (address.isNotEmpty &&
        !ref
            .read(pWallets)
            .getWallet(walletId)
            .cryptoCurrency
            .validateAddress(address)) {
      return "Invalid address";
    }
    return null;
  }

  void _setValidAddressProviders(String? address) {
    if (isPaynymSend) {
      ref.read(pValidSendToAddress.notifier).state = true;
    } else {
      final wallet = ref.read(pWallets).getWallet(walletId);
      if (wallet is SparkInterface) {
        ref.read(pValidSparkSendToAddress.notifier).state =
            SparkInterface.validateSparkAddress(
          address: address ?? "",
          isTestNet: wallet.cryptoCurrency.network.isTestNet,
        );

        ref.read(pIsExchangeAddress.state).state =
            (coin as Firo).isExchangeAddress(address ?? "");

        if (ref.read(publicPrivateBalanceStateProvider) == FiroType.spark &&
            ref.read(pIsExchangeAddress) &&
            !_isFiroExWarningDisplayed) {
          _isFiroExWarningDisplayed = true;
          showFiroExchangeAddressWarning(
            context,
            () => _isFiroExWarningDisplayed = false,
          );
        }
      }

      ref.read(pValidSendToAddress.notifier).state =
          wallet.cryptoCurrency.validateAddress(address ?? "");
    }
  }

  late Future<String> _calculateFeesFuture;

  Map<Amount, String> cachedFees = {};
  Map<Amount, String> cachedFiroLelantusFees = {};
  Map<Amount, String> cachedFiroSparkFees = {};
  Map<Amount, String> cachedFiroPublicFees = {};

  Future<String> calculateFees(Amount amount) async {
    if (amount <= Amount.zero) {
      return "0";
    }

    if (isFiro) {
      switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
        case FiroType.public:
          if (cachedFiroPublicFees[amount] != null) {
            return cachedFiroPublicFees[amount]!;
          }
          break;
        case FiroType.lelantus:
          if (cachedFiroLelantusFees[amount] != null) {
            return cachedFiroLelantusFees[amount]!;
          }
          break;
        case FiroType.spark:
          if (cachedFiroSparkFees[amount] != null) {
            return cachedFiroSparkFees[amount]!;
          }
          break;
      }
    } else if (cachedFees[amount] != null) {
      return cachedFees[amount]!;
    }

    final wallet = ref.read(pWallets).getWallet(walletId);
    final feeObject = await wallet.fees;

    late final int feeRate;

    switch (ref.read(feeRateTypeStateProvider.state).state) {
      case FeeRateType.fast:
        feeRate = feeObject.fast;
        break;
      case FeeRateType.average:
        feeRate = feeObject.medium;
        break;
      case FeeRateType.slow:
        feeRate = feeObject.slow;
        break;
      default:
        feeRate = -1;
    }

    Amount fee;
    if (coin is Monero) {
      MoneroTransactionPriority specialMoneroId;
      switch (ref.read(feeRateTypeStateProvider.state).state) {
        case FeeRateType.fast:
          specialMoneroId = MoneroTransactionPriority.fast;
          break;
        case FeeRateType.average:
          specialMoneroId = MoneroTransactionPriority.regular;
          break;
        case FeeRateType.slow:
          specialMoneroId = MoneroTransactionPriority.slow;
          break;
        default:
          throw ArgumentError("custom fee not available for monero");
      }

      fee = await wallet.estimateFeeFor(amount, specialMoneroId.raw!);
      cachedFees[amount] = ref.read(pAmountFormatter(coin)).format(
            fee,
            withUnitName: true,
            indicatePrecisionLoss: false,
          );

      return cachedFees[amount]!;
    } else if (isFiro) {
      final firoWallet = wallet as FiroWallet;

      switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
        case FiroType.public:
          fee = await firoWallet.estimateFeeFor(amount, feeRate);
          cachedFiroPublicFees[amount] =
              ref.read(pAmountFormatter(coin)).format(
                    fee,
                    withUnitName: true,
                    indicatePrecisionLoss: false,
                  );
          return cachedFiroPublicFees[amount]!;

        case FiroType.lelantus:
          fee = await firoWallet.estimateFeeForLelantus(amount);
          cachedFiroLelantusFees[amount] =
              ref.read(pAmountFormatter(coin)).format(
                    fee,
                    withUnitName: true,
                    indicatePrecisionLoss: false,
                  );
          return cachedFiroLelantusFees[amount]!;
        case FiroType.spark:
          fee = await firoWallet.estimateFeeForSpark(amount);
          cachedFiroSparkFees[amount] = ref.read(pAmountFormatter(coin)).format(
                fee,
                withUnitName: true,
                indicatePrecisionLoss: false,
              );
          return cachedFiroSparkFees[amount]!;
      }
    } else {
      fee = await wallet.estimateFeeFor(amount, feeRate);
      cachedFees[amount] = ref.read(pAmountFormatter(coin)).format(
            fee,
            withUnitName: true,
            indicatePrecisionLoss: false,
          );

      return cachedFees[amount]!;
    }
  }

  Future<void> _previewTransaction() async {
    // wait for keyboard to disappear
    FocusScope.of(context).unfocus();
    await Future<void>.delayed(
      const Duration(milliseconds: 100),
    );
    final wallet = ref.read(pWallets).getWallet(walletId);

    final Amount amount = ref.read(pSendAmount)!;
    final Amount availableBalance;
    if (isFiro) {
      switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
        case FiroType.public:
          availableBalance = wallet.info.cachedBalance.spendable;
          break;
        case FiroType.lelantus:
          availableBalance = wallet.info.cachedBalanceSecondary.spendable;
          break;
        case FiroType.spark:
          availableBalance = wallet.info.cachedBalanceTertiary.spendable;
          break;
      }
    } else {
      availableBalance = ref.read(pWalletBalance(walletId)).spendable;
    }

    final coinControlEnabled =
        ref.read(prefsChangeNotifierProvider).enableCoinControl;

    if (coin is! Ethereum &&
            !(wallet is CoinControlInterface && coinControlEnabled) ||
        (wallet is CoinControlInterface &&
            coinControlEnabled &&
            selectedUTXOs.isEmpty)) {
      // confirm send all
      if (amount == availableBalance) {
        bool? shouldSendAll;
        if (mounted) {
          shouldSendAll = await showDialog<bool>(
            context: context,
            useSafeArea: false,
            barrierDismissible: true,
            builder: (context) {
              return StackDialog(
                title: "Confirm send all",
                message:
                    "You are about to send your entire balance. Would you like to continue?",
                leftButton: TextButton(
                  style: Theme.of(context)
                      .extension<StackColors>()!
                      .getSecondaryEnabledButtonStyle(context),
                  child: Text(
                    "Cancel",
                    style: STextStyles.button(context).copyWith(
                      color: Theme.of(context)
                          .extension<StackColors>()!
                          .accentColorDark,
                    ),
                  ),
                  onPressed: () {
                    Navigator.of(context).pop(false);
                  },
                ),
                rightButton: TextButton(
                  style: Theme.of(context)
                      .extension<StackColors>()!
                      .getPrimaryEnabledButtonStyle(context),
                  child: Text(
                    "Yes",
                    style: STextStyles.button(context),
                  ),
                  onPressed: () {
                    Navigator.of(context).pop(true);
                  },
                ),
              );
            },
          );
        }

        if (shouldSendAll == null || shouldSendAll == false) {
          // cancel preview
          return;
        }
      }
    }

    try {
      bool wasCancelled = false;

      if (mounted) {
        unawaited(
          showDialog<void>(
            context: context,
            useSafeArea: false,
            barrierDismissible: false,
            builder: (context) {
              return BuildingTransactionDialog(
                coin: wallet.info.coin,
                isSpark: wallet is FiroWallet &&
                    ref.read(publicPrivateBalanceStateProvider.state).state ==
                        FiroType.spark,
                onCancel: () {
                  wasCancelled = true;

                  Navigator.of(context).pop();
                },
              );
            },
          ),
        );
      }

      final time = Future<dynamic>.delayed(
        const Duration(
          milliseconds: 2500,
        ),
      );

      Future<TxData> txDataFuture;

      if (isPaynymSend) {
        final feeRate = ref.read(feeRateTypeStateProvider);
        txDataFuture = (wallet as PaynymInterface).preparePaymentCodeSend(
          txData: TxData(
            paynymAccountLite: widget.accountLite!,
            recipients: [
              (
                address: widget.accountLite!.code,
                amount: amount,
                isChange: false,
              ),
            ],
            satsPerVByte: isCustomFee ? customFeeRate : null,
            feeRateType: feeRate,
            utxos: (wallet is CoinControlInterface &&
                    coinControlEnabled &&
                    selectedUTXOs.isNotEmpty)
                ? selectedUTXOs
                : null,
          ),
        );
      } else if (wallet is FiroWallet) {
        switch (ref.read(publicPrivateBalanceStateProvider.state).state) {
          case FiroType.public:
            if (ref.read(pValidSparkSendToAddress)) {
              txDataFuture = wallet.prepareSparkMintTransaction(
                txData: TxData(
                  sparkRecipients: [
                    (
                      address: _address!,
                      amount: amount,
                      memo: memoController.text,
                      isChange: false,
                    ),
                  ],
                  feeRateType: ref.read(feeRateTypeStateProvider),
                  satsPerVByte: isCustomFee ? customFeeRate : null,
                  utxos: (wallet is CoinControlInterface &&
                          coinControlEnabled &&
                          selectedUTXOs.isNotEmpty)
                      ? selectedUTXOs
                      : null,
                ),
              );
            } else {
              txDataFuture = wallet.prepareSend(
                txData: TxData(
                  recipients: [
                    (
                      address: _address!,
                      amount: amount,
                      isChange: false,
                    ),
                  ],
                  feeRateType: ref.read(feeRateTypeStateProvider),
                  satsPerVByte: isCustomFee ? customFeeRate : null,
                  utxos: (wallet is CoinControlInterface &&
                          coinControlEnabled &&
                          selectedUTXOs.isNotEmpty)
                      ? selectedUTXOs
                      : null,
                ),
              );
            }
            break;

          case FiroType.lelantus:
            txDataFuture = wallet.prepareSendLelantus(
              txData: TxData(
                recipients: [
                  (
                    address: _address!,
                    amount: amount,
                    isChange: false,
                  ),
                ],
              ),
            );
            break;

          case FiroType.spark:
            txDataFuture = wallet.prepareSendSpark(
              txData: TxData(
                recipients: ref.read(pValidSparkSendToAddress)
                    ? null
                    : [
                        (
                          address: _address!,
                          amount: amount,
                          isChange: false,
                        ),
                      ],
                sparkRecipients: ref.read(pValidSparkSendToAddress)
                    ? [
                        (
                          address: _address!,
                          amount: amount,
                          memo: memoController.text,
                          isChange: false,
                        ),
                      ]
                    : null,
              ),
            );
            break;
        }
      } else {
        final memo = coin is Stellar ? memoController.text : null;
        txDataFuture = wallet.prepareSend(
          txData: TxData(
            recipients: [
              (
                address: _address!,
                amount: amount,
                isChange: false,
              ),
            ],
            memo: memo,
            feeRateType: ref.read(feeRateTypeStateProvider),
            satsPerVByte: isCustomFee ? customFeeRate : null,
            utxos: (wallet is CoinControlInterface &&
                    coinControlEnabled &&
                    selectedUTXOs.isNotEmpty)
                ? selectedUTXOs
                : null,
          ),
        );
      }

      final results = await Future.wait([
        txDataFuture,
        time,
      ]);

      TxData txData = results.first as TxData;

      if (!wasCancelled && mounted) {
        if (isPaynymSend) {
          txData = txData.copyWith(
            paynymAccountLite: widget.accountLite!,
            note: noteController.text.isNotEmpty
                ? noteController.text
                : "PayNym send",
          );
        } else {
          txData = txData.copyWith(note: noteController.text);
          txData = txData.copyWith(noteOnChain: onChainNoteController.text);
        }

        // pop building dialog
        Navigator.of(context).pop();

        unawaited(
          Navigator.of(context).push(
            RouteGenerator.getRoute(
              shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
              builder: (_) => ConfirmTransactionView(
                txData: txData,
                walletId: walletId,
                isPaynymTransaction: isPaynymSend,
                onSuccess: clearSendForm,
              ),
              settings: const RouteSettings(
                name: ConfirmTransactionView.routeName,
              ),
            ),
          ),
        );
      }
    } catch (e) {
      if (mounted) {
        // pop building dialog
        Navigator.of(context).pop();

        unawaited(
          showDialog<dynamic>(
            context: context,
            useSafeArea: false,
            barrierDismissible: true,
            builder: (context) {
              return StackDialog(
                title: "Transaction failed",
                message: e.toString(),
                rightButton: TextButton(
                  style: Theme.of(context)
                      .extension<StackColors>()!
                      .getSecondaryEnabledButtonStyle(context),
                  child: Text(
                    "Ok",
                    style: STextStyles.button(context).copyWith(
                      color: Theme.of(context)
                          .extension<StackColors>()!
                          .accentColorDark,
                    ),
                  ),
                  onPressed: () {
                    Navigator.of(context).pop();
                  },
                ),
              );
            },
          ),
        );
      }
    }
  }

  void clearSendForm() {
    sendToController.text = "";
    cryptoAmountController.text = "";
    baseAmountController.text = "";
    noteController.text = "";
    onChainNoteController.text = "";
    feeController.text = "";
    memoController.text = "";
    _address = "";
    _addressToggleFlag = false;
    if (mounted) {
      setState(() {});
    }
  }

  bool get isPaynymSend => widget.accountLite != null;

  bool isCustomFee = false;

  int customFeeRate = 1;

  @override
  void initState() {
    coin = widget.coin;
    WidgetsBinding.instance.addPostFrameCallback((_) {
      ref.refresh(feeSheetSessionCacheProvider);
      ref.refresh(pIsExchangeAddress);
    });
    _currentFee = 0.toAmountAsRaw(fractionDigits: coin.fractionDigits);

    _calculateFeesFuture =
        calculateFees(0.toAmountAsRaw(fractionDigits: coin.fractionDigits));
    _data = widget.autoFillData;
    walletId = widget.walletId;
    clipboard = widget.clipboard;
    scanner = widget.barcodeScanner;
    isStellar = coin is Stellar;
    isFiro = coin is Firo;

    sendToController = TextEditingController();
    cryptoAmountController = TextEditingController();
    baseAmountController = TextEditingController();
    noteController = TextEditingController();
    onChainNoteController = TextEditingController();
    feeController = TextEditingController();
    memoController = TextEditingController();

    onCryptoAmountChanged = _cryptoAmountChanged;
    cryptoAmountController.addListener(onCryptoAmountChanged);
    baseAmountController.addListener(_baseAmountChanged);

    if (_data != null) {
      if (_data!.amount != null) {
        final amount = Amount.fromDecimal(
          _data!.amount!,
          fractionDigits: coin.fractionDigits,
        );

        cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
              amount,
              withUnitName: false,
            );
      }
      sendToController.text = _data!.contactLabel;
      _address = _data!.address.trim();
      _addressToggleFlag = true;

      WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
        _setValidAddressProviders(_address);
      });
    }

    if (isPaynymSend) {
      sendToController.text = widget.accountLite!.nymName;
      noteController.text = "PayNym send";
    }

    // if (coin is! Epiccash) {
    // _cryptoFocus.addListener(() {
    //   if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
    //     if (_amountToSend == null) {
    //       setState(() {
    //         _calculateFeesFuture = calculateFees(0);
    //       });
    //     } else {
    //       setState(() {
    //         _calculateFeesFuture = calculateFees(
    //             Format.decimalAmountToSatoshis(_amountToSend!, coin));
    //       });
    //     }
    //   }
    // });

    // _baseFocus.addListener(() {
    //   if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) {
    //     if (_amountToSend == null) {
    //       setState(() {
    //         _calculateFeesFuture = calculateFees(0);
    //       });
    //     } else {
    //       setState(() {
    //         _calculateFeesFuture = calculateFees(
    //             Format.decimalAmountToSatoshis(_amountToSend!, coin));
    //       });
    //     }
    //   }
    // });
    // }
    super.initState();
  }

  @override
  void dispose() {
    _cryptoAmountChangedFeeUpdateTimer?.cancel();
    _baseAmountChangedFeeUpdateTimer?.cancel();

    cryptoAmountController.removeListener(onCryptoAmountChanged);
    baseAmountController.removeListener(_baseAmountChanged);

    sendToController.dispose();
    cryptoAmountController.dispose();
    baseAmountController.dispose();
    noteController.dispose();
    onChainNoteController.dispose();
    feeController.dispose();
    memoController.dispose();

    _noteFocusNode.dispose();
    _onChainNoteFocusNode.dispose();
    _addressFocusNode.dispose();
    _cryptoFocus.dispose();
    _baseFocus.dispose();
    _memoFocus.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    debugPrint("BUILD: $runtimeType");
    final wallet = ref.watch(pWallets).getWallet(walletId);
    final String locale = ref.watch(
      localeServiceChangeNotifierProvider.select((value) => value.locale),
    );

    final showCoinControl = wallet is CoinControlInterface &&
        ref.watch(
          prefsChangeNotifierProvider.select(
            (value) => value.enableCoinControl,
          ),
        ) &&
        (coin is Firo
            ? ref.watch(publicPrivateBalanceStateProvider) == FiroType.public
            : true);

    if (isFiro) {
      final isExchangeAddress = ref.watch(pIsExchangeAddress);

      ref.listen(publicPrivateBalanceStateProvider, (previous, next) {
        selectedUTXOs = {};

        if (ref.read(pSendAmount) == null) {
          setState(() {
            _calculateFeesFuture = calculateFees(
              0.toAmountAsRaw(fractionDigits: coin.fractionDigits),
            );
          });
        } else {
          setState(() {
            _calculateFeesFuture = calculateFees(
              ref.read(pSendAmount)!,
            );
          });
        }

        if (previous != next &&
            next == FiroType.spark &&
            isExchangeAddress &&
            !_isFiroExWarningDisplayed) {
          _isFiroExWarningDisplayed = true;
          WidgetsBinding.instance.addPostFrameCallback(
            (_) => showFiroExchangeAddressWarning(
              context,
              () => _isFiroExWarningDisplayed = false,
            ),
          );
        }
      });
    }

    // add listener for epic cash to strip http:// and https:// prefixes if the address also ocntains an @ symbol (indicating an epicbox address)
    if (coin is Epiccash) {
      sendToController.addListener(() {
        _address = sendToController.text.trim();

        if (_address != null && _address!.isNotEmpty) {
          _address = _address!.trim();
          if (_address!.contains("\n")) {
            _address = _address!.substring(0, _address!.indexOf("\n"));
          }

          sendToController.text = formatAddress(_address!);
        }
      });
    }

    return Background(
      child: Scaffold(
        backgroundColor: Theme.of(context).extension<StackColors>()!.background,
        appBar: AppBar(
          leading: AppBarBackButton(
            onPressed: () async {
              if (FocusScope.of(context).hasFocus) {
                FocusScope.of(context).unfocus();
                await Future<void>.delayed(const Duration(milliseconds: 50));
              }
              if (context.mounted) {
                Navigator.of(context).pop();
              }
            },
          ),
          title: Text(
            "Send ${coin.ticker}",
            style: STextStyles.navBarTitle(context),
          ),
        ),
        body: LayoutBuilder(
          builder: (builderContext, constraints) {
            return Padding(
              padding: const EdgeInsets.only(
                left: 12,
                top: 12,
                right: 12,
              ),
              child: SingleChildScrollView(
                child: ConstrainedBox(
                  constraints: BoxConstraints(
                    // subtract top and bottom padding set in parent
                    minHeight: constraints.maxHeight - 24,
                  ),
                  child: IntrinsicHeight(
                    child: Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 4),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.stretch,
                        children: [
                          Container(
                            decoration: BoxDecoration(
                              color: Theme.of(context)
                                  .extension<StackColors>()!
                                  .popupBG,
                              borderRadius: BorderRadius.circular(
                                Constants.size.circularBorderRadius,
                              ),
                            ),
                            child: Padding(
                              padding: const EdgeInsets.all(12.0),
                              child: Row(
                                children: [
                                  SvgPicture.file(
                                    File(
                                      ref.watch(
                                        coinIconProvider(coin),
                                      ),
                                    ),
                                    width: 22,
                                    height: 22,
                                  ),
                                  const SizedBox(
                                    width: 6,
                                  ),
                                  Column(
                                    crossAxisAlignment:
                                        CrossAxisAlignment.start,
                                    children: [
                                      Text(
                                        ref.watch(pWalletName(walletId)),
                                        style: STextStyles.titleBold12(context)
                                            .copyWith(fontSize: 14),
                                        overflow: TextOverflow.ellipsis,
                                        maxLines: 1,
                                      ),
                                      // const SizedBox(
                                      //   height: 2,
                                      // ),
                                      if (isFiro)
                                        Text(
                                          "${ref.watch(publicPrivateBalanceStateProvider.state).state.name.capitalize()} balance",
                                          style: STextStyles.label(context)
                                              .copyWith(fontSize: 10),
                                        ),
                                      if (coin is! Firo)
                                        Text(
                                          "Available balance",
                                          style: STextStyles.label(context)
                                              .copyWith(fontSize: 10),
                                        ),
                                    ],
                                  ),
                                  const Spacer(),
                                  Builder(
                                    builder: (context) {
                                      final Amount amount;
                                      if (isFiro) {
                                        switch (ref
                                            .watch(
                                              publicPrivateBalanceStateProvider
                                                  .state,
                                            )
                                            .state) {
                                          case FiroType.public:
                                            amount = ref
                                                .read(pWalletBalance(walletId))
                                                .spendable;
                                            break;
                                          case FiroType.lelantus:
                                            amount = ref
                                                .read(
                                                  pWalletBalanceSecondary(
                                                    walletId,
                                                  ),
                                                )
                                                .spendable;
                                            break;
                                          case FiroType.spark:
                                            amount = ref
                                                .read(
                                                  pWalletBalanceTertiary(
                                                    walletId,
                                                  ),
                                                )
                                                .spendable;
                                            break;
                                        }
                                      } else {
                                        amount = ref
                                            .read(pWalletBalance(walletId))
                                            .spendable;
                                      }

                                      return GestureDetector(
                                        onTap: () {
                                          cryptoAmountController.text = ref
                                              .read(pAmountFormatter(coin))
                                              .format(
                                                amount,
                                                withUnitName: false,
                                              );
                                        },
                                        child: Container(
                                          color: Colors.transparent,
                                          child: Column(
                                            crossAxisAlignment:
                                                CrossAxisAlignment.end,
                                            children: [
                                              Text(
                                                ref
                                                    .watch(
                                                      pAmountFormatter(coin),
                                                    )
                                                    .format(amount),
                                                style: STextStyles.titleBold12(
                                                  context,
                                                ).copyWith(
                                                  fontSize: 10,
                                                ),
                                                textAlign: TextAlign.right,
                                              ),
                                              Text(
                                                "${(amount.decimal * ref.watch(priceAnd24hChangeNotifierProvider.select((value) => value.getPrice(coin).item1))).toAmount(
                                                      fractionDigits: 2,
                                                    ).fiatString(
                                                      locale: locale,
                                                    )} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}",
                                                style: STextStyles.subtitle(
                                                  context,
                                                ).copyWith(
                                                  fontSize: 8,
                                                ),
                                                textAlign: TextAlign.right,
                                              ),
                                            ],
                                          ),
                                        ),
                                      );
                                    },
                                  ),
                                ],
                              ),
                            ),
                          ),
                          const SizedBox(
                            height: 16,
                          ),
                          Row(
                            mainAxisAlignment: MainAxisAlignment.spaceBetween,
                            children: [
                              Text(
                                isPaynymSend
                                    ? "Send to PayNym address"
                                    : "Send to",
                                style: STextStyles.smallMed12(context),
                                textAlign: TextAlign.left,
                              ),
                              // if (coin is Monero)
                              //   CustomTextButton(
                              //     text: "Use OpenAlias",
                              //     onTap: () async {
                              //       await showModalBottomSheet(
                              //         context: context,
                              //         builder: (context) =>
                              //             OpenAliasBottomSheet(
                              //           onSelected: (address) {
                              //             sendToController.text = address;
                              //           },
                              //         ),
                              //       );
                              //     },
                              //   ),
                            ],
                          ),
                          const SizedBox(
                            height: 8,
                          ),
                          if (isPaynymSend)
                            TextField(
                              key: const Key("sendViewPaynymAddressFieldKey"),
                              controller: sendToController,
                              enabled: false,
                              readOnly: true,
                              style: STextStyles.fieldLabel(context),
                            ),
                          if (!isPaynymSend)
                            ClipRRect(
                              borderRadius: BorderRadius.circular(
                                Constants.size.circularBorderRadius,
                              ),
                              child: TextField(
                                key: const Key("sendViewAddressFieldKey"),
                                controller: sendToController,
                                readOnly: false,
                                autocorrect: false,
                                enableSuggestions: false,
                                // inputFormatters: <TextInputFormatter>[
                                //   FilteringTextInputFormatter.allow(
                                //       RegExp("[a-zA-Z0-9]{34}")),
                                // ],
                                toolbarOptions: const ToolbarOptions(
                                  copy: false,
                                  cut: false,
                                  paste: true,
                                  selectAll: false,
                                ),
                                onChanged: (newValue) {
                                  _address = newValue.trim();
                                  _setValidAddressProviders(_address);

                                  setState(() {
                                    _addressToggleFlag = newValue.isNotEmpty;
                                  });
                                },
                                focusNode: _addressFocusNode,
                                style: STextStyles.field(context),
                                decoration: standardInputDecoration(
                                  "Enter ${coin.ticker} address",
                                  _addressFocusNode,
                                  context,
                                ).copyWith(
                                  contentPadding: const EdgeInsets.only(
                                    left: 16,
                                    top: 6,
                                    bottom: 8,
                                    right: 5,
                                  ),
                                  suffixIcon: Padding(
                                    padding: sendToController.text.isEmpty
                                        ? const EdgeInsets.only(right: 8)
                                        : const EdgeInsets.only(right: 0),
                                    child: UnconstrainedBox(
                                      child: Row(
                                        mainAxisAlignment:
                                            MainAxisAlignment.spaceAround,
                                        children: [
                                          _addressToggleFlag
                                              ? TextFieldIconButton(
                                                  semanticsLabel:
                                                      "Clear Button. Clears The Address Field Input.",
                                                  key: const Key(
                                                    "sendViewClearAddressFieldButtonKey",
                                                  ),
                                                  onTap: () {
                                                    sendToController.text = "";
                                                    _address = "";
                                                    _setValidAddressProviders(
                                                      _address,
                                                    );
                                                    setState(() {
                                                      _addressToggleFlag =
                                                          false;
                                                    });
                                                  },
                                                  child: const XIcon(),
                                                )
                                              : TextFieldIconButton(
                                                  semanticsLabel:
                                                      "Paste Button. Pastes From Clipboard To Address Field Input.",
                                                  key: const Key(
                                                    "sendViewPasteAddressFieldButtonKey",
                                                  ),
                                                  onTap: () async {
                                                    final ClipboardData? data =
                                                        await clipboard.getData(
                                                      Clipboard.kTextPlain,
                                                    );
                                                    if (data?.text != null &&
                                                        data!
                                                            .text!.isNotEmpty) {
                                                      String content =
                                                          data.text!.trim();
                                                      if (content
                                                          .contains("\n")) {
                                                        content =
                                                            content.substring(
                                                          0,
                                                          content.indexOf(
                                                            "\n",
                                                          ),
                                                        );
                                                      }

                                                      if (coin is Epiccash) {
                                                        // strip http:// and https:// if content contains @
                                                        content = formatAddress(
                                                          content,
                                                        );
                                                      }
                                                      sendToController.text =
                                                          content.trim();
                                                      _address = content.trim();

                                                      _setValidAddressProviders(
                                                        _address,
                                                      );
                                                      setState(() {
                                                        _addressToggleFlag =
                                                            sendToController
                                                                .text
                                                                .isNotEmpty;
                                                      });
                                                    }
                                                  },
                                                  child: sendToController
                                                          .text.isEmpty
                                                      ? const ClipboardIcon()
                                                      : const XIcon(),
                                                ),
                                          if (sendToController.text.isEmpty)
                                            TextFieldIconButton(
                                              semanticsLabel:
                                                  "Address Book Button. Opens Address Book For Address Field.",
                                              key: const Key(
                                                "sendViewAddressBookButtonKey",
                                              ),
                                              onTap: () {
                                                Navigator.of(context).pushNamed(
                                                  AddressBookView.routeName,
                                                  arguments: widget.coin,
                                                );
                                              },
                                              child: const AddressBookIcon(),
                                            ),
                                          if (sendToController.text.isEmpty)
                                            TextFieldIconButton(
                                              semanticsLabel:
                                                  "Scan QR Button. Opens Camera For Scanning QR Code.",
                                              key: const Key(
                                                "sendViewScanQrButtonKey",
                                              ),
                                              onTap: _scanQr,
                                              child: const QrCodeIcon(),
                                            ),
                                        ],
                                      ),
                                    ),
                                  ),
                                ),
                              ),
                            ),
                          const SizedBox(
                            height: 10,
                          ),
                          if (isStellar ||
                              (ref.watch(pValidSparkSendToAddress) &&
                                  ref.watch(
                                        publicPrivateBalanceStateProvider,
                                      ) !=
                                      FiroType.lelantus))
                            ClipRRect(
                              borderRadius: BorderRadius.circular(
                                Constants.size.circularBorderRadius,
                              ),
                              child: TextField(
                                key: const Key("sendViewMemoFieldKey"),
                                maxLength: (coin is Firo) ? 31 : null,
                                controller: memoController,
                                readOnly: false,
                                autocorrect: false,
                                enableSuggestions: false,
                                focusNode: _memoFocus,
                                style: STextStyles.field(context),
                                onChanged: (_) {
                                  setState(() {});
                                },
                                decoration: standardInputDecoration(
                                  "Enter memo (optional)",
                                  _memoFocus,
                                  context,
                                ).copyWith(
                                  counterText: '',
                                  contentPadding: const EdgeInsets.only(
                                    left: 16,
                                    top: 6,
                                    bottom: 8,
                                    right: 5,
                                  ),
                                  suffixIcon: Padding(
                                    padding: memoController.text.isEmpty
                                        ? const EdgeInsets.only(right: 8)
                                        : const EdgeInsets.only(right: 0),
                                    child: UnconstrainedBox(
                                      child: Row(
                                        mainAxisAlignment:
                                            MainAxisAlignment.spaceAround,
                                        children: [
                                          memoController.text.isNotEmpty
                                              ? TextFieldIconButton(
                                                  semanticsLabel:
                                                      "Clear Button. Clears The Memo Field Input.",
                                                  key: const Key(
                                                    "sendViewClearMemoFieldButtonKey",
                                                  ),
                                                  onTap: () {
                                                    memoController.text = "";
                                                    setState(() {});
                                                  },
                                                  child: const XIcon(),
                                                )
                                              : TextFieldIconButton(
                                                  semanticsLabel:
                                                      "Paste Button. Pastes From Clipboard To Memo Field Input.",
                                                  key: const Key(
                                                    "sendViewPasteMemoFieldButtonKey",
                                                  ),
                                                  onTap: () async {
                                                    final ClipboardData? data =
                                                        await clipboard.getData(
                                                      Clipboard.kTextPlain,
                                                    );
                                                    if (data?.text != null &&
                                                        data!
                                                            .text!.isNotEmpty) {
                                                      final String content =
                                                          data.text!.trim();

                                                      memoController.text =
                                                          content.trim();

                                                      setState(() {});
                                                    }
                                                  },
                                                  child: const ClipboardIcon(),
                                                ),
                                        ],
                                      ),
                                    ),
                                  ),
                                ),
                              ),
                            ),
                          Builder(
                            builder: (_) {
                              final String? error;

                              if (_address == null || _address!.isEmpty) {
                                error = null;
                              } else if (isFiro) {
                                if (ref.watch(
                                      publicPrivateBalanceStateProvider,
                                    ) ==
                                    FiroType.lelantus) {
                                  if (_data != null &&
                                      _data!.contactLabel == _address) {
                                    error = SparkInterface.validateSparkAddress(
                                      address: _data!.address,
                                      isTestNet: coin.network ==
                                          CryptoCurrencyNetwork.test,
                                    )
                                        ? "Unsupported"
                                        : null;
                                  } else if (ref
                                      .watch(pValidSparkSendToAddress)) {
                                    error = "Unsupported";
                                  } else {
                                    error = ref.watch(pValidSendToAddress)
                                        ? null
                                        : "Invalid address";
                                  }
                                } else {
                                  if (_data != null &&
                                      _data!.contactLabel == _address) {
                                    error = null;
                                  } else if (!ref.watch(pValidSendToAddress) &&
                                      !ref.watch(pValidSparkSendToAddress)) {
                                    error = "Invalid address";
                                  } else {
                                    error = null;
                                  }
                                }
                              } else {
                                if (_data != null &&
                                    _data!.contactLabel == _address) {
                                  error = null;
                                } else if (!ref.watch(pValidSendToAddress)) {
                                  error = "Invalid address";
                                } else {
                                  error = null;
                                }
                              }

                              if (error == null || error.isEmpty) {
                                return Container();
                              } else {
                                return Align(
                                  alignment: Alignment.topLeft,
                                  child: Padding(
                                    padding: const EdgeInsets.only(
                                      left: 12.0,
                                      top: 4.0,
                                    ),
                                    child: Text(
                                      error,
                                      textAlign: TextAlign.left,
                                      style:
                                          STextStyles.label(context).copyWith(
                                        color: Theme.of(context)
                                            .extension<StackColors>()!
                                            .textError,
                                      ),
                                    ),
                                  ),
                                );
                              }
                            },
                          ),
                          if (isFiro)
                            const SizedBox(
                              height: 12,
                            ),
                          if (isFiro)
                            Text(
                              "Send from",
                              style: STextStyles.smallMed12(context),
                              textAlign: TextAlign.left,
                            ),
                          if (isFiro)
                            const SizedBox(
                              height: 8,
                            ),
                          if (isFiro)
                            Stack(
                              children: [
                                TextField(
                                  autocorrect: Util.isDesktop ? false : true,
                                  enableSuggestions:
                                      Util.isDesktop ? false : true,
                                  readOnly: true,
                                  textInputAction: TextInputAction.none,
                                ),
                                Padding(
                                  padding: const EdgeInsets.symmetric(
                                    horizontal: 12,
                                  ),
                                  child: RawMaterialButton(
                                    splashColor: Theme.of(context)
                                        .extension<StackColors>()!
                                        .highlight,
                                    shape: RoundedRectangleBorder(
                                      borderRadius: BorderRadius.circular(
                                        Constants.size.circularBorderRadius,
                                      ),
                                    ),
                                    onPressed: () {
                                      showModalBottomSheet<dynamic>(
                                        backgroundColor: Colors.transparent,
                                        context: context,
                                        shape: const RoundedRectangleBorder(
                                          borderRadius: BorderRadius.vertical(
                                            top: Radius.circular(20),
                                          ),
                                        ),
                                        builder: (_) =>
                                            FiroBalanceSelectionSheet(
                                          walletId: walletId,
                                        ),
                                      );
                                    },
                                    child: Row(
                                      mainAxisAlignment:
                                          MainAxisAlignment.spaceBetween,
                                      children: [
                                        Row(
                                          children: [
                                            Text(
                                              "${ref.watch(publicPrivateBalanceStateProvider.state).state.name.capitalize()} balance",
                                              style: STextStyles.itemSubtitle12(
                                                context,
                                              ),
                                            ),
                                            const SizedBox(
                                              width: 10,
                                            ),
                                            Builder(
                                              builder: (_) {
                                                final Amount amount;
                                                switch (ref
                                                    .read(
                                                      publicPrivateBalanceStateProvider
                                                          .state,
                                                    )
                                                    .state) {
                                                  case FiroType.public:
                                                    amount = ref
                                                        .watch(
                                                          pWalletBalance(
                                                            walletId,
                                                          ),
                                                        )
                                                        .spendable;
                                                    break;
                                                  case FiroType.lelantus:
                                                    amount = ref
                                                        .watch(
                                                          pWalletBalanceSecondary(
                                                            walletId,
                                                          ),
                                                        )
                                                        .spendable;
                                                    break;
                                                  case FiroType.spark:
                                                    amount = ref
                                                        .watch(
                                                          pWalletBalanceTertiary(
                                                            walletId,
                                                          ),
                                                        )
                                                        .spendable;
                                                    break;
                                                }

                                                return Text(
                                                  ref
                                                      .watch(
                                                        pAmountFormatter(coin),
                                                      )
                                                      .format(
                                                        amount,
                                                      ),
                                                  style:
                                                      STextStyles.itemSubtitle(
                                                    context,
                                                  ),
                                                );
                                              },
                                            ),
                                          ],
                                        ),
                                        SvgPicture.asset(
                                          Assets.svg.chevronDown,
                                          width: 8,
                                          height: 4,
                                          color: Theme.of(context)
                                              .extension<StackColors>()!
                                              .textSubtitle2,
                                        ),
                                      ],
                                    ),
                                  ),
                                ),
                              ],
                            ),
                          const SizedBox(
                            height: 12,
                          ),
                          Row(
                            mainAxisAlignment: MainAxisAlignment.spaceBetween,
                            children: [
                              Text(
                                "Amount",
                                style: STextStyles.smallMed12(context),
                                textAlign: TextAlign.left,
                              ),
                              if (coin is! Ethereum && coin is! Tezos)
                                CustomTextButton(
                                  text: "Send all ${coin.ticker}",
                                  onTap: () async {
                                    if (isFiro) {
                                      final Amount amount;
                                      switch (ref
                                          .read(
                                            publicPrivateBalanceStateProvider
                                                .state,
                                          )
                                          .state) {
                                        case FiroType.public:
                                          amount = ref
                                              .read(pWalletBalance(walletId))
                                              .spendable;
                                          break;
                                        case FiroType.lelantus:
                                          amount = ref
                                              .read(
                                                pWalletBalanceSecondary(
                                                  walletId,
                                                ),
                                              )
                                              .spendable;
                                          break;
                                        case FiroType.spark:
                                          amount = ref
                                              .read(
                                                pWalletBalanceTertiary(
                                                  walletId,
                                                ),
                                              )
                                              .spendable;
                                          break;
                                      }

                                      cryptoAmountController.text = ref
                                          .read(pAmountFormatter(coin))
                                          .format(
                                            amount,
                                            withUnitName: false,
                                          );
                                    } else {
                                      cryptoAmountController.text = ref
                                          .read(pAmountFormatter(coin))
                                          .format(
                                            ref
                                                .read(pWalletBalance(walletId))
                                                .spendable,
                                            withUnitName: false,
                                          );
                                    }
                                    _cryptoAmountChanged();
                                  },
                                ),
                            ],
                          ),
                          const SizedBox(
                            height: 8,
                          ),
                          TextField(
                            autocorrect: Util.isDesktop ? false : true,
                            enableSuggestions: Util.isDesktop ? false : true,
                            style: STextStyles.smallMed14(context).copyWith(
                              color: Theme.of(context)
                                  .extension<StackColors>()!
                                  .textDark,
                            ),
                            key:
                                const Key("amountInputFieldCryptoTextFieldKey"),
                            controller: cryptoAmountController,
                            focusNode: _cryptoFocus,
                            keyboardType: Util.isDesktop
                                ? null
                                : const TextInputType.numberWithOptions(
                                    signed: false,
                                    decimal: true,
                                  ),
                            textAlign: TextAlign.right,
                            inputFormatters: [
                              AmountInputFormatter(
                                decimals: coin.fractionDigits,
                                unit: ref.watch(pAmountUnit(coin)),
                                locale: locale,
                              ),

                              // regex to validate a crypto amount with 8 decimal places
                              // TextInputFormatter.withFunction((oldValue,
                              //         newValue) =>
                              //     // RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$')
                              //     // RegExp(r'^\d{1,3}([,\.]\d+)?|[,\.\d]+$')
                              //     getAmountRegex(locale, coin.fractionDigits)
                              //             .hasMatch(newValue.text)
                              //         ? newValue
                              //         : oldValue),
                            ],
                            decoration: InputDecoration(
                              contentPadding: const EdgeInsets.only(
                                top: 12,
                                right: 12,
                              ),
                              hintText: "0",
                              hintStyle:
                                  STextStyles.fieldLabel(context).copyWith(
                                fontSize: 14,
                              ),
                              prefixIcon: FittedBox(
                                fit: BoxFit.scaleDown,
                                child: Padding(
                                  padding: const EdgeInsets.all(12),
                                  child: Text(
                                    ref
                                        .watch(pAmountUnit(coin))
                                        .unitForCoin(coin),
                                    style: STextStyles.smallMed14(context)
                                        .copyWith(
                                      color: Theme.of(context)
                                          .extension<StackColors>()!
                                          .accentColorDark,
                                    ),
                                  ),
                                ),
                              ),
                            ),
                          ),
                          if (Prefs.instance.externalCalls)
                            const SizedBox(
                              height: 8,
                            ),
                          if (Prefs.instance.externalCalls)
                            TextField(
                              autocorrect: Util.isDesktop ? false : true,
                              enableSuggestions: Util.isDesktop ? false : true,
                              style: STextStyles.smallMed14(context).copyWith(
                                color: Theme.of(context)
                                    .extension<StackColors>()!
                                    .textDark,
                              ),
                              key:
                                  const Key("amountInputFieldFiatTextFieldKey"),
                              controller: baseAmountController,
                              focusNode: _baseFocus,
                              keyboardType: Util.isDesktop
                                  ? null
                                  : const TextInputType.numberWithOptions(
                                      signed: false,
                                      decimal: true,
                                    ),
                              textAlign: TextAlign.right,
                              inputFormatters: [
                                AmountInputFormatter(
                                  decimals: 2,
                                  locale: locale,
                                ),
                                // regex to validate a fiat amount with 2 decimal places
                                // TextInputFormatter.withFunction((oldValue,
                                //         newValue) =>
                                //     // RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$')
                                //     getAmountRegex(locale, 2)
                                //             .hasMatch(newValue.text)
                                //         ? newValue
                                //         : oldValue),
                              ],
                              onChanged: _fiatFieldChanged,
                              decoration: InputDecoration(
                                contentPadding: const EdgeInsets.only(
                                  top: 12,
                                  right: 12,
                                ),
                                hintText: "0",
                                hintStyle:
                                    STextStyles.fieldLabel(context).copyWith(
                                  fontSize: 14,
                                ),
                                prefixIcon: FittedBox(
                                  fit: BoxFit.scaleDown,
                                  child: Padding(
                                    padding: const EdgeInsets.all(12),
                                    child: Text(
                                      ref.watch(
                                        prefsChangeNotifierProvider
                                            .select((value) => value.currency),
                                      ),
                                      style: STextStyles.smallMed14(context)
                                          .copyWith(
                                        color: Theme.of(context)
                                            .extension<StackColors>()!
                                            .accentColorDark,
                                      ),
                                    ),
                                  ),
                                ),
                              ),
                            ),
                          if (showCoinControl)
                            const SizedBox(
                              height: 8,
                            ),
                          if (showCoinControl)
                            RoundedWhiteContainer(
                              child: Row(
                                mainAxisAlignment:
                                    MainAxisAlignment.spaceBetween,
                                children: [
                                  Text(
                                    "Coin control",
                                    style:
                                        STextStyles.w500_14(context).copyWith(
                                      color: Theme.of(context)
                                          .extension<StackColors>()!
                                          .textSubtitle1,
                                    ),
                                  ),
                                  CustomTextButton(
                                    text: selectedUTXOs.isEmpty
                                        ? "Select coins"
                                        : "Selected coins (${selectedUTXOs.length})",
                                    onTap: () async {
                                      if (FocusScope.of(context).hasFocus) {
                                        FocusScope.of(context).unfocus();
                                        await Future<void>.delayed(
                                          const Duration(milliseconds: 100),
                                        );
                                      }

                                      if (context.mounted) {
                                        final spendable = ref
                                            .read(pWalletBalance(walletId))
                                            .spendable;

                                        Amount? amount;
                                        if (ref.read(pSendAmount) != null) {
                                          amount = ref.read(pSendAmount)!;

                                          if (spendable == amount) {
                                            // this is now a send all
                                          } else {
                                            amount += _currentFee;
                                          }
                                        }

                                        final result =
                                            await Navigator.of(context)
                                                .pushNamed(
                                          CoinControlView.routeName,
                                          arguments: Tuple4(
                                            walletId,
                                            CoinControlViewType.use,
                                            amount,
                                            selectedUTXOs,
                                          ),
                                        );

                                        if (result is Set<UTXO>) {
                                          setState(() {
                                            selectedUTXOs = result;
                                          });
                                        }
                                      }
                                    },
                                  ),
                                ],
                              ),
                            ),
                          const SizedBox(
                            height: 12,
                          ),
                          if (coin is Epiccash)
                            Text(
                              "On chain Note (optional)",
                              style: STextStyles.smallMed12(context),
                              textAlign: TextAlign.left,
                            ),
                          if (coin is Epiccash)
                            const SizedBox(
                              height: 8,
                            ),
                          if (coin is Epiccash)
                            ClipRRect(
                              borderRadius: BorderRadius.circular(
                                Constants.size.circularBorderRadius,
                              ),
                              child: TextField(
                                autocorrect: Util.isDesktop ? false : true,
                                enableSuggestions:
                                    Util.isDesktop ? false : true,
                                maxLength: 256,
                                controller: onChainNoteController,
                                focusNode: _onChainNoteFocusNode,
                                style: STextStyles.field(context),
                                onChanged: (_) => setState(() {}),
                                decoration: standardInputDecoration(
                                  "Type something...",
                                  _onChainNoteFocusNode,
                                  context,
                                ).copyWith(
                                  suffixIcon: onChainNoteController
                                          .text.isNotEmpty
                                      ? Padding(
                                          padding:
                                              const EdgeInsets.only(right: 0),
                                          child: UnconstrainedBox(
                                            child: Row(
                                              children: [
                                                TextFieldIconButton(
                                                  child: const XIcon(),
                                                  onTap: () async {
                                                    setState(() {
                                                      onChainNoteController
                                                          .text = "";
                                                    });
                                                  },
                                                ),
                                              ],
                                            ),
                                          ),
                                        )
                                      : null,
                                ),
                              ),
                            ),
                          if (coin is Epiccash)
                            const SizedBox(
                              height: 12,
                            ),
                          Text(
                            (coin is Epiccash)
                                ? "Local Note (optional)"
                                : "Note (optional)",
                            style: STextStyles.smallMed12(context),
                            textAlign: TextAlign.left,
                          ),
                          const SizedBox(
                            height: 8,
                          ),
                          ClipRRect(
                            borderRadius: BorderRadius.circular(
                              Constants.size.circularBorderRadius,
                            ),
                            child: TextField(
                              autocorrect: Util.isDesktop ? false : true,
                              enableSuggestions: Util.isDesktop ? false : true,
                              controller: noteController,
                              focusNode: _noteFocusNode,
                              style: STextStyles.field(context),
                              onChanged: (_) => setState(() {}),
                              decoration: standardInputDecoration(
                                "Type something...",
                                _noteFocusNode,
                                context,
                              ).copyWith(
                                suffixIcon: noteController.text.isNotEmpty
                                    ? Padding(
                                        padding:
                                            const EdgeInsets.only(right: 0),
                                        child: UnconstrainedBox(
                                          child: Row(
                                            children: [
                                              TextFieldIconButton(
                                                child: const XIcon(),
                                                onTap: () async {
                                                  setState(() {
                                                    noteController.text = "";
                                                  });
                                                },
                                              ),
                                            ],
                                          ),
                                        ),
                                      )
                                    : null,
                              ),
                            ),
                          ),
                          const SizedBox(
                            height: 12,
                          ),
                          if (coin is! Epiccash &&
                              coin is! NanoCurrency &&
                              coin is! Tezos)
                            Text(
                              "Transaction fee (estimated)",
                              style: STextStyles.smallMed12(context),
                              textAlign: TextAlign.left,
                            ),
                          if (coin is! Epiccash &&
                              coin is! NanoCurrency &&
                              coin is! Tezos)
                            const SizedBox(
                              height: 8,
                            ),
                          if (coin is! Epiccash &&
                              coin is! NanoCurrency &&
                              coin is! Tezos)
                            Stack(
                              children: [
                                TextField(
                                  autocorrect: Util.isDesktop ? false : true,
                                  enableSuggestions:
                                      Util.isDesktop ? false : true,
                                  controller: feeController,
                                  readOnly: true,
                                  textInputAction: TextInputAction.none,
                                ),
                                Padding(
                                  padding: const EdgeInsets.symmetric(
                                    horizontal: 12,
                                  ),
                                  child: RawMaterialButton(
                                    splashColor: Theme.of(context)
                                        .extension<StackColors>()!
                                        .highlight,
                                    shape: RoundedRectangleBorder(
                                      borderRadius: BorderRadius.circular(
                                        Constants.size.circularBorderRadius,
                                      ),
                                    ),
                                    onPressed: isFiro &&
                                            ref
                                                    .watch(
                                                      publicPrivateBalanceStateProvider
                                                          .state,
                                                    )
                                                    .state !=
                                                FiroType.public
                                        ? null
                                        : () {
                                            showModalBottomSheet<dynamic>(
                                              backgroundColor:
                                                  Colors.transparent,
                                              context: context,
                                              shape:
                                                  const RoundedRectangleBorder(
                                                borderRadius:
                                                    BorderRadius.vertical(
                                                  top: Radius.circular(20),
                                                ),
                                              ),
                                              builder: (_) =>
                                                  TransactionFeeSelectionSheet(
                                                walletId: walletId,
                                                amount: (Decimal.tryParse(
                                                          cryptoAmountController
                                                              .text,
                                                        ) ??
                                                        ref
                                                            .watch(pSendAmount)
                                                            ?.decimal ??
                                                        Decimal.zero)
                                                    .toAmount(
                                                  fractionDigits:
                                                      coin.fractionDigits,
                                                ),
                                                updateChosen: (String fee) {
                                                  if (fee == "custom") {
                                                    if (!isCustomFee) {
                                                      setState(() {
                                                        isCustomFee = true;
                                                      });
                                                    }
                                                    return;
                                                  }

                                                  _setCurrentFee(
                                                    fee,
                                                    true,
                                                  );
                                                  setState(() {
                                                    _calculateFeesFuture =
                                                        Future(() => fee);
                                                    if (isCustomFee) {
                                                      isCustomFee = false;
                                                    }
                                                  });
                                                },
                                              ),
                                            );
                                          },
                                    child: (isFiro &&
                                            ref
                                                    .watch(
                                                      publicPrivateBalanceStateProvider
                                                          .state,
                                                    )
                                                    .state !=
                                                FiroType.public)
                                        ? Row(
                                            children: [
                                              FutureBuilder(
                                                future: _calculateFeesFuture,
                                                builder: (context, snapshot) {
                                                  if (snapshot.connectionState ==
                                                          ConnectionState
                                                              .done &&
                                                      snapshot.hasData) {
                                                    _setCurrentFee(
                                                      snapshot.data!,
                                                      false,
                                                    );
                                                    return Text(
                                                      "~${snapshot.data!}",
                                                      style: STextStyles
                                                          .itemSubtitle(
                                                        context,
                                                      ),
                                                    );
                                                  } else {
                                                    return AnimatedText(
                                                      stringsToLoopThrough: const [
                                                        "Calculating",
                                                        "Calculating.",
                                                        "Calculating..",
                                                        "Calculating...",
                                                      ],
                                                      style: STextStyles
                                                          .itemSubtitle(
                                                        context,
                                                      ),
                                                    );
                                                  }
                                                },
                                              ),
                                            ],
                                          )
                                        : Row(
                                            mainAxisAlignment:
                                                MainAxisAlignment.spaceBetween,
                                            children: [
                                              Row(
                                                children: [
                                                  Text(
                                                    ref
                                                        .watch(
                                                          feeRateTypeStateProvider
                                                              .state,
                                                        )
                                                        .state
                                                        .prettyName,
                                                    style: STextStyles
                                                        .itemSubtitle12(
                                                      context,
                                                    ),
                                                  ),
                                                  const SizedBox(
                                                    width: 10,
                                                  ),
                                                  FutureBuilder(
                                                    future:
                                                        _calculateFeesFuture,
                                                    builder:
                                                        (context, snapshot) {
                                                      if (snapshot.connectionState ==
                                                              ConnectionState
                                                                  .done &&
                                                          snapshot.hasData) {
                                                        _setCurrentFee(
                                                          snapshot.data!,
                                                          false,
                                                        );
                                                        return Text(
                                                          isCustomFee
                                                              ? ""
                                                              : "~${snapshot.data!}",
                                                          style: STextStyles
                                                              .itemSubtitle(
                                                            context,
                                                          ),
                                                        );
                                                      } else {
                                                        return AnimatedText(
                                                          stringsToLoopThrough: const [
                                                            "Calculating",
                                                            "Calculating.",
                                                            "Calculating..",
                                                            "Calculating...",
                                                          ],
                                                          style: STextStyles
                                                              .itemSubtitle(
                                                            context,
                                                          ),
                                                        );
                                                      }
                                                    },
                                                  ),
                                                ],
                                              ),
                                              SvgPicture.asset(
                                                Assets.svg.chevronDown,
                                                width: 8,
                                                height: 4,
                                                color: Theme.of(context)
                                                    .extension<StackColors>()!
                                                    .textSubtitle2,
                                              ),
                                            ],
                                          ),
                                  ),
                                ),
                              ],
                            ),
                          if (isCustomFee)
                            Padding(
                              padding: const EdgeInsets.only(
                                bottom: 12,
                                top: 16,
                              ),
                              child: FeeSlider(
                                coin: coin,
                                onSatVByteChanged: (rate) {
                                  customFeeRate = rate;
                                },
                              ),
                            ),
                          const Spacer(),
                          const SizedBox(
                            height: 12,
                          ),
                          TextButton(
                            onPressed: ref.watch(pPreviewTxButtonEnabled(coin))
                                ? _previewTransaction
                                : null,
                            style: ref.watch(pPreviewTxButtonEnabled(coin))
                                ? Theme.of(context)
                                    .extension<StackColors>()!
                                    .getPrimaryEnabledButtonStyle(context)
                                : Theme.of(context)
                                    .extension<StackColors>()!
                                    .getPrimaryDisabledButtonStyle(context),
                            child: Text(
                              "Preview",
                              style: STextStyles.button(context),
                            ),
                          ),
                          const SizedBox(
                            height: 4,
                          ),
                        ],
                      ),
                    ),
                  ),
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

String formatAddress(String epicAddress) {
  // strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an epicbox address)
  if ((epicAddress.startsWith("http://") ||
          epicAddress.startsWith("https://")) &&
      epicAddress.contains("@")) {
    epicAddress = epicAddress.replaceAll("http://", "");
    epicAddress = epicAddress.replaceAll("https://", "");
  }
  // strip mailto: prefix
  if (epicAddress.startsWith("mailto:")) {
    epicAddress = epicAddress.replaceAll("mailto:", "");
  }
  // strip / suffix if the address contains an @ symbol (and is thus an epicbox address)
  if (epicAddress.endsWith("/") && epicAddress.contains("@")) {
    epicAddress = epicAddress.substring(0, epicAddress.length - 1);
  }
  return epicAddress;
}