 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 { base64 } from 'rfc4648'


import { decryptText } from '../../util/crypto/crypto'
import { makeCreateKit } from '../login/create'
import {
  decryptKeyInfos,
  findFirstKey,
  makeAccountType,
  makeKeyInfo,
  makeKeysKit
} from '../login/keys'
import { applyKit, decryptChildKey, searchTree } from '../login/login'
import { getStashById } from '../login/login-selectors'


import { createStorageKeys, wasEdgeStorageKeys } from '../login/storage-keys'


/**
 * Creates a child login under the provided login, with the given appId.
 */
async function createChildLogin(
  ai,
  stashTree,
  sessionKey,
  appId
) {
  let pin
  if (stashTree.pin2TextBox != null) {
    pin = decryptText(stashTree.pin2TextBox, sessionKey.loginKey)
  }

  const { kit } = await makeCreateKit(ai, sessionKey, appId, {
    keyInfo: makeKeyInfo(
      makeAccountType(appId),
      wasEdgeStorageKeys(createStorageKeys(ai))
    ),
    pin,
    username: stashTree.username
  })
  const parentKit = {
    loginId: sessionKey.loginId,
    server: kit.server,
    serverPath: kit.serverPath,
    stash: { children: [kit.stash ] }
  }
  return await applyKit(ai, sessionKey, parentKit)
}

/**
 * Ensures that the loginTree contains an account for the specified appId.
 * @return A `Promise`, which will resolve to a loginTree that does have
 * the requested account.
 */
export async function ensureAccountExists(
  ai,
  stashTree,
  sessionKey,
  appId
) {
  const { log } = ai.props

  // If there is no app login, make that:
  const appStash = searchTree(stashTree, stash => stash.appId === appId)
  if (appStash == null) {
    // For crash errors:
    ai.props.log.breadcrumb('createChildLogin', {})

    return await createChildLogin(ai, stashTree, sessionKey, appId)
  }

  // Decrypt the wallet keys:
  // TODO: Once we cache public keys, use those instead:
  const appKey = decryptChildKey(stashTree, sessionKey, appStash.loginId)
  const keyInfos = decryptKeyInfos(appStash, appKey.loginKey)
  log.warn(
    `Login: decrypted keys for user ${base64.stringify(stashTree.loginId)}`
  )

  // If the account has no repo, make one:
  const accountType = makeAccountType(appId)
  if (findFirstKey(keyInfos, accountType) == null) {
    // For crash errors:
    ai.props.log.breadcrumb('createAccountRepo', {})

    const keyInfo = makeKeyInfo(
      accountType,
      wasEdgeStorageKeys(createStorageKeys(ai))
    )
    const keysKit = makeKeysKit(ai, appKey, [keyInfo])
    return await applyKit(ai, sessionKey, keysKit)
  }

  // Everything is fine, so do nothing:
  return stashTree
}

/**
 * Creates an `EdgeAccount` API object.
 */
export async function makeAccount(
  ai,
  sessionKey,
  loginType,
  opts
) {
  const { pauseWallets = false } = opts
  // Override the appId if duress mode is enabled:
  const appId =
    opts.duressMode === true
      ? ai.props.state.login.contextAppId + '.duress'
      : ai.props.state.login.contextAppId
  const { log } = ai.props

  // For crash errors:
  ai.props.log.breadcrumb('makeAccount', {})

  // Create the loginTree:
  const { stashTree } = getStashById(ai, sessionKey.loginId)
  await ensureAccountExists(ai, stashTree, sessionKey, appId)
  log.warn('Login: account exists for appId')

  // Add the login to redux:
  ai.props.dispatch({
    type: 'LOGIN',
    payload: {
      appId,
      loginType,
      pauseWallets,
      rootLoginId: stashTree.loginId,
      sessionKey
    }
  })

  return await waitForAccount(ai, ai.props.state.lastAccountId)
}

/**
 * Waits for the account API to appear and returns it.
 */
function waitForAccount(ai, accountId) {
  const out = ai.waitFor(
    (props) => {
      const accountState = props.state.accounts[accountId]
      if (accountState.loadFailure != null) throw accountState.loadFailure

      const accountOutput = props.output.accounts[accountId]
      if (_optionalChain([accountOutput, 'optionalAccess', _ => _.accountApi]) != null) {
        return accountOutput.accountApi
      }
    }
  )
  return out
}
