 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 { makeSyncClient } from 'edge-sync-client'
import { createStore } from 'redux'
import { attachPixie, filterPixie, } from 'redux-pixies'
import { emit } from 'yaob'


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

import { CLIENT_FILE_NAME, clientFile } from './context/client-file'
import { INFO_CACHE_FILE_NAME, infoCacheFile } from './context/info-cache-file'
import { filterLogs, makeLog } from './log/log'
import { loadAirbitzStashes } from './login/airbitz-stashes'
import { loadStashes } from './login/login-stash'
import { watchPlugins } from './plugins/plugins-actions'
import { rootPixie, } from './root-pixie'
import { defaultLogSettings, reducer, } from './root-reducer'

let allContexts = []

const enhancer =
  typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION__ != null
    ? window.__REDUX_DEVTOOLS_EXTENSION__({ name: 'core' })
    : undefined

/**
 * Creates the root object for the entire core state machine.
 * This core object contains the `io` object, context options,
 * Redux store, and tree of background workers.
 */
export async function makeContext(
  ios,
  logBackend,
  opts
) {
  const { io } = ios
  const {
    airbitzSupport = false,
    apiSecret,
    appId = '',
    authServer,
    deviceDescription = null,
    changeServer,
    hideKeys = false,
    infoServer,
    loginServer,
    plugins: pluginsInit = {},
    skipBlockHeight = false,
    syncServer
  } = opts
  let { apiKey } = opts
  if (apiKey == null || apiKey === '') {
    apiKey = '4248c1bf41e53b840a5fdb2c872dd3ade525e66d'
  }
  const authServers = toServerArray(authServer, [
    'https://login1.edge.app',
    'https://login2.edge.app'
  ])
  const changeServers = toServerArray(changeServer, ['wss://change1.edge.app'])
  const infoServers = toServerArray(infoServer, [
    'https://info1.edge.app',
    'https://info2.edge.app'
  ])
  const loginServers = toServerArray(
    loginServer,
    authServers.map(server => server.replace(/\/api$/, ''))
  )
  const syncServers = toServerArray(syncServer, [
    'https://sync-us1.edge.app',
    'https://sync-us2.edge.app',
    'https://sync-us3.edge.app',
    'https://sync-us4.edge.app',
    'https://sync-us5.edge.app',
    'https://sync-us6.edge.app',
    'https://sync-eu.edge.app'
  ])
  const logSettings = { ...defaultLogSettings, ...opts.logSettings }

  changeServers.map(server => validateServer(server))
  infoServers.map(server => validateServer(server))
  loginServers.map(server => validateServer(server))
  syncServers.map(server => validateServer(server))

  // Create a redux store:
  const redux = createStore(reducer, enhancer)

  // Create a log wrapper, using the settings from redux:
  logBackend = filterLogs(logBackend, () => {
    const state = redux.getState()
    return state.ready ? state.logSettings : logSettings
  })
  const log = makeLog(logBackend, 'edge-core')

  // Load the login stashes from disk:
  let [clientInfo, infoCache = {}, stashes] = await Promise.all([
    clientFile.load(io.disklet, CLIENT_FILE_NAME),
    infoCacheFile.load(io.disklet, INFO_CACHE_FILE_NAME),
    loadStashes(io.disklet, log)
  ])

  // Load legacy stashes from disk
  if (airbitzSupport) {
    // Edge will write modern files to disk at login time,
    // but it won't delete the legacy Airbitz data.
    // Once this happens, we need to ignore the legacy files
    // and just use the new files:
    const avoidUsernames = new Set()
    for (const { username } of stashes) {
      if (username != null) avoidUsernames.add(username)
    }

    const airbitzStashes = await loadAirbitzStashes(io, avoidUsernames)
    stashes.push(...airbitzStashes)
  }

  // Save the initial client info if the client is new:
  if (clientInfo == null) {
    clientInfo = { clientId: io.random(16) }
    await clientFile.save(io.disklet, CLIENT_FILE_NAME, clientInfo)
  }

  // Write everything to redux:
  redux.dispatch({
    type: 'INIT',
    payload: {
      apiKey,
      apiSecret,
      appId,
      changeServers,
      loginServers,
      infoCache,
      infoServers,
      syncServers,
      clientInfo,
      deviceDescription,
      hideKeys,
      logSettings,
      pluginsInit,
      skipBlockHeight,
      stashes
    }
  })

  // Subscribe to new plugins:
  const closePlugins = watchPlugins(
    ios,
    infoCache,
    logBackend,
    pluginsInit,
    redux.dispatch
  )

  // Create sync client:
  const syncClient = makeSyncClient({
    log,
    fetch: (uri, opts) => io.fetch(uri, { ...opts, corsBypass: 'never' }),
    edgeServers: { infoServers, syncServers }
  })

  // Start the pixie tree:
  const mirror = { output: {}  }
  const closePixie = attachPixie(
    redux,
    filterPixie(
      rootPixie,
      (props) => ({
        ...props,
        close() {
          closePixie()
          closePlugins()
          redux.dispatch({ type: 'CLOSE' })
        },
        io,
        log,
        logBackend,
        onError: error => {
          if (_optionalChain([mirror, 'access', _ => _.output, 'access', _2 => _2.context, 'optionalAccess', _3 => _3.api]) != null) {
            emit(mirror.output.context.api, 'error', error)
          }
        },
        syncClient
      })
    ),
    error => log.error(error),
    output => (mirror.output = output)
  )

  const out = mirror.output.context.api
  allContexts.push(out)
  return out
}

function toServerArray(
  server,
  fallback
) {
  return typeof server === 'string'
    ? [server]
    : server != null && server.length > 0
    ? server
    : fallback
}

/**
 * We use this for unit testing, to kill all core contexts.
 */
export function closeEdge() {
  for (const context of allContexts) context.close().catch(() => {})
  allContexts = []
}
