 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 { buildReducer, filterReducer, memoizeReducer } from 'redux-keto'








import { compare } from '../../util/compare'
import { verifyData } from '../../util/crypto/verify'

import {
  decryptAllWalletInfos,
  findFirstKey,
  makeAccountType
} from '../login/keys'
import { makeLoginTree, searchTree } from '../login/login'







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
















































export const initialCustomTokens = {}
const blankSessionKey = {
  loginId: new Uint8Array(),
  loginKey: new Uint8Array()
}

const accountInner = buildReducer({
  accountWalletInfo: memoizeReducer(
    (next) => next.self.activeAppId,
    (next) => next.self.allWalletInfosFull,
    (appId, walletInfos) => {
      const type = makeAccountType(appId)
      const accountWalletInfo = findFirstKey(walletInfos, type)
      if (accountWalletInfo == null) {
        throw new Error(`Cannot find a "${type}" repo`)
      }
      return accountWalletInfo
    }
  ),

  accountWalletInfos: memoizeReducer(
    (next) => next.self.activeAppId,
    (next) => next.self.allWalletInfosFull,
    (appId, walletInfos) => {
      // Wallets created in Edge that then log into Airbitz or BitcoinPay
      // might end up with wallets stored in the wrong account repo.
      // This code attempts to locate those repos.
      const walletTypes = [makeAccountType(appId)]
      if (appId === '') walletTypes.push('account:repo:co.airbitz.wallet', '')
      return walletInfos.filter(info => walletTypes.includes(info.type))
    }
  ),

  allWalletInfosFull: memoizeReducer(
    (next) => next.self.stashTree,
    (next) => next.self.login,
    (next) => next.self.legacyWalletInfos,
    (next) => next.self.walletStates,
    (
      stashTree,
      appSessionKey,
      legacyWalletInfos,
      walletStates
    ) =>
      decryptAllWalletInfos(
        stashTree,
        appSessionKey,
        legacyWalletInfos,
        walletStates
      )
  ),

  allWalletInfosClean: memoizeReducer(
    (next) => next.self.allWalletInfosFull,
    (walletInfos) =>
      walletInfos.map(info => ({ ...info, keys: {} }))
  ),

  currencyWalletErrors(state = {}, action, next, prev) {
    const { activeWalletIds } = next.self
    const walletStates = next.root.currency.wallets
    let dirty = activeWalletIds !== _optionalChain([prev, 'access', _ => _.self, 'optionalAccess', _2 => _2.activeWalletIds])

    const out = {}
    for (const id of activeWalletIds) {
      const failure = walletStates[id].engineFailure
      if (failure != null) out[id] = failure
      if (out[id] !== state[id]) dirty = true
    }
    return dirty ? out : state
  },

  currencyWalletIds: memoizeReducer(
    (next) => next.self.walletInfos,
    (next) => next.root.plugins.currency,
    (walletInfos, plugins) =>
      Object.keys(walletInfos)
        .filter(walletId => {
          const info = walletInfos[walletId]
          const pluginId = maybeFindCurrencyPluginId(plugins, info.type)
          return !info.deleted && pluginId != null
        })
        .sort((walletId1, walletId2) => {
          const info1 = walletInfos[walletId1]
          const info2 = walletInfos[walletId2]
          return info1.sortIndex - info2.sortIndex
        })
  ),

  activeWalletIds: memoizeReducer(
    (next) => next.self.walletInfos,
    (next) => next.self.currencyWalletIds,
    (next) => next.self.keysLoaded,
    (walletInfos, ids, keysLoaded) =>
      keysLoaded ? ids.filter(id => !walletInfos[id].archived) : []
  ),

  archivedWalletIds: memoizeReducer(
    (next) => next.self.walletInfos,
    (next) => next.self.currencyWalletIds,
    (next) => next.self.keysLoaded,
    (walletInfos, ids, keysLoaded) =>
      keysLoaded ? ids.filter(id => walletInfos[id].archived) : []
  ),

  hiddenWalletIds: memoizeReducer(
    (next) => next.self.walletInfos,
    (next) => next.self.currencyWalletIds,
    (next) => next.self.keysLoaded,
    (walletInfos, ids, keysLoaded) =>
      keysLoaded ? ids.filter(id => walletInfos[id].hidden) : []
  ),

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

  legacyWalletInfos(state = [], action) {
    return action.type === 'ACCOUNT_KEYS_LOADED'
      ? action.payload.legacyWalletInfos
      : state
  },

  walletInfos: memoizeReducer(
    (next) => next.self.allWalletInfosFull,
    (walletInfos) => {
      const out = {}
      for (const info of walletInfos) {
        out[info.id] = info
      }
      return out
    }
  ),

  walletStates(state = {}, action) {
    return action.type === 'ACCOUNT_CHANGED_WALLET_STATES' ||
      action.type === 'ACCOUNT_KEYS_LOADED'
      ? action.payload.walletStates
      : state
  },

  pauseWallets(state = false, action) {
    return action.type === 'LOGIN' ? action.payload.pauseWallets : state
  },

  activeAppId: (state = '', action) => {
    return action.type === 'LOGIN' ? action.payload.appId : state
  },

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

  login: memoizeReducer(
    (next) => next.self.activeAppId,
    (next) => next.self.loginTree,
    (appId, loginTree) => {
      const out = searchTree(loginTree, login => login.appId === appId)
      if (out == null) {
        throw new Error(`Internal error: cannot find login for ${appId}`)
      }
      return out
    }
  ),

  loginTree: memoizeReducer(
    (next) => next.self.stashTree,
    (next) => next.self.sessionKey,
    (stashTree, sessionKey) => makeLoginTree(stashTree, sessionKey)
  ),

  loginType(state = 'newAccount', action) {
    return action.type === 'LOGIN' ? action.payload.loginType : state
  },

  rootLoginId(state = new Uint8Array(0), action) {
    return action.type === 'LOGIN' ? action.payload.rootLoginId : state
  },

  sessionKey(state = blankSessionKey, action) {
    return action.type === 'LOGIN' ? action.payload.sessionKey : state
  },

  stashTree: memoizeReducer(
    (next) => next.self.rootLoginId,
    (next) => next.root.login.stashes,
    (rootLoginId, stashes) => {
      for (const stash of stashes) {
        if (verifyData(stash.loginId, rootLoginId)) return stash
      }
      throw new Error('There is no stash')
    }
  ),

  allTokens(state = {}, action, next, prev) {
    const { builtinTokens, customTokens } = next.self

    // Roll our own `memoizeReducer` implementation,
    // so we can minimize our diff as much as possible:
    if (
      prev.self == null ||
      builtinTokens !== prev.self.builtinTokens ||
      customTokens !== prev.self.customTokens
    ) {
      const out = { ...state }
      for (const pluginId of Object.keys(next.root.plugins.currency)) {
        if (
          prev.self == null ||
          builtinTokens[pluginId] !== prev.self.builtinTokens[pluginId] ||
          customTokens[pluginId] !== prev.self.customTokens[pluginId]
        ) {
          out[pluginId] = {
            ...customTokens[pluginId],
            ...builtinTokens[pluginId]
          }
        }
      }
      return out
    }
    return state
  },

  builtinTokens(state = {}, action) {
    switch (action.type) {
      case 'ACCOUNT_BUILTIN_TOKENS_LOADED': {
        const { pluginId, tokens } = action.payload
        return { ...state, [pluginId]: tokens }
      }
    }
    return state
  },

  customTokens(
    state = initialCustomTokens,
    action
  ) {
    switch (action.type) {
      case 'ACCOUNT_CUSTOM_TOKENS_LOADED': {
        const { customTokens } = action.payload
        return customTokens
      }
      case 'ACCOUNT_CUSTOM_TOKEN_ADDED': {
        const { pluginId, tokenId, token } = action.payload
        const oldList = _nullishCoalesce(state[pluginId], () => ( {}))

        // Has anything changed?
        if (compare(oldList[tokenId], token)) return state

        const newList = { ...oldList, [tokenId]: token }
        return { ...state, [pluginId]: newList }
      }
      case 'ACCOUNT_CUSTOM_TOKEN_REMOVED': {
        const { pluginId, tokenId } = action.payload
        const oldList = _nullishCoalesce(state[pluginId], () => ( {}))

        // Has anything changed?
        if (oldList[tokenId] == null) return state

        const { [tokenId]: unused, ...newList } = oldList
        return { ...state, [pluginId]: newList }
      }
    }
    return state
  },

  alwaysEnabledTokenIds(state = {}, action) {
    switch (action.type) {
      case 'ACCOUNT_ALWAYS_ENABLED_TOKENS_CHANGED': {
        const { pluginId, tokenIds } = action.payload
        return { ...state, [pluginId]: tokenIds }
      }
    }
    return state
  },

  swapSettings(state = {}, action) {
    switch (action.type) {
      case 'ACCOUNT_PLUGIN_SETTINGS_LOADED':
        return action.payload.swapSettings

      case 'ACCOUNT_SWAP_SETTINGS_CHANGED': {
        const { pluginId, swapSettings } = action.payload
        const out = { ...state }
        out[pluginId] = swapSettings
        return out
      }
    }
    return state
  },

  userSettings(state = {}, action) {
    switch (action.type) {
      case 'ACCOUNT_PLUGIN_SETTINGS_CHANGED': {
        const { pluginId, userSettings } = action.payload
        const out = { ...state }
        out[pluginId] = userSettings
        return out
      }

      case 'ACCOUNT_PLUGIN_SETTINGS_LOADED':
        return action.payload.userSettings
    }
    return state
  }
})

export const accountReducer = filterReducer




(accountInner, (action, next) => {
  if (
    /^ACCOUNT_/.test(action.type) &&
    'payload' in action &&
    typeof action.payload === 'object' &&
    'accountId' in action.payload &&
    action.payload.accountId === next.id
  ) {
    return action
  }

  if (action.type === 'LOGIN' && next.root.lastAccountId === next.id) {
    return action
  }

  return { type: 'UPDATE_NEXT' }
})
