const watchOnly = async () => {
  Vue.component(VueQrcode.name, VueQrcode)

  await walletConfig('static/components/wallet-config/wallet-config.html')
  await walletList('static/components/wallet-list/wallet-list.html')
  await addressList('static/components/address-list/address-list.html')
  await history('static/components/history/history.html')
  await utxoList('static/components/utxo-list/utxo-list.html')
  await feeRate('static/components/fee-rate/fee-rate.html')
  await seedInput('static/components/seed-input/seed-input.html')
  await sendTo('static/components/send-to/send-to.html')
  await payment('static/components/payment/payment.html')
  await serialSigner('static/components/serial-signer/serial-signer.html')
  await serialPortConfig(
    'static/components/serial-port-config/serial-port-config.html'
  )

  Vue.filter('reverse', function (value) {
    // slice to make a copy of array, then reverse the copy
    return value.slice().reverse()
  })

  new Vue({
    el: '#vue',
    mixins: [windowMixin],
    data: function () {
      return {
        scan: {
          scanning: false,
          scanCount: 0,
          scanIndex: 0
        },

        currentAddress: null,

        tab: 'addresses',

        config: {sats_denominated: true},

        qrCodeDialog: {
          show: false,
          data: null
        },
        ...tables,
        ...tableData,

        walletAccounts: [],
        addresses: [],
        history: [],
        historyFilter: '',

        showAddress: false,
        addressNote: '',
        showPayment: false,
        fetchedUtxos: false,
        utxosFilter: '',
        network: null
      }
    },
    computed: {
      mempoolHostname: function () {
        if (!this.config.isLoaded) return
        let hostname = new URL(this.config.mempool_endpoint).hostname
        if (this.config.network === 'Testnet') {
          hostname += '/testnet'
        }
        return hostname
      }
    },

    methods: {
      updateAmountForAddress: async function (addressData, amount = 0) {
        try {
          const wallet = this.g.user.wallets[0]
          addressData.amount = amount
          if (!addressData.isChange) {
            const addressWallet = this.walletAccounts.find(
              w => w.id === addressData.wallet
            )
            if (
              addressWallet &&
              addressWallet.address_no < addressData.addressIndex
            ) {
              addressWallet.address_no = addressData.addressIndex
            }
          }

          // todo: account deleted
          await LNbits.api.request(
            'PUT',
            `/watchonly/api/v1/address/${addressData.id}`,
            wallet.adminkey,
            {amount}
          )
        } catch (err) {
          addressData.error = 'Failed to refresh amount for address'
          this.$q.notify({
            type: 'warning',
            message: `Failed to refresh amount for address ${addressData.address}`,
            timeout: 10000
          })
          LNbits.utils.notifyApiError(err)
        }
      },
      updateNoteForAddress: async function ({addressId, note}) {
        try {
          const wallet = this.g.user.wallets[0]
          await LNbits.api.request(
            'PUT',
            `/watchonly/api/v1/address/${addressId}`,
            wallet.adminkey,
            {note}
          )
          const updatedAddress =
            this.addresses.find(a => a.id === addressId) || {}
          updatedAddress.note = note
        } catch (err) {
          LNbits.utils.notifyApiError(err)
        }
      },

      //################### ADDRESS HISTORY ###################
      addressHistoryFromTxs: function (addressData, txs) {
        const addressHistory = []
        txs.forEach(tx => {
          const sent = tx.vin
            .filter(
              vin => vin.prevout.scriptpubkey_address === addressData.address
            )
            .map(vin => mapInputToSentHistory(tx, addressData, vin))

          const received = tx.vout
            .filter(vout => vout.scriptpubkey_address === addressData.address)
            .map(vout => mapOutputToReceiveHistory(tx, addressData, vout))
          addressHistory.push(...sent, ...received)
        })
        return addressHistory
      },

      markSameTxAddressHistory: function () {
        this.history
          .filter(s => s.sent)
          .forEach((el, i, arr) => {
            if (el.isSubItem) return

            const sameTxItems = arr.slice(i + 1).filter(e => e.txId === el.txId)
            if (!sameTxItems.length) return
            sameTxItems.forEach(e => {
              e.isSubItem = true
            })

            el.totalAmount =
              el.amount + sameTxItems.reduce((t, e) => (t += e.amount || 0), 0)
            el.sameTxItems = sameTxItems
          })
      },

      //################### PAYMENT ###################

      initPaymentData: async function () {
        if (!this.payment.show) return
        await this.refreshAddresses()
      },

      goToPaymentView: async function () {
        this.showPayment = true
        await this.initPaymentData()
      },

      //################### PSBT ###################

      updateSignedPsbt: async function (psbtBase64) {
        this.$refs.paymentRef.updateSignedPsbt(psbtBase64)
      },

      //################### UTXOs ###################
      scanAllAddresses: async function () {
        await this.refreshAddresses()
        this.history = []
        let addresses = this.addresses
        this.utxos.data = []
        this.utxos.total = 0
        // Loop while new funds are found on the gap adresses.
        // Use 1000 limit as a safety check (scan 20 000 addresses max)
        for (let i = 0; i < 1000 && addresses.length; i++) {
          await this.updateUtxosForAddresses(addresses)
          const oldAddresses = this.addresses.slice()
          await this.refreshAddresses()
          const newAddresses = this.addresses.slice()
          // check if gap addresses have been extended
          addresses = newAddresses.filter(
            newAddr => !oldAddresses.find(oldAddr => oldAddr.id === newAddr.id)
          )
          if (addresses.length) {
            this.$q.notify({
              type: 'positive',
              message: 'Funds found! Scanning for more...',
              timeout: 10000
            })
          }
        }
      },
      scanAddressWithAmount: async function () {
        this.utxos.data = []
        this.utxos.total = 0
        this.history = []
        const addresses = this.addresses.filter(a => a.hasActivity)
        await this.updateUtxosForAddresses(addresses)
      },
      scanAddress: async function (addressData) {
        this.updateUtxosForAddresses([addressData])
        this.$q.notify({
          type: 'positive',
          message: 'Address Rescanned',
          timeout: 10000
        })
      },
      refreshAddresses: async function () {
        if (!this.walletAccounts) return
        this.addresses = []
        for (const {id, type} of this.walletAccounts) {
          const newAddresses = await this.getAddressesForWallet(id)
          const uniqueAddresses = newAddresses.filter(
            newAddr => !this.addresses.find(a => a.address === newAddr.address)
          )

          const lastActiveAddress =
            uniqueAddresses.filter(a => !a.isChange && a.hasActivity).pop() ||
            {}

          uniqueAddresses.forEach(a => {
            a.expanded = false
            a.accountType = type
            a.gapLimitExceeded =
              !a.isChange &&
              a.addressIndex >
                lastActiveAddress.addressIndex + DEFAULT_RECEIVE_GAP_LIMIT
          })
          this.addresses.push(...uniqueAddresses)
        }
        this.$emit('update:addresses', this.addresses)
      },
      getAddressesForWallet: async function (walletId) {
        try {
          const {data} = await LNbits.api.request(
            'GET',
            '/watchonly/api/v1/addresses/' + walletId,
            this.g.user.wallets[0].inkey
          )
          return data.map(mapAddressesData)
        } catch (error) {
          this.$q.notify({
            type: 'warning',
            message: `Failed to fetch addresses for wallet with id ${walletId}.`,
            timeout: 10000
          })
          LNbits.utils.notifyApiError(error)
        }
        return []
      },
      updateUtxosForAddresses: async function (addresses = []) {
        this.scan = {scanning: true, scanCount: addresses.length, scanIndex: 0}

        try {
          for (addrData of addresses) {
            const addressHistory = await this.getAddressTxsDelayed(addrData)
            // remove old entries
            this.history = this.history.filter(
              h => h.address !== addrData.address
            )

            // add new entries
            this.history.push(...addressHistory)
            this.history.sort((a, b) => (!a.height ? -1 : b.height - a.height))
            this.markSameTxAddressHistory()

            if (addressHistory.length) {
              // search only if it ever had any activity
              const utxos = await this.getAddressTxsUtxoDelayed(
                addrData.address
              )
              this.updateUtxosForAddress(addrData, utxos)
            }

            this.scan.scanIndex++
          }
        } catch (error) {
          console.error(error)
          this.$q.notify({
            type: 'warning',
            message: 'Failed to scan addresses',
            timeout: 10000
          })
        } finally {
          this.scan.scanning = false
        }
      },
      updateUtxosForAddress: function (addressData, utxos = []) {
        const wallet =
          this.walletAccounts.find(w => w.id === addressData.wallet) || {}

        const newUtxos = utxos.map(utxo =>
          mapAddressDataToUtxo(wallet, addressData, utxo)
        )
        // remove old utxos
        this.utxos.data = this.utxos.data.filter(
          u => u.address !== addressData.address
        )
        // add new utxos
        this.utxos.data.push(...newUtxos)
        if (utxos.length) {
          this.utxos.data.sort((a, b) => b.sort - a.sort)
          this.utxos.total = this.utxos.data.reduce(
            (total, y) => (total += y?.amount || 0),
            0
          )
        }
        const addressTotal = utxos.reduce(
          (total, y) => (total += y?.value || 0),
          0
        )
        this.updateAmountForAddress(addressData, addressTotal)
      },

      //################### MEMPOOL API ###################
      getAddressTxsDelayed: async function (addrData) {
        const accounts = this.walletAccounts
        const {
          bitcoin: {addresses: addressesAPI}
        } = mempoolJS({
          hostname: this.mempoolHostname
        })
        const fn = async () => {
          if (!accounts.find(w => w.id === addrData.wallet)) return []
          return addressesAPI.getAddressTxs({
            address: addrData.address
          })
        }
        const addressTxs = await retryWithDelay(fn)
        return this.addressHistoryFromTxs(addrData, addressTxs)
      },

      getAddressTxsUtxoDelayed: async function (address) {
        const endpoint = this.mempoolHostname
        const {
          bitcoin: {addresses: addressesAPI}
        } = mempoolJS({
          hostname: endpoint
        })

        const fn = async () => {
          if (endpoint !== this.mempoolHostname) return []
          return addressesAPI.getAddressTxsUtxo({
            address
          })
        }
        return retryWithDelay(fn)
      },

      //################### OTHER ###################

      openQrCodeDialog: function (addressData) {
        this.currentAddress = addressData
        this.addressNote = addressData.note || ''
        this.showAddress = true
      },
      searchInTab: function ({tab, value}) {
        this.tab = tab
        this[`${tab}Filter`] = value
      },

      updateAccounts: async function (accounts) {
        this.walletAccounts = accounts
        await this.refreshAddresses()
        await this.scanAddressWithAmount()
      },
      showAddressDetails: function (addressData) {
        this.openQrCodeDialog(addressData)
      },
      showAddressDetailsWithConfirmation: function ({addressData, wallet}) {
        this.showAddressDetails(addressData)
        if (this.$refs.serialSigner.isConnected()) {
          if (this.$refs.serialSigner.isAuthenticated()) {
            if (wallet.meta?.accountPath) {
              const branchIndex = addressData.isChange ? 1 : 0
              const path =
                wallet.meta.accountPath +
                `/${branchIndex}/${addressData.addressIndex}`
              this.$refs.serialSigner.hwwShowAddress(path, addressData.address)
            }
          } else {
            this.$q.notify({
              type: 'warning',
              message: 'Please login in order to confirm address on device',
              timeout: 10000
            })
          }
        }
      },
      initUtxos: function (addresses) {
        if (!this.fetchedUtxos && addresses.length) {
          this.fetchedUtxos = true
          this.addresses = addresses
          this.scanAddressWithAmount()
        }
      },
      handleBroadcastSuccess: async function (txId) {
        this.tab = 'history'
        this.searchInTab({tab: 'history', value: txId})
        this.showPayment = false
        await this.refreshAddresses()
        await this.scanAddressWithAmount()
      }
    },
    created: async function () {
      if (this.g.user.wallets.length) {
        await this.refreshAddresses()
        await this.scanAddressWithAmount()
      }
    }
  })
}
watchOnly()
