 function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }import { bridgifyObject, onMethod, watchMethod } from 'yaob'

import { checkPasswordRules, fixUsername } from '../../client-side'
import {
  asChallengeErrorPayload,
  asMaybePasswordError,








} from '../../types/types'
import { verifyData } from '../../util/crypto/verify'
import { base58 } from '../../util/encoding'
import { makeAccount } from '../account/account-init'
import { createLogin, usernameAvailable } from '../login/create'
import { requestEdgeLogin } from '../login/edge'
import { makeAuthJson, searchTree, syncLogin } from '../login/login'
import { loginFetch } from '../login/login-fetch'
import { fetchLoginMessages } from '../login/login-messages'
import {
  getEmptyStash,
  getStashById,
  getStashByUsername
} from '../login/login-selectors'
import { removeStash, saveStash } from '../login/login-stash'

import { resetOtp } from '../login/otp'
import { loginPassword } from '../login/password'
import { findPin2Stash, loginPin2 } from '../login/pin2'
import { getQuestions2, loginRecovery2 } from '../login/recovery2'

import { CLIENT_FILE_NAME, clientFile } from './client-file'
import { EdgeInternalStuff } from './internal-api'

export function makeContextApi(ai) {
  const appId = ai.props.state.login.contextAppId
  const clientId = base58.stringify(ai.props.state.clientInfo.clientId)
  const $internalStuff = new EdgeInternalStuff(ai)
  let pauseTimer

  async function disableDuressMode() {
    // Persist disabled duress mode
    await clientFile.save(ai.props.io.disklet, CLIENT_FILE_NAME, {
      ...ai.props.state.clientInfo,
      duressLoginId: undefined
    })
    // Disable duress mode
    ai.props.dispatch({
      type: 'LOGIN_DURESS_MODE_DISABLED'
    })
  }
  async function enableDuressMode(duressLoginId) {
    // Persist enabled duress mode
    await clientFile.save(ai.props.io.disklet, CLIENT_FILE_NAME, {
      ...ai.props.state.clientInfo,
      duressLoginId
    })
    // Enable duress mode
    ai.props.dispatch({
      type: 'LOGIN_DURESS_MODE_ENABLED',
      payload: {
        duressLoginId
      }
    })
  }

  const out = {
    on: onMethod,
    watch: watchMethod,

    appId,
    clientId,

    async close() {
      ai.props.close()
    },

    $internalStuff,

    fixUsername,

    get localUsers() {
      return ai.props.state.login.localUsers
    },

    async forgetAccount(rootLoginId) {
      const loginId = base58.parse(rootLoginId)

      // Safety check:
      for (const accountId of ai.props.state.accountIds) {
        const accountState = ai.props.state.accounts[accountId]
        if (verifyData(accountState.stashTree.loginId, loginId)) {
          throw new Error('Cannot remove logged-in user')
        }
      }

      await removeStash(ai, loginId)
    },

    async fetchChallenge() {
      const response = await loginFetch(ai, 'POST', '/v2/captcha/create', {})
      const { challengeId, challengeUri } = asChallengeErrorPayload(response)
      return { challengeId, challengeUri }
    },

    async usernameAvailable(username, opts = {}) {
      const { challengeId } = opts
      username = fixUsername(username)
      return await usernameAvailable(ai, username, challengeId)
    },

    async createAccount(
      opts
    ) {
      // For crash errors:
      ai.props.log.breadcrumb('EdgeContext.createAccount', {})

      if (opts.username != null) {
        opts.username = fixUsername(opts.username)
      }
      const sessionKey = await createLogin(ai, opts, opts)
      return await makeAccount(ai, sessionKey, 'newAccount', opts)
    },

    async loginWithKey(
      usernameOrLoginId,
      loginKey,
      opts = {}
    ) {
      const { now = new Date(), useLoginId = false } = opts

      const stashTree = useLoginId
        ? getStashById(ai, base58.parse(usernameOrLoginId)).stashTree
        : getStashByUsername(ai, fixUsername(usernameOrLoginId))
      if (stashTree == null) {
        throw new Error('User does not exist on this device')
      }

      const appStash = searchTree(stashTree, stash => stash.appId === appId)
      if (appStash == null) {
        throw new Error(`Cannot find requested appId: "${appId}"`)
      }
      const sessionKey = {
        loginId: appStash.loginId,
        loginKey: base58.parse(loginKey)
      }

      // Verify that the provided key works for decryption:
      makeAuthJson(stashTree, sessionKey)

      // Save the date:
      stashTree.lastLogin = now
      saveStash(ai, stashTree).catch(() => {})

      // Since we logged in offline, update the stash in the background:
      syncLogin(ai, sessionKey).catch(error => ai.props.onError(error))

      return await makeAccount(ai, sessionKey, 'keyLogin', opts)
    },

    async loginWithPassword(
      username,
      password,
      opts = {}
    ) {
      // For crash errors:
      ai.props.log.breadcrumb('EdgeContext.loginWithPassword', {})

      username = fixUsername(username)
      const stashTree = getStashByUsername(ai, username)
      const sessionKey = await loginPassword(
        ai,
        _nullishCoalesce(stashTree, () => ( getEmptyStash(username))),
        password,
        opts
      )
      // Disable duress mode if it is setup and active
      if (ai.props.state.clientInfo.duressLoginId != null) {
        await disableDuressMode()
      }
      return await makeAccount(ai, sessionKey, 'passwordLogin', opts)
    },

    checkPasswordRules,

    async loginWithPIN(
      usernameOrLoginId,
      pin,
      opts = {}
    ) {
      // For crash errors:
      ai.props.log.breadcrumb('EdgeContext.loginWithPIN', {})

      const { useLoginId = false } = opts

      const stashTree = useLoginId
        ? getStashById(ai, base58.parse(usernameOrLoginId)).stashTree
        : getStashByUsername(ai, fixUsername(usernameOrLoginId))
      if (stashTree == null) {
        throw new Error('User does not exist on this device')
      }

      let sessionKey
      try {
        const stash = findPin2Stash(stashTree, appId)
        if (stash == null) {
          throw new Error(
            'PIN login is not enabled for this account on this device'
          )
        }
        sessionKey = await loginPin2(ai, stashTree, stash, pin, opts)
        // Disable duress mode if it is setup and active
        if (ai.props.state.clientInfo.duressLoginId != null) {
          await disableDuressMode()
        }
        return await makeAccount(ai, sessionKey, 'pinLogin', opts)
      } catch (error) {
        // No duress mode setup or error is not a failed login
        if (asMaybePasswordError(error) == null) {
          throw error
        }
        const duressAppId = appId + '.duress'
        const duressStash = searchTree(
          stashTree,
          stash => stash.appId === duressAppId
        )
        // No duress account configured
        if (duressStash == null) {
          throw error
        }
        // Try login with duress account
        sessionKey = await loginPin2(ai, stashTree, duressStash, pin, opts)
        // The original account will be used for display
        await enableDuressMode(stashTree.loginId)
        // Make the account with duress mode enabled
        return await makeAccount(ai, sessionKey, 'pinLogin', {
          ...opts,
          duressMode: true
        })
      }
    },

    async loginWithRecovery2(
      recovery2Key,
      username,
      answers,
      opts = {}
    ) {
      // For crash errors:
      ai.props.log.breadcrumb('EdgeContext.loginWithRecovery2', {})

      username = fixUsername(username)
      const stashTree = getStashByUsername(ai, username)
      const sessionKey = await loginRecovery2(
        ai,
        _nullishCoalesce(stashTree, () => ( getEmptyStash(username))),
        base58.parse(recovery2Key),
        answers,
        opts
      )
      return await makeAccount(ai, sessionKey, 'recoveryLogin', opts)
    },

    async fetchRecovery2Questions(
      recovery2Key,
      username
    ) {
      username = fixUsername(username)
      return await getQuestions2(ai, base58.parse(recovery2Key), username)
    },

    async requestEdgeLogin(
      opts
    ) {
      // For crash errors:
      ai.props.log.breadcrumb('EdgeContext.requestEdgeLogin', {})

      return await requestEdgeLogin(ai, appId, opts)
    },

    async requestOtpReset(
      username,
      otpResetToken
    ) {
      username = fixUsername(username)
      return await resetOtp(ai, username, otpResetToken)
    },

    async fetchLoginMessages() {
      return await fetchLoginMessages(ai)
    },

    get paused() {
      return ai.props.state.paused
    },

    async changePaused(
      paused,
      opts = {}
    ) {
      const { secondsDelay = 0 } = opts

      // If a timer is already running, stop that:
      if (pauseTimer != null) {
        clearTimeout(pauseTimer)
        pauseTimer = undefined
      }

      // If the state is the same, do nothing:
      if (ai.props.state.paused === paused) return

      // Otherwise, make the change:
      if (secondsDelay === 0) {
        ai.props.dispatch({ type: 'PAUSE', payload: paused })
      } else {
        pauseTimer = setTimeout(() => {
          pauseTimer = undefined
          ai.props.dispatch({ type: 'PAUSE', payload: paused })
        }, secondsDelay * 1000)
      }
    },

    get logSettings() {
      return ai.props.state.logSettings
    },

    async changeLogSettings(settings) {
      const newSettings = { ...ai.props.state.logSettings, ...settings }
      ai.props.dispatch({ type: 'CHANGE_LOG_SETTINGS', payload: newSettings })
    }
  }
  bridgifyObject(out)

  return out
}
