package com.bitcointxoko.gudariwallet.ui import timber.log.Timber import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.bitcointxoko.gudariwallet.api.LnurlScanResponse import com.bitcointxoko.gudariwallet.data.NodeAliasRepository import com.bitcointxoko.gudariwallet.data.PaymentCacheRepository import com.bitcointxoko.gudariwallet.data.WalletRepository import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import com.bitcointxoko.gudariwallet.data.BalancePrefsStore import com.bitcointxoko.gudariwallet.data.LightningAddressStore import com.bitcointxoko.gudariwallet.data.NwcCacheRepository import com.bitcointxoko.gudariwallet.ui.balance.BalanceViewModel import com.bitcointxoko.gudariwallet.ui.clipboard.ClipboardViewModel import com.bitcointxoko.gudariwallet.ui.fiat.FiatViewModel import com.bitcointxoko.gudariwallet.ui.receive.ReceiveViewModel import com.bitcointxoko.gudariwallet.ui.send.SendEvent import com.bitcointxoko.gudariwallet.ui.send.SendStrings import com.bitcointxoko.gudariwallet.ui.send.SendViewModel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.stateIn private const val TAG = "WalletViewModel" class WalletViewModel( val repo : WalletRepository, val aliasRepo : NodeAliasRepository, val paymentCache: PaymentCacheRepository, val nwcCache : NwcCacheRepository, private val balancePrefs: BalancePrefsStore, private val lnAddressStore : LightningAddressStore, val balanceVm : BalanceViewModel, val fiatVm : FiatViewModel, val receiveVm : ReceiveViewModel, val sendVm : SendViewModel, val clipboardVm : ClipboardViewModel ) : ViewModel() { val balanceState: StateFlow get() = balanceVm.balanceState val balanceHidden: StateFlow get() = fiatVm.balanceHidden val selectedCurrency: StateFlow get() = fiatVm.selectedCurrency val fiatSatsPerUnit: StateFlow get() = fiatVm.fiatSatsPerUnit fun toggleBalanceVisibility() = fiatVm.toggleBalanceVisibility() fun setSelectedCurrency(c: String?) = fiatVm.setSelectedCurrency(c) suspend fun getSupportedCurrencies() = fiatVm.getSupportedCurrencies() val fiatRate: Double? get() = fiatVm.fiatRate val fiatCurrency: String? get() = fiatVm.fiatCurrency val receiveState: StateFlow get() = receiveVm.receiveState val navigateToReceive: SharedFlow get() = receiveVm.navigateToReceive fun createInvoice( amountSats : Long, memo : String, failedToCreateInvoice : String, ) = receiveVm.createInvoice( amountSats = amountSats, memo = memo, failedToCreateInvoice = failedToCreateInvoice, ) fun resetReceiveState() = receiveVm.resetReceiveState() fun executeWithdraw( k1 : String, callback : String, amountSats : Long, memo : String, failedToCreateInvoice : String, withdrawRejected : String, withdrawFailed : String, ) = receiveVm.executeWithdraw( k1 = k1, callback = callback, amountSats = amountSats, memo = memo, failedToCreateInvoice = failedToCreateInvoice, withdrawRejected = withdrawRejected, withdrawFailed = withdrawFailed, ) val sendState: StateFlow get() = sendVm.sendState fun refreshBalance() = balanceVm.refreshBalance() fun scan(rawInput: String, strings: SendStrings) = sendVm.scan(rawInput, strings) fun payBolt11(bolt11: String, strings: SendStrings) = sendVm.payBolt11(bolt11, strings) fun payLnurl( rawRes : LnurlScanResponse, lnurl : String, amountMsat: Long, comment : String?, strings : SendStrings, ) = sendVm.payLnurl(rawRes, lnurl, amountMsat, comment, strings) fun fetchLnurlInvoice( rawRes : LnurlScanResponse, lnurl : String, domain : String, amountMsat: Long, comment : String?, strings : SendStrings, ) = sendVm.fetchLnurlInvoice(rawRes, lnurl, domain, amountMsat, comment, strings) fun payLnurlInvoice(state: SendState.LnurlInvoiceReady, strings: SendStrings) = sendVm.payLnurlInvoice(state, strings) fun onAuthFailed(message: String, strings: SendStrings) { sendVm.onAuthFailed(message, strings) } fun resetSendState() = sendVm.resetSendState() val sendEvents: SharedFlow = sendVm.events fun handleWithdrawScanned(raw: LnurlScanResponse, lnurl: String) { receiveVm.handleWithdrawResponse(raw, lnurl) } val lightningAddress: StateFlow = lnAddressStore.lightningAddressFlow .stateIn( scope = viewModelScope, started = SharingStarted.Eagerly, initialValue = null ) val clipboardOfferState: StateFlow get() = clipboardVm.clipboardOfferState fun checkClipboard(text: String?) = clipboardVm.checkClipboard(text) fun dismissClipboardOffer() = clipboardVm.dismissClipboardOffer() init { observePaymentEvents() fetchLightningAddress() viewModelScope.launch { aliasRepo.warmUp() } } // ── WebSocket event collection ──────────────────────────────────────────── private fun observePaymentEvents() { viewModelScope.launch { balanceVm.incomingPayment.collect { event -> receiveVm.onIncomingPayment(event) } } } private fun fetchLightningAddress() { // lightningAddressFlow already emits the cached DataStore value immediately // via SharingStarted.Eagerly — this coroutine only does the network refresh. viewModelScope.launch { if (!lnAddressStore.isCacheStale()) { Timber.d("LNADDR [TTL ] cache is fresh — skipping network fetch") return@launch } runCatching { repo.getLightningAddress() } .onSuccess { address -> if (address != null) { Timber.d("LNADDR [FETCH OK ] fetched from API: \"$address\" — updating cache") lnAddressStore.setLightningAddress(address) // StateFlow updates automatically via the DataStore flow } else { Timber.d("LNADDR [FETCH OK ] fetched from API: no pay link / username configured") } } .onFailure { e -> Timber.w("LNADDR [FETCH ERR] API call failed (${e.message}) — falling back to cached value") // Nothing to do — DataStore flow already holds the last good value } } } }