 function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }import { lt } from 'biggystring'
import { buildReducer, filterReducer, memoizeReducer } from 'redux-keto'














import { compare } from '../../../util/compare'

import { findCurrencyPluginId } from '../../plugins/plugins-selectors'


import { uniqueStrings } from './enabled-tokens'

/** Maps from txid hash to file creation date & path. */


























































































// Used for detectedTokenIds & enabledTokenIds:
export const initialTokenIds = []

const currencyWalletInner = buildReducer



({
  accountId(state, action, next) {
    if (state != null) return state
    for (const accountId of Object.keys(next.root.accounts)) {
      const account = next.root.accounts[accountId]
      for (const walletId of Object.keys(account.walletInfos)) {
        if (walletId === next.id) return accountId
      }
    }
    throw new Error(`Cannot find account for walletId ${next.id}`)
  },

  allEnabledTokenIds: memoizeReducer(
    (next) =>
      next.root.accounts[next.self.accountId].alwaysEnabledTokenIds[
        next.self.pluginId
      ],
    (next) => next.self.enabledTokenIds,
    (alwaysEnabledTokenIds = [], enabledTokenIds = []) =>
      uniqueStrings([...alwaysEnabledTokenIds, ...enabledTokenIds])
  ),

  pluginId: memoizeReducer(
    next => next.root.login.walletInfos[next.id].type,
    next => next.root.plugins.currency,
    (walletType, plugins) => {
      return findCurrencyPluginId(plugins, walletType)
    }
  ),

  paused(state, action, next) {
    return state == null
      ? next.root.accounts[next.self.accountId].pauseWallets
      : action.type === 'CURRENCY_WALLET_CHANGED_PAUSED'
      ? action.payload.paused
      : state
  },

  currencyInfo(state, action, next) {
    if (state != null) return state
    const { pluginId } = next.self
    return next.root.plugins.currency[pluginId].currencyInfo
  },

  detectedTokenIds: sortStringsReducer(
    (state = initialTokenIds, action) => {
      if (action.type === 'CURRENCY_WALLET_LOADED_TOKEN_FILE') {
        return action.payload.detectedTokenIds
      } else if (action.type === 'CURRENCY_ENGINE_DETECTED_TOKENS') {
        const { detectedTokenIds } = action.payload
        return uniqueStrings([...state, ...detectedTokenIds])
      } else if (action.type === 'CURRENCY_ENGINE_CLEARED') {
        return []
      }
      return state
    }
  ),

  enabledTokenIds: sortStringsReducer(
    (state = initialTokenIds, action) => {
      if (action.type === 'CURRENCY_WALLET_LOADED_TOKEN_FILE') {
        return action.payload.enabledTokenIds
      } else if (action.type === 'CURRENCY_WALLET_ENABLED_TOKENS_CHANGED') {
        return action.payload.enabledTokenIds
      } else if (action.type === 'CURRENCY_ENGINE_DETECTED_TOKENS') {
        const { enablingTokenIds } = action.payload
        return uniqueStrings([...state, ...enablingTokenIds])
      }
      return state
    }
  ),

  changeServiceSubscriptions(state = [], action) {
    if (action.type === 'CURRENCY_ENGINE_UPDATE_CHANGE_SERVICE_SUBSCRIPTIONS') {
      const filteredState = state.filter(subscription => {
        return !action.payload.subscriptions.some(
          sub => sub.address === subscription.address
        )
      })
      return [...filteredState, ...action.payload.subscriptions]
    }
    return state
  },

  tokenFileDirty(state = false, action, next, prev) {
    switch (action.type) {
      case 'CURRENCY_WALLET_LOADED_TOKEN_FILE':
      case 'CURRENCY_WALLET_SAVED_TOKEN_FILE':
        // The file has been synced to disk, so it's not dirty:
        return false

      case 'CURRENCY_ENGINE_CLEARED':
      case 'CURRENCY_ENGINE_DETECTED_TOKENS':
      case 'CURRENCY_WALLET_ENABLED_TOKENS_CHANGED':
        // These actions might update the file, so check for diffs:
        return (
          next.self.detectedTokenIds !== prev.self.detectedTokenIds ||
          next.self.enabledTokenIds !== prev.self.enabledTokenIds
        )

      default:
        return state
    }
  },

  engineFailure(state = null, action) {
    if (action.type === 'CURRENCY_ENGINE_FAILED') {
      const { error } = action.payload
      if (error instanceof Error) return error
      return new Error(String(error))
    }
    return state
  },

  engineStarted(state = false, action) {
    return action.type === 'CURRENCY_ENGINE_STARTED'
      ? true
      : action.type === 'CURRENCY_ENGINE_STOPPED'
      ? false
      : state
  },

  fiat(state = '', action) {
    return action.type === 'CURRENCY_WALLET_FIAT_CHANGED'
      ? action.payload.fiatCurrencyCode
      : state
  },

  fiatLoaded(state = false, action) {
    return action.type === 'CURRENCY_WALLET_FIAT_CHANGED' ? true : state
  },

  files(state = {}, action) {
    switch (action.type) {
      case 'CURRENCY_WALLET_FILE_CHANGED': {
        const { json, txidHash } = action.payload
        const out = { ...state }
        out[txidHash] = json
        return out
      }
      case 'CURRENCY_WALLET_FILES_LOADED': {
        const { files } = action.payload
        return {
          ...state,
          ...files
        }
      }
    }
    return state
  },

  fileNames(state = {}, action) {
    switch (action.type) {
      case 'CURRENCY_WALLET_FILE_NAMES_LOADED': {
        const { txFileNames } = action.payload
        return {
          ...state,
          ...txFileNames
        }
      }
      case 'CURRENCY_WALLET_FILE_CHANGED': {
        const { fileName, creationDate, txidHash } = action.payload
        if (
          state[txidHash] == null ||
          creationDate < state[txidHash].creationDate
        ) {
          state[txidHash] = { creationDate, fileName }
        }
        return state
      }
    }
    return state
  },

  syncRatio(state = 0, action) {
    switch (action.type) {
      case 'CURRENCY_ENGINE_CHANGED_SYNC_RATIO': {
        return action.payload.ratio
      }
      case 'CURRENCY_ENGINE_CLEARED': {
        return 0
      }
    }
    return state
  },

  balanceMap(state = new Map(), action) {
    if (action.type === 'CURRENCY_ENGINE_CHANGED_BALANCE') {
      const { balance, tokenId } = action.payload
      const out = new Map(state)
      out.set(tokenId, balance)
      return out
    }
    return state
  },

  balances: memoizeReducer(
    next => next.self.balanceMap,
    next => next.self.currencyInfo,
    next =>
      next.root.accounts[next.self.accountId].allTokens[next.self.pluginId],
    (balanceMap, currencyInfo, allTokens) => {
      const out = {}
      for (const tokenId of balanceMap.keys()) {
        const balance = balanceMap.get(tokenId)
        const { currencyCode } =
          tokenId == null ? currencyInfo : allTokens[tokenId]
        if (balance != null) out[currencyCode] = balance
      }
      return out
    }
  ),

  height(state = 0, action) {
    return action.type === 'CURRENCY_ENGINE_CHANGED_HEIGHT'
      ? action.payload.height
      : state
  },

  name(state = null, action) {
    return action.type === 'CURRENCY_WALLET_NAME_CHANGED'
      ? action.payload.name
      : state
  },

  nameLoaded(state = false, action) {
    return action.type === 'CURRENCY_WALLET_NAME_CHANGED' ? true : state
  },

  seenTxCheckpoint(state = null, action) {
    return action.type === 'CURRENCY_ENGINE_SEEN_TX_CHECKPOINT_CHANGED'
      ? action.payload.checkpoint
      : state
  },

  sortedTxidHashes: memoizeReducer(
    next => next.self.txidHashes,
    txidHashes =>
      Object.keys(txidHashes).sort((txidHash1, txidHash2) => {
        if (txidHashes[txidHash1].date > txidHashes[txidHash2].date) return -1
        if (txidHashes[txidHash1].date < txidHashes[txidHash2].date) return 1
        return 0
      })
  ),

  stakingStatus(state = { stakedAmounts: [] }, action) {
    return action.type === 'CURRENCY_ENGINE_CHANGED_STAKING'
      ? action.payload.stakingStatus
      : state
  },

  txidHashes(state = {}, action) {
    switch (action.type) {
      case 'CURRENCY_ENGINE_CHANGED_TXS': {
        return mergeTxidHashes(state, action.payload.txidHashes)
      }
      case 'CURRENCY_WALLET_FILE_NAMES_LOADED': {
        const { txFileNames } = action.payload
        const newTxidHashes = {}
        for (const txidHash of Object.keys(txFileNames)) {
          newTxidHashes[txidHash] = {
            date: txFileNames[txidHash].creationDate
          }
        }
        return mergeTxidHashes(state, newTxidHashes)
      }
    }
    return state
  },

  txs(state = {}, action, next) {
    switch (action.type) {
      case 'CHANGE_MERGE_TX': {
        const { tx } = action.payload
        const out = { ...state }
        out[tx.txid] = tx
        return out
      }
      case 'CURRENCY_ENGINE_CHANGED_TXS': {
        const { txs } = action.payload
        const out = { ...state }
        for (const tx of txs) {
          out[tx.txid] = mergeTx(tx, out[tx.txid])
        }
        return out
      }
      case 'CURRENCY_ENGINE_CLEARED':
        return {}
    }
    return state
  },

  unactivatedTokenIds(state = [], action) {
    switch (action.type) {
      case 'CURRENCY_ENGINE_CHANGED_UNACTIVATED_TOKEN_IDS': {
        return action.payload.unactivatedTokenIds
      }
      case 'CURRENCY_ENGINE_CLEARED': {
        return []
      }
    }
    return state
  },

  gotTxs(state = new Set(), action) {
    switch (action.type) {
      case 'CURRENCY_ENGINE_GOT_TXS': {
        const { tokenId } = action.payload
        const out = new Set(state)
        out.add(tokenId)
        return out
      }
      case 'CURRENCY_ENGINE_CLEARED':
        return new Set()
      default:
        return state
    }
  },

  walletInfo(state, action, next) {
    return next.root.login.walletInfos[next.id]
  },

  publicWalletInfo(state = null, action) {
    return action.type === 'CURRENCY_WALLET_PUBLIC_INFO'
      ? action.payload.walletInfo
      : state
  }
})

