/* 
 * 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:bip47/bip47.dart';
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:stackwallet/models/isar/models/isar_models.dart';
import 'package:stackwallet/models/paynym/paynym_account_lite.dart';
import 'package:stackwallet/models/send_view_auto_fill_data.dart';
import 'package:stackwallet/pages/address_book_views/address_book_view.dart';
import 'package:stackwallet/pages/coin_control/coin_control_view.dart';
import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart';
import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart';
import 'package:stackwallet/providers/providers.dart';
import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart';
import 'package:stackwallet/providers/ui/preview_tx_button_state_provider.dart';
import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart';
import 'package:stackwallet/route_generator.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/services/coins/manager.dart';
import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart';
import 'package:stackwallet/themes/coin_icon_provider.dart';
import 'package:stackwallet/themes/stack_colors.dart';
import 'package:stackwallet/utilities/address_utils.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/amount/amount_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_input_formatter.dart';
import 'package:stackwallet/utilities/amount/amount_unit.dart';
import 'package:stackwallet/utilities/assets.dart';
import 'package:stackwallet/utilities/barcode_scanner_interface.dart';
import 'package:stackwallet/utilities/clipboard_interface.dart';
import 'package:stackwallet/utilities/constants.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
import 'package:stackwallet/utilities/prefs.dart';
import 'package:stackwallet/utilities/text_styles.dart';
import 'package:stackwallet/utilities/util.dart';
import 'package:stackwallet/widgets/animated_text.dart';
import 'package:stackwallet/widgets/background.dart';
import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart';
import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart';
import 'package:stackwallet/widgets/fee_slider.dart';
import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart';
import 'package:stackwallet/widgets/icon_widgets/x_icon.dart';
import 'package:stackwallet/widgets/rounded_white_container.dart';
import 'package:stackwallet/widgets/stack_dialog.dart';
import 'package:stackwallet/widgets/stack_text_field.dart';
import 'package:stackwallet/widgets/textfield_icon_button.dart';
import 'package:tuple/tuple.dart';

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

  static const String routeName = "/sendView";

  final String walletId;
  final Coin 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 Coin 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 final SendViewAutoFillData? _data;

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

  Amount? _amountToSend;
  Amount? _cachedAmountToSend;
  String? _address;

  String? _privateBalanceString;
  String? _publicBalanceString;

  bool _addressToggleFlag = false;

  bool _cryptoAmountChangeLock = false;
  late VoidCallback onCryptoAmountChanged;

  Amount? _cachedBalance;

  Set<UTXO> selectedUTXOs = {};

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

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

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

      _updatePreviewButtonState(_address, _amountToSend);

      _cryptoAmountChangedFeeUpdateTimer?.cancel();
      _cryptoAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () {
        if (coin != Coin.epicCash && !_baseFocus.hasFocus) {
          setState(() {
            _calculateFeesFuture = calculateFees(
              _amountToSend == null
                  ? 0.toAmountAsRaw(fractionDigits: coin.decimals)
                  : _amountToSend!,
            );
          });
        }
      });
    }
  }

  final updateFeesTimerDuration = const Duration(milliseconds: 500);

  Timer? _cryptoAmountChangedFeeUpdateTimer;
  Timer? _baseAmountChangedFeeUpdateTimer;

  void _baseAmountChanged() {
    _baseAmountChangedFeeUpdateTimer?.cancel();
    _baseAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () {
      if (coin != Coin.epicCash && !_cryptoFocus.hasFocus) {
        setState(() {
          _calculateFeesFuture = calculateFees(
            _amountToSend == null
                ? 0.toAmountAsRaw(fractionDigits: coin.decimals)
                : _amountToSend!,
          );
        });
      }
    });
  }

  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.decimals)
        : Decimal.parse(fee).toAmount(fractionDigits: coin.decimals);

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

  String? _updateInvalidAddressText(String address, Manager manager) {
    if (_data != null && _data!.contactLabel == address) {
      return null;
    }
    if (address.isNotEmpty && !manager.validateAddress(address)) {
      return "Invalid address";
    }
    return null;
  }

  void _updatePreviewButtonState(String? address, Amount? amount) {
    if (isPaynymSend) {
      ref.read(previewTxButtonStateProvider.state).state =
          (amount != null && amount > Amount.zero);
    } else {
      final isValidAddress = ref
          .read(walletsChangeNotifierProvider)
          .getManager(walletId)
          .validateAddress(address ?? "");
      ref.read(previewTxButtonStateProvider.state).state =
          (isValidAddress && amount != null && amount > Amount.zero);
    }
  }

  late Future<String> _calculateFeesFuture;

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

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

    if (coin == Coin.firo || coin == Coin.firoTestNet) {
      if (ref.read(publicPrivateBalanceStateProvider.state).state ==
          "Private") {
        if (cachedFiroPrivateFees[amount] != null) {
          return cachedFiroPrivateFees[amount]!;
        }
      } else {
        if (cachedFiroPublicFees[amount] != null) {
          return cachedFiroPublicFees[amount]!;
        }
      }
    } else if (cachedFees[amount] != null) {
      return cachedFees[amount]!;
    }

    final manager =
        ref.read(walletsChangeNotifierProvider).getManager(walletId);
    final feeObject = await manager.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 == Coin.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 manager.estimateFeeFor(amount, specialMoneroId.raw!);
      cachedFees[amount] = ref.read(pAmountFormatter(coin)).format(
            fee,
            withUnitName: true,
            indicatePrecisionLoss: false,
          );

      return cachedFees[amount]!;
    } else if (coin == Coin.firo || coin == Coin.firoTestNet) {
      if (ref.read(publicPrivateBalanceStateProvider.state).state ==
          "Private") {
        fee = await manager.estimateFeeFor(amount, feeRate);

        cachedFiroPrivateFees[amount] = ref.read(pAmountFormatter(coin)).format(
              fee,
              withUnitName: true,
              indicatePrecisionLoss: false,
            );

        return cachedFiroPrivateFees[amount]!;
      } else {
        fee = await (manager.wallet as FiroWallet)
            .estimateFeeForPublic(amount, feeRate);

        cachedFiroPublicFees[amount] = ref.read(pAmountFormatter(coin)).format(
              fee,
              withUnitName: true,
              indicatePrecisionLoss: false,
            );

        return cachedFiroPublicFees[amount]!;
      }
    } else {
      fee = await manager.estimateFeeFor(amount, feeRate);
      cachedFees[amount] = ref.read(pAmountFormatter(coin)).format(
            fee,
            withUnitName: true,
            indicatePrecisionLoss: false,
          );

      return cachedFees[amount]!;
    }
  }

  Future<String?> _firoBalanceFuture(
      ChangeNotifierProvider<Manager> provider, String locale) async {
    final wallet = ref.read(provider).wallet as FiroWallet?;

    if (wallet != null) {
      Amount? balance;
      if (ref.read(publicPrivateBalanceStateProvider.state).state ==
          "Private") {
        balance = wallet.availablePrivateBalance();
      } else {
        balance = wallet.availablePublicBalance();
      }

      return ref.read(pAmountFormatter(coin)).format(
            balance,
          );
    }

    return null;
  }

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

    final Amount amount = _amountToSend!;
    final Amount availableBalance;
    if ((coin == Coin.firo || coin == Coin.firoTestNet)) {
      if (ref.read(publicPrivateBalanceStateProvider.state).state ==
          "Private") {
        availableBalance =
            (manager.wallet as FiroWallet).availablePrivateBalance();
      } else {
        availableBalance =
            (manager.wallet as FiroWallet).availablePublicBalance();
      }
    } else {
      availableBalance = manager.balance.spendable;
    }

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

    if (coin != Coin.ethereum &&
            !(manager.hasCoinControlSupport && coinControlEnabled) ||
        (manager.hasCoinControlSupport &&
            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: manager.coin,
                onCancel: () {
                  wasCancelled = true;

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

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

      Map<String, dynamic> txData;
      Future<Map<String, dynamic>> txDataFuture;

      if (isPaynymSend) {
        final wallet = manager.wallet as PaynymWalletInterface;
        final paymentCode = PaymentCode.fromPaymentCode(
          widget.accountLite!.code,
          networkType: wallet.networkType,
        );
        final feeRate = ref.read(feeRateTypeStateProvider);
        txDataFuture = wallet.preparePaymentCodeSend(
          paymentCode: paymentCode,
          isSegwit: widget.accountLite!.segwit,
          amount: amount,
          args: {
            "satsPerVByte": isCustomFee ? customFeeRate : null,
            "feeRate": feeRate,
            "UTXOs": (manager.hasCoinControlSupport &&
                    coinControlEnabled &&
                    selectedUTXOs.isNotEmpty)
                ? selectedUTXOs
                : null,
          },
        );
      } else if ((coin == Coin.firo || coin == Coin.firoTestNet) &&
          ref.read(publicPrivateBalanceStateProvider.state).state !=
              "Private") {
        txDataFuture = (manager.wallet as FiroWallet).prepareSendPublic(
          address: _address!,
          amount: amount,
          args: {
            "feeRate": ref.read(feeRateTypeStateProvider),
            "satsPerVByte": isCustomFee ? customFeeRate : null,
          },
        );
      } else {
        txDataFuture = manager.prepareSend(
          address: _address!,
          amount: amount,
          args: {
            "feeRate": ref.read(feeRateTypeStateProvider),
            "satsPerVByte": isCustomFee ? customFeeRate : null,
            "UTXOs": (manager.hasCoinControlSupport &&
                    coinControlEnabled &&
                    selectedUTXOs.isNotEmpty)
                ? selectedUTXOs
                : null,
          },
        );
      }

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

      txData = results.first as Map<String, dynamic>;

      if (!wasCancelled && mounted) {
        // pop building dialog
        Navigator.of(context).pop();
        txData["note"] = noteController.text;
        txData["onChainNote"] = onChainNoteController.text;
        if (isPaynymSend) {
          txData["paynymAccountLite"] = widget.accountLite!;
        } else {
          txData["address"] = _address;
        }

        unawaited(Navigator.of(context).push(
          RouteGenerator.getRoute(
            shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute,
            builder: (_) => ConfirmTransactionView(
              transactionInfo: txData,
              walletId: walletId,
              isPaynymTransaction: isPaynymSend,
            ),
            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();
                },
              ),
            );
          },
        ));
      }
    }
  }

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

  bool isCustomFee = false;

  int customFeeRate = 1;

  @override
  void initState() {
    coin = widget.coin;
    ref.refresh(feeSheetSessionCacheProvider);
    _currentFee = 0.toAmountAsRaw(fractionDigits: coin.decimals);

    _calculateFeesFuture =
        calculateFees(0.toAmountAsRaw(fractionDigits: coin.decimals));
    _data = widget.autoFillData;
    walletId = widget.walletId;
    clipboard = widget.clipboard;
    scanner = widget.barcodeScanner;

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

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

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

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

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

    // if (coin != Coin.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();

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

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

    final showCoinControl = ref.watch(
          walletsChangeNotifierProvider.select(
            (value) => value.getManager(walletId).hasCoinControlSupport,
          ),
        ) &&
        ref.watch(
          prefsChangeNotifierProvider.select(
            (value) => value.enableCoinControl,
          ),
        );

    if (coin == Coin.firo || coin == Coin.firoTestNet) {
      ref.listen(publicPrivateBalanceStateProvider, (previous, next) {
        if (_amountToSend == null) {
          setState(() {
            _calculateFeesFuture =
                calculateFees(0.toAmountAsRaw(fractionDigits: coin.decimals));
          });
        } else {
          setState(() {
            _calculateFeesFuture = calculateFees(
              _amountToSend!,
            );
          });
        }
      });
    }

    // add listener for epic cash to strip http:// and https:// prefixes if the address also ocntains an @ symbol (indicating an epicbox address)
    if (coin == Coin.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 (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(provider.select(
                                            (value) => value.walletName)),
                                        style: STextStyles.titleBold12(context)
                                            .copyWith(fontSize: 14),
                                        overflow: TextOverflow.ellipsis,
                                        maxLines: 1,
                                      ),
                                      // const SizedBox(
                                      //   height: 2,
                                      // ),
                                      if (coin == Coin.firo ||
                                          coin == Coin.firoTestNet)
                                        Text(
                                          "${ref.watch(publicPrivateBalanceStateProvider.state).state} balance",
                                          style: STextStyles.label(context)
                                              .copyWith(fontSize: 10),
                                        ),
                                      if (coin != Coin.firo &&
                                          coin != Coin.firoTestNet)
                                        Text(
                                          "Available balance",
                                          style: STextStyles.label(context)
                                              .copyWith(fontSize: 10),
                                        ),
                                    ],
                                  ),
                                  const Spacer(),
                                  FutureBuilder(
                                    // TODO redo this widget now that its not actually a future
                                    future: (coin != Coin.firo &&
                                            coin != Coin.firoTestNet)
                                        ? Future(() => ref.watch(
                                            provider.select((value) =>
                                                value.balance.spendable)))
                                        : ref.watch(publicPrivateBalanceStateProvider.state).state ==
                                                "Private"
                                            ? Future(() => (ref
                                                    .watch(provider)
                                                    .wallet as FiroWallet)
                                                .availablePrivateBalance())
                                            : Future(() => (ref
                                                    .watch(provider)
                                                    .wallet as FiroWallet)
                                                .availablePublicBalance()),
                                    builder:
                                        (_, AsyncSnapshot<Amount> snapshot) {
                                      if (snapshot.connectionState ==
                                              ConnectionState.done &&
                                          snapshot.hasData) {
                                        _cachedBalance = snapshot.data!;
                                      }

                                      if (_cachedBalance != null) {
                                        return GestureDetector(
                                          onTap: () {
                                            cryptoAmountController.text = ref
                                                .read(pAmountFormatter(coin))
                                                .format(
                                                  _cachedBalance!,
                                                  withUnitName: false,
                                                );
                                          },
                                          child: Container(
                                            color: Colors.transparent,
                                            child: Column(
                                              crossAxisAlignment:
                                                  CrossAxisAlignment.end,
                                              children: [
                                                Text(
                                                  ref
                                                      .watch(pAmountFormatter(
                                                          coin))
                                                      .format(_cachedBalance!),
                                                  style:
                                                      STextStyles.titleBold12(
                                                              context)
                                                          .copyWith(
                                                    fontSize: 10,
                                                  ),
                                                  textAlign: TextAlign.right,
                                                ),
                                                Text(
                                                  "${(_cachedBalance!.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,
                                                )
                                              ],
                                            ),
                                          ),
                                        );
                                      } else {
                                        return Column(
                                          crossAxisAlignment:
                                              CrossAxisAlignment.end,
                                          children: [
                                            AnimatedText(
                                              stringsToLoopThrough: const [
                                                "Loading balance   ",
                                                "Loading balance.  ",
                                                "Loading balance.. ",
                                                "Loading balance...",
                                              ],
                                              style: STextStyles.itemSubtitle(
                                                      context)
                                                  .copyWith(
                                                fontSize: 10,
                                              ),
                                            ),
                                            const SizedBox(
                                              height: 2,
                                            ),
                                            AnimatedText(
                                              stringsToLoopThrough: const [
                                                "Loading balance   ",
                                                "Loading balance.  ",
                                                "Loading balance.. ",
                                                "Loading balance...",
                                              ],
                                              style: STextStyles.itemSubtitle(
                                                      context)
                                                  .copyWith(
                                                fontSize: 8,
                                              ),
                                            )
                                          ],
                                        );
                                      }
                                    },
                                  ),
                                ],
                              ),
                            ),
                          ),
                          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 == Coin.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();
                                  _updatePreviewButtonState(
                                      _address, _amountToSend);

                                  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 = "";
                                                    _updatePreviewButtonState(
                                                        _address,
                                                        _amountToSend);
                                                    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 ==
                                                          Coin.epicCash) {
                                                        // strip http:// and https:// if content contains @
                                                        content = formatAddress(
                                                            content);
                                                      }
                                                      sendToController.text =
                                                          content.trim();
                                                      _address = content.trim();

                                                      _updatePreviewButtonState(
                                                          _address,
                                                          _amountToSend);
                                                      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: () 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.decimals,
                                                      );
                                                      cryptoAmountController
                                                              .text =
                                                          ref
                                                              .read(
                                                                  pAmountFormatter(
                                                                      coin))
                                                              .format(
                                                                amount,
                                                                withUnitName:
                                                                    false,
                                                              );
                                                      _amountToSend = amount;
                                                    }

                                                    _updatePreviewButtonState(
                                                        _address,
                                                        _amountToSend);
                                                    setState(() {
                                                      _addressToggleFlag =
                                                          sendToController
                                                              .text.isNotEmpty;
                                                    });

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

                                                    _updatePreviewButtonState(
                                                        _address,
                                                        _amountToSend);
                                                    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);
                                                }
                                              },
                                              child: const QrCodeIcon(),
                                            )
                                        ],
                                      ),
                                    ),
                                  ),
                                ),
                              ),
                            ),
                          Builder(
                            builder: (_) {
                              final error = _updateInvalidAddressText(
                                _address ?? "",
                                ref
                                    .read(walletsChangeNotifierProvider)
                                    .getManager(walletId),
                              );

                              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 (coin == Coin.firo)
                            const SizedBox(
                              height: 12,
                            ),
                          if (coin == Coin.firo)
                            Text(
                              "Send from",
                              style: STextStyles.smallMed12(context),
                              textAlign: TextAlign.left,
                            ),
                          if (coin == Coin.firo)
                            const SizedBox(
                              height: 8,
                            ),
                          if (coin == Coin.firo)
                            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} balance",
                                              style: STextStyles.itemSubtitle12(
                                                  context),
                                            ),
                                            const SizedBox(
                                              width: 10,
                                            ),
                                            FutureBuilder(
                                              future: _firoBalanceFuture(
                                                  provider, locale),
                                              builder: (context,
                                                  AsyncSnapshot<String?>
                                                      snapshot) {
                                                if (snapshot.connectionState ==
                                                        ConnectionState.done &&
                                                    snapshot.hasData) {
                                                  if (ref
                                                          .read(
                                                              publicPrivateBalanceStateProvider
                                                                  .state)
                                                          .state ==
                                                      "Private") {
                                                    _privateBalanceString =
                                                        snapshot.data!;
                                                  } else {
                                                    _publicBalanceString =
                                                        snapshot.data!;
                                                  }
                                                }
                                                if (ref
                                                            .read(
                                                                publicPrivateBalanceStateProvider
                                                                    .state)
                                                            .state ==
                                                        "Private" &&
                                                    _privateBalanceString !=
                                                        null) {
                                                  return Text(
                                                    "$_privateBalanceString",
                                                    style: STextStyles
                                                        .itemSubtitle(context),
                                                  );
                                                } else if (ref
                                                            .read(
                                                                publicPrivateBalanceStateProvider
                                                                    .state)
                                                            .state ==
                                                        "Public" &&
                                                    _publicBalanceString !=
                                                        null) {
                                                  return Text(
                                                    "$_publicBalanceString",
                                                    style: STextStyles
                                                        .itemSubtitle(context),
                                                  );
                                                } else {
                                                  return AnimatedText(
                                                    stringsToLoopThrough: const [
                                                      "Loading balance",
                                                      "Loading balance.",
                                                      "Loading balance..",
                                                      "Loading balance...",
                                                    ],
                                                    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 != Coin.ethereum)
                                CustomTextButton(
                                  text: "Send all ${coin.ticker}",
                                  onTap: () async {
                                    if (coin == Coin.firo ||
                                        coin == Coin.firoTestNet) {
                                      final firoWallet = ref
                                          .read(provider)
                                          .wallet as FiroWallet;
                                      if (ref
                                              .read(
                                                  publicPrivateBalanceStateProvider
                                                      .state)
                                              .state ==
                                          "Private") {
                                        cryptoAmountController.text = ref
                                            .read(pAmountFormatter(coin))
                                            .format(
                                              firoWallet
                                                  .availablePrivateBalance(),
                                              withUnitName: false,
                                            );
                                      } else {
                                        cryptoAmountController.text = ref
                                            .read(pAmountFormatter(coin))
                                            .format(
                                              firoWallet
                                                  .availablePublicBalance(),
                                              withUnitName: false,
                                            );
                                      }
                                    } else {
                                      cryptoAmountController.text = ref
                                          .read(pAmountFormatter(coin))
                                          .format(
                                            ref
                                                .read(provider)
                                                .balance
                                                .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.decimals,
                                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.decimals)
                              //             .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: (baseAmountString) {
                                final baseAmount = Amount.tryParseFiatString(
                                  baseAmountString,
                                  locale: locale,
                                );
                                if (baseAmount != null) {
                                  final Decimal _price = ref
                                      .read(priceAnd24hChangeNotifierProvider)
                                      .getPrice(coin)
                                      .item1;

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

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

                                  _cryptoAmountChangeLock = true;
                                  cryptoAmountController.text = amountString;
                                  _cryptoAmountChangeLock = false;
                                } else {
                                  _amountToSend = 0.toAmountAsRaw(
                                      fractionDigits: coin.decimals);
                                  _cryptoAmountChangeLock = true;
                                  cryptoAmountController.text = "";
                                  _cryptoAmountChangeLock = false;
                                }
                                // setState(() {
                                //   _calculateFeesFuture = calculateFees(
                                //       Format.decimalAmountToSatoshis(
                                //           _amountToSend!));
                                // });
                                _updatePreviewButtonState(
                                    _address, _amountToSend);
                              },
                              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 (mounted) {
                                        final spendable = ref
                                            .read(walletsChangeNotifierProvider)
                                            .getManager(widget.walletId)
                                            .balance
                                            .spendable;

                                        Amount? amount;
                                        if (_amountToSend != null) {
                                          amount = _amountToSend!;

                                          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 == Coin.epicCash)
                            Text(
                              "On chain Note (optional)",
                              style: STextStyles.smallMed12(context),
                              textAlign: TextAlign.left,
                            ),
                          if (coin == Coin.epicCash)
                            const SizedBox(
                              height: 8,
                            ),
                          if (coin == Coin.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 == Coin.epicCash)
                            const SizedBox(
                              height: 12,
                            ),
                          Text(
                              (coin == Coin.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 != Coin.epicCash &&
                              coin != Coin.nano &&
                              coin != Coin.banano)
                            Text(
                              "Transaction fee (estimated)",
                              style: STextStyles.smallMed12(context),
                              textAlign: TextAlign.left,
                            ),
                          if (coin != Coin.epicCash &&
                              coin != Coin.nano &&
                              coin != Coin.banano)
                            const SizedBox(
                              height: 8,
                            ),
                          if (coin != Coin.epicCash &&
                              coin != Coin.nano &&
                              coin != Coin.banano)
                            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: (coin == Coin.firo ||
                                                coin == Coin.firoTestNet) &&
                                            ref
                                                    .watch(
                                                        publicPrivateBalanceStateProvider
                                                            .state)
                                                    .state ==
                                                "Private"
                                        ? 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) ??
                                                        _amountToSend
                                                            ?.decimal ??
                                                        Decimal.zero)
                                                    .toAmount(
                                                  fractionDigits: coin.decimals,
                                                ),
                                                updateChosen: (String fee) {
                                                  if (fee == "custom") {
                                                    if (!isCustomFee) {
                                                      setState(() {
                                                        isCustomFee = true;
                                                      });
                                                    }
                                                    return;
                                                  }

                                                  _setCurrentFee(
                                                    fee,
                                                    true,
                                                  );
                                                  setState(() {
                                                    _calculateFeesFuture =
                                                        Future(() => fee);
                                                    if (isCustomFee) {
                                                      isCustomFee = false;
                                                    }
                                                  });
                                                },
                                              ),
                                            );
                                          },
                                    child: ((coin == Coin.firo ||
                                                coin == Coin.firoTestNet) &&
                                            ref
                                                    .watch(
                                                        publicPrivateBalanceStateProvider
                                                            .state)
                                                    .state ==
                                                "Private")
                                        ? 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(previewTxButtonStateProvider.state)
                                    .state
                                ? _previewTransaction
                                : null,
                            style: ref
                                    .watch(previewTxButtonStateProvider.state)
                                    .state
                                ? 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;
}