text: import ObservableStore from 'obs-store'
  import PendingBalanceCalculator from '../lib/pending-balance-calculator'
  import { BN } from 'cfx-util'
  
  class BalanceController {
    /**
     * Controller responsible for storing and updating an account's balance.
     *
     * @typedef {Object} BalanceController
     * @param {Object} opts - Initialize various properties of the class.
     * @property {string} address A base 16 hex string. The account address which has the balance managed by this
     * BalanceController.
     * @property {AccountTracker} accountTracker Stores and updates the users accounts
     * for which this BalanceController manages balance.
     * @property {TransactionController} txController Stores, tracks and manages transactions. Here used to create a listener for
     * transaction updates.
     * @property {BlockTracker} blockTracker Tracks updates to blocks. On new blocks, this BalanceController updates its balance
     * @property {Object} store The store for the ethBalance
     * @property {string} store.ethBalance A base 16 hex string. The balance for the current account.
     * @property {PendingBalanceCalculator} balanceCalc Used to calculate the accounts balance with possible pending
     * transaction costs taken into account.
     *
     */
    constructor (opts = {}) {
      this._validateParams(opts)
      const { address, accountTracker, txController, blockTracker } = opts
  
      this.address = address
      this.accountTracker = accountTracker
      this.txController = txController
      this.blockTracker = blockTracker
  
      const initState = {
        ethBalance: undefined,
      }
      this.store = new ObservableStore(initState)
  
      this.balanceCalc = new PendingBalanceCalculator({
        getBalance: () => this._getBalance(),
        getPendingTransactions: this._getPendingTransactions.bind(this),
      })
  
      this._registerUpdates()
    }
  
    /**
     * Updates the ethBalance property to the current pending balance
     *
     * @returns {Promise<void>} - Promises undefined
     */
    async updateBalance () {
      const balance = await this.balanceCalc.getBalance()
      this.store.updateState({
        ethBalance: balance,
      })
    }
  
    /**
     * Sets up listeners and subscriptions which should trigger an update of ethBalance. These updates include:
     * - when a transaction changes state to 'submitted', 'confirmed' or 'failed'
     * - when the current account changes (i.e. a new account is selected)
     * - when there is a block update
     *
     * @private
     *
     */
    _registerUpdates () {
      const update = this.updateBalance.bind(this)
  
      this.txController.on('tx:status-update', (_, status) => {
        switch (status) {
          case 'submitted':
          case 'confirmed':
          case 'failed':
            update()
            return
          default:
            return
        }
      })
      this.accountTracker.store.subscribe(update)
      this.blockTracker.on('latest', update)
    }
  
    /**
     * Gets the balance, as a base 16 hex string, of the account at this BalanceController's current address.
     * If the current account has no balance, returns undefined.
     *
     * @returns {Promise<BN|void>} - Promises a BN with a value equal to the balance of the current account, or undefined
     * if the current account has no balance
     *
     */
    async _getBalance () {
      const { accounts } = this.accountTracker.store.getState()
      const entry = accounts[this.address]
      const balance = entry.balance
      return balance ? new BN(balance.substring(2), 16) : undefined
    }
  
    /**
     * Gets the pending transactions (i.e. those with a 'submitted' status). These are accessed from the
     * TransactionController passed to this BalanceController during construction.
     *
     * @private
     * @returns {Promise<array>} - Promises an array of transaction objects.
     *
     */
    async _getPendingTransactions () {
      const pending = this.txController.getFilteredTxList({
        from: this.address,
        status: 'submitted',
        err: undefined,
      })
      return pending
    }
  
    /**
     * Validates that the passed options have all required properties.
     *
     * @param {Object} opts - The options object to validate
     * @throws {string} Throw a custom error indicating that address, accountTracker, txController and blockTracker are
     * missing and at least one is required
     *
     */
    _validateParams (opts) {
      const { address, accountTracker, txController, blockTracker } = opts
      if (!address || !accountTracker || !txController || !blockTracker) {
        const error =
          'Cannot construct a balance checker without address, accountTracker, txController, and blockTracker.'
        throw new Error(error)
      }
    }
  }
  
  export default BalanceController
  