function mergeTxidHashes(a, b) {
  const out = { ...a }
  for (const hash of Object.keys(b)) {
    const oldItem = out[hash]
    const newItem = b[hash]
    out[hash] =
      oldItem == null
        ? newItem
        : {
            date: Math.min(newItem.date, oldItem.date),
            txid: _nullishCoalesce(newItem.txid, () => ( oldItem.txid))
          }
  }
  return out
}

export const currencyWalletReducer = filterReducer




(currencyWalletInner, (action, next) => {
  return /^CURRENCY_/.test(action.type) &&
    'payload' in action &&
    typeof action.payload === 'object' &&
    'walletId' in action.payload &&
    action.payload.walletId === next.id
    ? action
    : { type: 'UPDATE_NEXT' }
})

/**
 * Merges a new incoming transaction with an existing transaction.
 */
export function mergeTx(
  tx,
  oldTx
) {
  const {
    confirmations = 'unconfirmed',
    isSend = lt(tx.nativeAmount, '0'),
    tokenId = null
  } = tx

  const out = {
    blockHeight: tx.blockHeight,
    chainAction: tx.chainAction,
    chainAssetAction: new Map(_nullishCoalesce(_optionalChain([oldTx, 'optionalAccess', _ => _.chainAssetAction]), () => ( []))),
    confirmations,
    date: tx.date,
    isSend,
    memos: tx.memos,
    nativeAmount: new Map(_nullishCoalesce(_optionalChain([oldTx, 'optionalAccess', _2 => _2.nativeAmount]), () => ( []))),
    networkFee: new Map(_nullishCoalesce(_optionalChain([oldTx, 'optionalAccess', _3 => _3.networkFee]), () => ( []))),
    otherParams: tx.otherParams,
    ourReceiveAddresses: tx.ourReceiveAddresses,
    signedTx: tx.signedTx,
    txid: tx.txid
  }
  out.nativeAmount.set(tokenId, tx.nativeAmount)
  out.networkFee.set(tokenId, _nullishCoalesce(tx.networkFee, () => ( '0')))
  if (tx.feeRateUsed != null) {
    out.feeRateUsed = tx.feeRateUsed
  }
  if (tx.parentNetworkFee != null) {
    out.networkFee.set(null, String(tx.parentNetworkFee))
  }
  if (tx.chainAssetAction != null) {
    out.chainAssetAction.set(tokenId, tx.chainAssetAction)
  }

  return out
}






function sortStringsReducer(reducer) {
  return (state, action) => {
    const out = reducer(state, action)
    if (out === state) return state

    out.sort((a, b) => (a === b ? 0 : a > b ? 1 : -1))
    if (state == null || !compare(out, state)) return out
    return state
  }
}
