// @flow

import type { Disklet } from "disklet";
import type { FetchHeaders, FetchOptions, FetchResponse } from "serverlet";
import type { Subscriber } from "yaob";

export * from "./error";
export * from "./fake-types";
export * from "./server-cleaners";
export * from "./server-types";

// ---------------------------------------------------------------------
// helper types
// ---------------------------------------------------------------------

/** A JSON object (as opposed to an array or primitive). */
export type JsonObject = {
  [name: string]: any; // TODO: this needs to become `mixed`
}

/** When we return errors explicitly instead of throwing them */
export type EdgeResult<T> =
  | { ok: true; result: T }
  | { ok: false; error: mixed };

/** A collection of mixed extra methods exposed by a plugin. */
export type EdgeOtherMethods = {
  +[name: string]: any;
}

/** We frequently index things by pluginId, so provide a helper. */
export type EdgePluginMap<Value> = {
  [pluginId: string]: Value;
}

// ---------------------------------------------------------------------
// io types
// ---------------------------------------------------------------------

// Node.js randomBytes function:
export type EdgeRandomFunction = (bytes: number) => Uint8Array;

// The scrypt function Edge expects:
export type EdgeScryptFunction = (
  data: Uint8Array,
  salt: Uint8Array,
  n: number,
  r: number,
  p: number,
  dklen: number,
) => Promise<Uint8Array>;

// The subset of the fetch function Edge expects:
export type EdgeFetchOptions = FetchOptions & {
  corsBypass?: "auto" | "always" | "never";
};
export type EdgeFetchHeaders = FetchHeaders;
export type EdgeFetchResponse = FetchResponse;
export type EdgeFetchFunction = (
  uri: string,
  opts?: EdgeFetchOptions,
) => Promise<EdgeFetchResponse>;

/**
 * Access to platform-specific resources.
 * The core never talks to the outside world on its own,
 * but always goes through this object.
 */
export type EdgeIo = {
  // Crypto:
  +random: EdgeRandomFunction;
  +scrypt: EdgeScryptFunction;

  // Local io:
  +disklet: Disklet;
  +fetch: EdgeFetchFunction;

  /**
   * This is like `fetch`, but will try to avoid CORS limitations
   * on platforms where that may be a problem.
   *
   * @deprecated Use EdgeIo.fetch instead, which now includes CORS avoidance by default
   */
  +fetchCors: EdgeFetchFunction;
}

// logging -------------------------------------------------------------

export type EdgeLogMethod = (...args: any[]) => void;

/**
 * Logs a message.
 *
 * Call `log(message)` for normal information messages,
 * or `log.warn(message)` / `log.error(message)` for something more severe.
 * To record crash information, use `log.crash(error, json)` for errors,
 * and `log.breadcrumb(message, json)` for data leading up to crashes.
 */
export type EdgeLog = EdgeLogMethod & {
  // Crash logging:
  +breadcrumb: (message: string, metadata: JsonObject) => void;
  +crash: (error: mixed, metadata: JsonObject) => void;

  // Message logging:
  +warn: EdgeLogMethod;
  +error: EdgeLogMethod;
};

export type EdgeLogType = "info" | "warn" | "error";

export type EdgeLogSettings = {
  sources: { [pluginId: string]: EdgeLogType | "silent" };
  defaultLogLevel: EdgeLogType | "silent";
}

/**
 * The EdgeLog function stringifies its arguments and adds
 * some extra information to form this event type.
 */
export type EdgeLogEvent = {
  message: string;
  source: string;
  time: Date;
  type: EdgeLogType;
}

export type EdgeBreadcrumbEvent = {
  message: string;
  metadata: JsonObject;
  source: string;
  time: Date;
}

export type EdgeCrashEvent = {
  error: mixed;
  metadata: JsonObject;
  source: string;
  time: Date;
}

/**
 * Receives crash reports.
 * The app should implement this interface and pass it to the context.
 */
export type EdgeCrashReporter = {
  +logBreadcrumb: (breadcrumb: EdgeBreadcrumbEvent) => void;
  +logCrash: (crash: EdgeCrashEvent) => void;
}

/**
 * Receives log messages.
 * The app should implement this function and pass it to the context.
 */
export type EdgeOnLog = (event: EdgeLogEvent) => void;

// plugins -------------------------------------------------------------

/**
 * On React Native, each plugin can provide a bridge to whatever native
 * io it needs.
 */
export type EdgeNativeIo = {
  [packageName: string]: EdgeOtherMethods;
}

/**
 * All core plugins receive these options at creation time.
 */
export type EdgeCorePluginOptions = {
  /** Load-time options (like API keys) passed into the context */
  initOptions: JsonObject;

  /** Data provided by the info server */
  infoPayload: JsonObject;

  // Access to the world outside the plugin:
  io: EdgeIo;
  log: EdgeLog; // Plugin-scoped logging
  nativeIo: EdgeNativeIo; // Only filled in on React Native
  pluginDisklet: Disklet; // Plugin-scoped local storage
}

// ---------------------------------------------------------------------
// key types
// ---------------------------------------------------------------------

export type EdgeWalletInfo = {
  id: string;
  type: string;
  keys: JsonObject;
}

export type EdgeWalletInfoFull = EdgeWalletInfo & {
  appIds: string[];
  archived: boolean;
  created?: Date;
  deleted: boolean;
  hidden: boolean;
  imported?: boolean;
  migratedFromWalletId?: string;
  sortIndex: number;
};

export type EdgeWalletState = {
  archived?: boolean;
  deleted?: boolean;
  hidden?: boolean;
  migratedFromWalletId?: string;
  sortIndex?: number;
}

export type EdgeWalletStates = {
  [walletId: string]: EdgeWalletState;
}

// ---------------------------------------------------------------------
// currency types
// ---------------------------------------------------------------------

/**
 * Different currencies support different types of on-chain memos,
 * so this structure describes the options that are available,
 * along with the applicable limits.
 */
export type EdgeMemoOption =
  | {
      type: "text";
      hidden?: boolean;
      memoName?: string;

      /**  Maximum number of text characters */
      maxLength?: number;
    }
  | {
      type: "number";
      hidden?: boolean;
      memoName?: string;

      /**
       * Maximum numerical value.
       * Numbers are passed as decimal strings.
       */
      maxValue?: string;
    }
  | {
      type: "hex";
      hidden?: boolean;
      memoName?: string;

      /** Number of hexadecimal bytes. */
      maxBytes?: number;
      minBytes?: number;
    };

export type EdgeMemo = {
  type: "text" | "number" | "hex";
  value: string;

  /** Should we hide this from the user, such as for OP_RETURN? */
  hidden?: boolean;

  /** What does the chain call this? Defaults to "memo". */
  memoName?: string;
}

export type EdgeAssetAmount = {
  pluginId: string;
  tokenId: EdgeTokenId;
  nativeAmount?: string;
}

export type EdgeFiatAmount = {
  // core-js style fiat code including 'iso:'
  fiatCurrencyCode: string;
  fiatAmount: string;
}

export type EdgeTxActionSwap = {
  actionType: "swap";
  swapInfo: EdgeSwapInfo;
  orderId?: string;
  orderUri?: string;
  isEstimate?: boolean;
  canBePartial?: boolean;
  fromAsset: EdgeAssetAmount;
  toAsset: EdgeAssetAmount;
  payoutAddress: string;
  payoutWalletId: string;
  refundAddress?: string;
}

export type EdgeTxActionStake = {
  actionType: "stake";
  pluginId: string;
  stakeAssets: EdgeAssetAmount[];
}

export type EdgeTxActionFiat = {
  actionType: "fiat";

  orderId: string;
  orderUri?: string;
  isEstimate: boolean;

  fiatPlugin: {
    providerId: string;
    providerDisplayName: string;
    supportEmail?: string;
  };

  payinAddress?: string;
  payoutAddress?: string;
  fiatAsset: EdgeFiatAmount;
  cryptoAsset: EdgeAssetAmount;
}

export type EdgeTxActionTokenApproval = {
  actionType: "tokenApproval";
  tokenApproved: EdgeAssetAmount;
  tokenContractAddress: string;
  contractAddress: string;
}

export type EdgeTxAction =
  | EdgeTxActionSwap
  | EdgeTxActionStake
  | EdgeTxActionFiat
  | EdgeTxActionTokenApproval;

export type EdgeTxAmount = {
  tokenId: EdgeTokenId;
  nativeAmount: string;
}

export type EdgeAssetActionType =
  | "claim"
  | "claimOrder"
  | "stake"
  | "stakeNetworkFee"
  | "stakeOrder"
  | "unstake"
  | "unstakeNetworkFee"
  | "unstakeOrder"
  | "swap"
  | "swapNetworkFee"
  | "swapOrderPost"
  | "swapOrderFill"
  | "swapOrderCancel"
  | "buy"
  | "sell"
  | "sellNetworkFee"
  | "tokenApproval"
  | "transfer"
  | "transferNetworkFee";

export type EdgeAssetAction = {
  assetActionType: EdgeAssetActionType;
}

// token info ----------------------------------------------------------

export type EdgeDenomination = {
  // Multiply a display amount by this number to get the native amount.
  // BTC would use "100000000", for instance:
  multiplier: string;

  // A display name for this denomination, like "BTC", "bits", or "sats":
  name: string;

  // A prefix to add to the formatted number, like "₿", "ƀ", or "s":
  symbol?: string;
}

/**
 * Information used to display a token or currency to the user.
 */
export type EdgeToken = {
  // The short code used on exchanges, such as "BTC":
  currencyCode: string;

  // How many decimal places to shift the native amount.
  // The first item in this array is always the default for exchanges:
  denominations: EdgeDenomination[];

  // The full marketing name, such as "Bitcoin":
  displayName: string;

  // Each currency plugin decides what this contains,
  // such as a contract address.
  // This may be `undefined` for special built-in tokens
  // such as staking balances.
  networkLocation: JsonObject | void;
}

/**
 * A normal tokenId (chosen by the currency plugin),
 * or `null` to indicate the parent currency (such as "ETH").
 */
export type EdgeTokenId = string | null;

export type EdgeTokenMap = {
  // Each currency plugin decides how to generate this ID,
  // such as by using the contract address:
  [tokenId: string]: EdgeToken;
}

/**
 * Available tokens stored in the `EdgeCurrencyInfo`,
 * or parsed out of URI's.
 */
export type EdgeMetaToken = {
  currencyCode: string;
  currencyName: string;
  contractAddress?: string;

  denominations: EdgeDenomination[];
  symbolImage?: string;
}

/**
 * Tokens passed to `addCustomToken`.
 */
export type EdgeTokenInfo = {
  currencyCode: string;
  currencyName: string;
  contractAddress: string;

  multiplier: string;
}

// currency info -------------------------------------------------------

export type EdgeObjectTemplate = Array<
  | {
      // Displayed as a decimal number, but saved as an integer string.
      // The multiplier gives the position of the display decimal point.
      // This is only used for custom fees.
      // It is *not* supported for custom tokens:
      type: "nativeAmount";
      key: string;
      displayName: string;
      displayMultiplier: string;
    }
  | {
      // An integer, saved as a JavaScript `number` type:
      type: "number";
      key: string;
      displayName: string;
    }
  | {
      type: "string";
      key: string;
      displayName: string;
    }
>;

export type EdgeCurrencyInfo = {
  // Basic currency information:
  +pluginId: string;

  /** deprecated: refers to chain, use chainDisplayName instead */
  displayName: string;

  chainDisplayName: string;
  assetDisplayName: string;
  walletType: string;

  // Native token information:
  currencyCode: string;
  denominations: EdgeDenomination[];

  // Chain information:
  canAdjustFees?: boolean; // Defaults to true
  canImportKeys?: boolean; // Defaults to false
  canReplaceByFee?: boolean; // Defaults to false
  customFeeTemplate?: EdgeObjectTemplate; // Indicates custom fee support
  customTokenTemplate?: EdgeObjectTemplate; // Indicates custom token support
  requiredConfirmations?: number; // Block confirmations required for a tx

  /**
   * Lists the types of memos this chain supports.
   * A missing or empty list means no memo support.
   */
  memoOptions?: EdgeMemoOption[];

  /** True if the transaction can have multiple memos at once: */
  multipleMemos?: boolean;

  // Explorers:
  addressExplorer: string;
  blockExplorer?: string;
  transactionExplorer: string;
  xpubExplorer?: string;

  // Flags:
  unsafeBroadcastTx?: boolean;
  unsafeMakeSpend?: boolean;
  unsafeSyncNetwork?: boolean;

  /** @deprecated The default user settings are always `{}` */
  defaultSettings?: JsonObject;

  /** @deprecated Use EdgeCurrencyPlugin.getBuiltinTokens instead */
  metaTokens?: EdgeMetaToken[];

  /** @deprecated Use memoOptions instead. */
  memoMaxLength?: number; // Max number of text characters, if supported

  /** @deprecated Use memoOptions instead. */
  memoMaxValue?: string; // Max numerical value, if supported

  /** @deprecated Use memoOptions instead. */
  memoType?: "text" | "number" | "hex" | "other"; // undefined means no memo support
}

// spending ------------------------------------------------------------

export type EdgeMetadata = {
  bizId?: number;
  category?: string;
  exchangeAmount?: { [fiatCurrencyCode: string]: number };
  name?: string;
  notes?: string;
}

/**
 * Like EdgeMetadata, but passing `null` will delete a saved value,
 * while passing `undefined` will leave the value unchanged.
 */
export type EdgeMetadataChange = {
  bizId?: number | null;
  category?: string | null;
  exchangeAmount?: { [fiatCurrencyCode: string]: number | null };
  name?: string | null;
  notes?: string | null;
}

export type EdgeTxSwap = {
  orderId?: string;
  orderUri?: string;
  isEstimate: boolean;

  // The EdgeSwapInfo from the swap plugin:
  plugin: {
    pluginId: string;
    displayName: string;
    supportEmail?: string;
  };

  // Address information:
  payoutAddress: string;
  payoutCurrencyCode: string;
  payoutNativeAmount: string;
  payoutWalletId: string;
  refundAddress?: string;
}

export type EdgeConfirmationState =
  // More than `EdgeCurrencyInfo.requiredConfirmations`:
  | "confirmed"
  // Dropped from the network without confirmations:
  | "dropped"
  // Confirmed, but failed on-chain execution (exceeded gas limit,
  // smart-contract failure, etc):
  | "failed"
  // We don't know the chain height yet:
  | "syncing"
  // No confirmations yet:
  | "unconfirmed"
  // Something between 1 and `requiredConfirmations`.
  // Currency engines can always return a number,
  // and the core will translate it into one of the other states:
  | number;

export type EdgeTransaction = {
  /**
   * The asset used to query this transaction.
   * The amounts and metadata will reflect the chosen asset.
   */
  tokenId: EdgeTokenId;

  // Amounts:
  nativeAmount: string;
  networkFees: EdgeTxAmount[];

  // Confirmation status:
  confirmations?: EdgeConfirmationState;
  blockHeight: number;
  date: number;

  // Transaction info:
  txid: string;
  signedTx: string;
  memos: EdgeMemo[];
  ourReceiveAddresses: string[];

  /** App-provided per-asset action data */
  assetAction?: EdgeAssetAction;

  /** Plugin-provided action data for all assets in a transaction */
  chainAction?: EdgeTxAction;

  /** Plugin-provided per-asset action data */
  chainAssetAction?: EdgeAssetAction;

  /** App-provided action data for all assets in a transaction */
  savedAction?: EdgeTxAction;

  /** This has the same format as the `customNetworkFee` */
  feeRateUsed?: JsonObject;

  // Spend-specific metadata:
  deviceDescription?: string;
  networkFeeOption?: "high" | "standard" | "low" | "custom";
  requestedCustomFee?: JsonObject;
  spendTargets?: Array<{
    +currencyCode: string; // Saved for future reference
    +nativeAmount: string;
    +publicAddress: string;

    /** @deprecated Use `EdgeTransaction.memos` instead */
    +memo: string | void;

    /** @deprecated Use `EdgeTransaction.memos` instead */
    +uniqueIdentifier: string | void;
  }>;
  swapData?: EdgeTxSwap;
  txSecret?: string; // Monero decryption key

  /**
   * True if the user themselves signed & sent this transaction.
   * This will not be true for transactions created by other users,
   * smart contracts, assets becoming unstaked, or anything else automatic.
   * A send doesn't necessarily spend money, although it often does.
   */
  isSend: boolean;

  // Core:
  metadata?: EdgeMetadata;
  walletId: string;
  otherParams?: JsonObject;

  /** @deprecated Use tokenId instead */
  currencyCode: string;

  /** @deprecated Use networkFees instead */
  networkFee: string;

  /** @deprecated Use networkFees instead */
  parentNetworkFee?: string;
}

export type EdgeTransactionEvent = {
  isNew: boolean;
  transaction: EdgeTransaction;
}

export type EdgeSpendTarget = {
  nativeAmount?: string;
  otherParams?: JsonObject;
  publicAddress?: string;

  /** @deprecated. Use `EdgeSpendInfo.memos` instead. */
  memo?: string;

  /** @deprecated. Use `EdgeSpendInfo.memos` instead. */
  uniqueIdentifier?: string; // Use memo instead.
}

export type EdgePaymentProtocolInfo = {
  domain: string;
  memo: string;
  merchant: string;
  nativeAmount: string;
  spendTargets: EdgeSpendTarget[];
}

export type EdgeSpendInfo = {
  // Basic information:
  tokenId: EdgeTokenId;
  privateKeys?: string[];
  spendTargets: EdgeSpendTarget[];
  memos?: EdgeMemo[];

  // Options:
  noUnconfirmed?: boolean;
  networkFeeOption?: "high" | "standard" | "low" | "custom";
  customNetworkFee?: JsonObject; // Some kind of currency-specific JSON
  /** Enables RBF on chains where RBF is optional */
  enableRbf?: boolean;
  pendingTxs?: EdgeTransaction[];
  /** @deprecated Use EdgeCurrencyWallet.accelerate instead */
  rbfTxid?: string;
  skipChecks?: boolean;

  // Core:
  assetAction?: EdgeAssetAction;
  savedAction?: EdgeTxAction;
  metadata?: EdgeMetadata;
  swapData?: EdgeTxSwap;
  otherParams?: JsonObject;
}

// query data ----------------------------------------------------------

export type EdgeDataDump = {
  walletId: string;
  walletType: string;
  data: {
    [dataCache: string]: JsonObject;
  };
}

/**
 * Addresses may appear in any order that the plugin chooses,
 * based on how the user should see them. If you need a specific address type,
 * use the `addressType` to find what you need.
 */
export type EdgeAddress = {
  addressType: string; // 'publicAddress' | 'segwitAddress' | 'legacyAddress' | 'fooAddress'
  publicAddress: string;
  nativeBalance?: string;
}

/** @deprecated */
export type EdgeFreshAddress = {
  publicAddress: string;
  segwitAddress?: string;
  legacyAddress?: string;
  nativeBalance?: string;
  segwitNativeBalance?: string;
  legacyNativeBalance?: string;
}

/**
 * Balances, unlock dates, and other information about staked funds.
 *
 * The currency engine is responsible for keeping this up to date.
 * For instance, if the user submits a transaction to unlock funds,
 * the engine should update this data once it detects that transaction,
 * and then again once the funds actually unlock some time later.
 *
 * As with wallet balances, this data may not be reliable until the
 * `syncRatio` hits 1.
 */
export type EdgeStakingStatus = {
  // Funds can be in various stages of being locked or unlocked,
  // so each row can describe a single batch of locked coins.
  // Adding together the rows in this array should give the
  // total amount of locked-up funds in the wallet:
  stakedAmounts: Array<{
    nativeAmount: string;

    // Maybe these funds are not the chain's parent currency:
    // tokenId?: string,

    // Maybe these funds are being unlocked?
    unlockDate?: Date;

    // Feel free to add other weird coin states here.
    // We can standardize them later if they are common:
    otherParams?: JsonObject;
  }>;
}

export type EdgeTxidMap = {
  [txid: string]: number;
}

// URI -----------------------------------------------------------------

export type WalletConnect = {
  uri: string;
  topic: string;
  version?: string;
  bridge?: string;
  key?: string;
}

export type EdgeParsedUri = {
  bitIDCallbackUri?: string;
  bitIDDomain?: string;
  bitidKycProvider?: string; // Experimental
  bitidKycRequest?: string; // Experimental
  bitidPaymentAddress?: string; // Experimental
  bitIDURI?: string;
  expireDate?: Date;
  legacyAddress?: string;
  metadata?: EdgeMetadata;
  minNativeAmount?: string;
  nativeAmount?: string;
  paymentProtocolUrl?: string;
  privateKeys?: string[];
  publicAddress?: string;
  returnUri?: string;
  segwitAddress?: string;
  token?: EdgeMetaToken;
  tokenId?: EdgeTokenId;
  uniqueIdentifier?: string; // Ripple payment id
  walletConnect?: WalletConnect;

  /** @deprecated Use tokenId instead */
  currencyCode?: string;
}

export type EdgeEncodeUri = {
  publicAddress: string;
  nativeAmount?: string;
  label?: string;
  message?: string;
  currencyCode?: string;
}

// options -------------------------------------------------------------

export type EdgeTokenIdOptions = {
  tokenId: EdgeTokenId;
}

export type EdgeGetTransactionsOptions = {
  // Filtering:
  startDate?: Date;
  endDate?: Date;
  searchString?: string;
  spamThreshold?: string;
  tokenId: EdgeTokenId;
}

export type EdgeStreamTransactionOptions = {
  /**
   * The number of entries to return in each batch.
   * Defaults to something reasonable, like 10.
   */
  batchSize?: number;

  /**
   * The number entries to return on the first batch.
   * Defaults to `batchSize`.
   */
  firstBatchSize?: number;

  /** Only return transactions newer than this date */
  afterDate?: Date;

  /** Only return transactions older than this date */
  beforeDate?: Date;

  /** Only return transactions matching this string */
  searchString?: string;

  /** Filter incoming transactions with a `nativeAmount` below this */
  spamThreshold?: string;

  /** The token to query, or undefined for the main currency */
  tokenId: EdgeTokenId;
}

export type EdgeGetReceiveAddressOptions = EdgeTokenIdOptions & {
  forceIndex?: number;
};

export type EdgeEngineActivationOptions = {
  // If null, activate parent wallet:
  activateTokenIds: EdgeTokenId[];

  // Wallet if the user is paying with a different currency:
  paymentInfo?: {
    wallet: EdgeCurrencyWallet;
    tokenId: EdgeTokenId;
  };
}

export type EdgeEngineGetActivationAssetsOptions = {
  // All wallets in the users account. This allows the engine to choose
  // which wallets can fulfill this activation request
  currencyWallets: { [walletId: string]: EdgeCurrencyWallet };

  // If null, activate parent wallet:
  activateTokenIds: EdgeTokenId[];
}

export type EdgeEnginePrivateKeyOptions = {
  privateKeys?: JsonObject;
}

export type EdgeEngineSyncNetworkOptions = {
  subscribeParam?: {
    address: string;
    /**
     * The checkpoint from the change-server. Mempool transactions are an
     * example of updates without checkpoints.
     * */
    checkpoint?: string;
  };
  privateKeys?: JsonObject;
}

export type EdgeSaveTxActionOptions = {
  txid: string;
  tokenId: EdgeTokenId;
  assetAction: EdgeAssetAction;
  savedAction: EdgeTxAction;
}

export type EdgeSaveTxMetadataOptions = {
  txid: string;
  tokenId: EdgeTokenId;
  metadata: EdgeMetadataChange;
}

export type EdgeSignMessageOptions = {
  otherParams?: JsonObject;
}

// engine --------------------------------------------------------------

export type EdgeCurrencyEngineCallbacks = {
  +onAddressChanged: () => void;
  +onAddressesChecked: (progressRatio: number) => void;
  +onNewTokens: (tokenIds: string[]) => void;
  +onSeenTxCheckpoint: (checkpoint: string) => void;
  +onStakingStatusChanged: (status: EdgeStakingStatus) => void;
  +onSubscribeAddresses: (addresses: string[]) => void;
  +onTokenBalanceChanged: (
    tokenId: EdgeTokenId,
    balance: string,
  ) => void;
  +onTransactions: (transactionEvents: EdgeTransactionEvent[]) => void;
  +onTxidsChanged: (txids: EdgeTxidMap) => void;
  +onUnactivatedTokenIdsChanged: (
    unactivatedTokenIds: string[],
  ) => void;
  +onWcNewContractCall: (payload: JsonObject) => void;

  /** @deprecated onTransactionsChanged handles confirmation changes */
  +onBlockHeightChanged: (blockHeight: number) => void;

  /** @deprecated Use onTokenBalanceChanged instead */
  +onBalanceChanged: (
    currencyCode: string,
    nativeBalance: string,
  ) => void;

  /** @deprecated Use onTransactions */
  +onTransactionsChanged: (transactions: EdgeTransaction[]) => void;
}

export type EdgeCurrencyEngineOptions = {
  callbacks: EdgeCurrencyEngineCallbacks;

  // Engine state kept by the core:
  seenTxCheckpoint?: string;

  /** True if we only need a balance and the ability to spend it. */
  lightMode?: boolean;

  // Wallet-scoped IO objects:
  log: EdgeLog;
  walletLocalDisklet: Disklet;
  walletLocalEncryptedDisklet: Disklet;

  // User settings:
  customTokens: EdgeTokenMap;
  enabledTokenIds: string[];
  userSettings: JsonObject | void;
}

export type EdgeCurrencyEngine = {
  +changeUserSettings: (settings: JsonObject) => Promise<void>;

  /**
   * Starts any persistent resources the engine needs, such as WebSockets.
   * Engines should use `syncNetwork` for periodic tasks (polling),
   * rather than trying to manage those by itself.
   */
  +startEngine: () => Promise<void>;

  /**
   * Shut down the engine, including open sockets, timers,
   * and any in-progress tasks that support cancellation.
   */
  +killEngine: () => Promise<void>;

  /**
   * Polls the blockchain for updates.
   * The return value is the delay (in ms) that the engine wants to wait
   * before its next poll. For engines with active address subscriptions,
   * the core will ignore this number and simply wait for the next
   * on-chain update.
   * Engines with `EdgeCurrencyInfo.unsafeSyncNetwork`
   * will receive their private keys in the arguments.
   */
  +syncNetwork?: (
    opts: EdgeEngineSyncNetworkOptions,
  ) => Promise<number>;

  // Engine status:
  +resyncBlockchain: () => Promise<void>;
  +dumpData: () => Promise<EdgeDataDump>;

  // Chain state:
  +getBlockHeight: () => number;
  +getBalance: (opts: EdgeTokenIdOptions) => string;
  +getNumTransactions: (opts: EdgeTokenIdOptions) => number;
  +getTransactions: (
    opts: EdgeTokenIdOptions,
  ) => Promise<EdgeTransaction[]>;
  +getTxids?: () => EdgeTxidMap;

  // Tokens:
  +changeCustomTokens?: (tokens: EdgeTokenMap) => Promise<void>;
  +changeEnabledTokenIds?: (tokenIds: string[]) => Promise<void>;

  // Asset activation:
  +engineGetActivationAssets?: (
    options: EdgeEngineGetActivationAssetsOptions,
  ) => Promise<EdgeGetActivationAssetsResults>;
  +engineActivateWallet?: (
    options: EdgeEngineActivationOptions,
  ) => Promise<EdgeActivationQuote>;

  // Addresses:
  +getAddresses?: (
    opts: EdgeGetReceiveAddressOptions,
  ) => Promise<EdgeAddress[]>;

  /** @deprecated */
  +getFreshAddress: (
    opts: EdgeGetReceiveAddressOptions,
  ) => Promise<EdgeFreshAddress>;
  +addGapLimitAddresses: (addresses: string[]) => Promise<void>;
  +isAddressUsed: (address: string) => Promise<boolean>;

  // Spending:
  +getMaxSpendable?: (
    spendInfo: EdgeSpendInfo,
    opts?: EdgeEnginePrivateKeyOptions,
  ) => Promise<string>;
  +makeSpend: (
    spendInfo: EdgeSpendInfo,
    opts?: EdgeEnginePrivateKeyOptions,
  ) => Promise<EdgeTransaction>;
  +signTx: (
    transaction: EdgeTransaction,
    privateKeys: JsonObject,
  ) => Promise<EdgeTransaction>;
  +broadcastTx: (
    transaction: EdgeTransaction,
    opts?: EdgeEnginePrivateKeyOptions,
  ) => Promise<EdgeTransaction>;
  +saveTx: (transaction: EdgeTransaction) => Promise<void>;
  +sweepPrivateKeys?: (
    spendInfo: EdgeSpendInfo,
  ) => Promise<EdgeTransaction>;
  +getPaymentProtocolInfo?: (
    paymentProtocolUrl: string,
  ) => Promise<EdgePaymentProtocolInfo>;

  // Signing:
  +signBytes?: (
    bytes: Uint8Array,
    privateKeys: JsonObject,
    opts: EdgeSignMessageOptions,
  ) => Promise<string>;

  // Accelerating:
  +accelerate?: (
    tx: EdgeTransaction,
  ) => Promise<EdgeTransaction | null>;

  // Staking:
  +getStakingStatus?: () => Promise<EdgeStakingStatus>;

  // Escape hatch:
  +otherMethods?: EdgeOtherMethods;
  +otherMethodsWithKeys?: EdgeOtherMethods;

  /** @deprecated Replaced by changeEnabledTokenIds */
  +enableTokens?: (tokens: string[]) => Promise<void>;

  /** @deprecated Replaced by changeEnabledTokenIds */
  +disableTokens?: (tokens: string[]) => Promise<void>;

  /** @deprecated Replaced by changeCustomTokens */
  +addCustomToken?: (token: EdgeTokenInfo & EdgeToken) => Promise<void>;

  /** @deprecated Provide EdgeCurrencyTools.getDisplayPrivateKey: */
  +getDisplayPrivateSeed?: (privateKeys: JsonObject) => string | null;

  /** @deprecated Provide EdgeCurrencyTools.getDisplayPublicKey: */
  +getDisplayPublicSeed?: () => string | null;

  /**
   * @deprecated Replaced by `signBytes`.
   * Various plugins implement this function with inconsistent encodings.
   */
  +signMessage?: (
    message: string,
    privateKeys: JsonObject,
    opts: EdgeSignMessageOptions,
  ) => Promise<string>;
}

// currency plugin -----------------------------------------------------

export type EdgeCurrencyTools = {
  // Keys:
  +checkPublicKey?: (publicKey: JsonObject) => Promise<boolean>;
  +createPrivateKey: (
    walletType: string,
    opts?: JsonObject,
  ) => Promise<JsonObject>;
  +derivePublicKey: (
    privateWalletInfo: EdgeWalletInfo,
  ) => Promise<JsonObject>;
  +getDisplayPrivateKey?: (
    privateWalletInfo: EdgeWalletInfo,
  ) => Promise<string>;
  +getDisplayPublicKey?: (
    publicWalletInfo: EdgeWalletInfo,
  ) => Promise<string>;
  +getDisplayPublicKeys?: (publicWalletInfo: EdgeWalletInfo) => {
    [key: string]: string;
  };
  +getSplittableTypes?: (
    publicWalletInfo: EdgeWalletInfo,
  ) => string[] | Promise<string[]>;
  +importPrivateKey?: (
    key: string,
    opts?: JsonObject,
  ) => Promise<JsonObject>;

  // Derives a tokenId string from a token's network information:
  +getTokenId?: (token: EdgeToken) => Promise<string>;

  // URIs:
  +parseUri: (
    uri: string,
    currencyCode?: string,
    customTokens?: EdgeMetaToken[],
  ) => Promise<EdgeParsedUri>;
  +encodeUri: (
    obj: EdgeEncodeUri,
    customTokens?: EdgeMetaToken[],
  ) => Promise<string>;
}

export type EdgeCurrencyPlugin = {
  +currencyInfo: EdgeCurrencyInfo;

  +getBuiltinTokens?: () => Promise<EdgeTokenMap>;
  +makeCurrencyTools: () => Promise<EdgeCurrencyTools>;
  +makeCurrencyEngine: (
    walletInfo: EdgeWalletInfo,
    opts: EdgeCurrencyEngineOptions,
  ) => Promise<EdgeCurrencyEngine>;
  +updateInfoPayload?: (infoPayload: JsonObject) => Promise<void>;

  // Escape hatch:
  +otherMethods?: EdgeOtherMethods;
}

// wallet --------------------------------------------------------------

export type EdgeBalances = {
  [currencyCode: string]: string;
}

export type EdgeBalanceMap = Map<EdgeTokenId, string>;

export type EdgeReceiveAddress = EdgeFreshAddress & {
  metadata: EdgeMetadata;
  nativeAmount: string;
};

export type EdgeCurrencyWalletEvents = {
  addressChanged: void;
  close: void;
  enabledDetectedTokens: string[];
  newTransactions: EdgeTransaction[];
  transactionsChanged: EdgeTransaction[];
  wcNewContractCall: JsonObject;
}

export type EdgeGetActivationAssetsOptions = {
  activateWalletId: string;
  // If null, activate parent wallet:
  activateTokenIds: EdgeTokenId[];
}

export type EdgeGetActivationAssetsResults = {
  assetOptions: Array<{
    paymentWalletId?: string; // If walletId is present, use MUST activate with this wallet
    currencyPluginId: string;
    tokenId: EdgeTokenId;
  }>;
}

export type EdgeActivationOptions = {
  activateWalletId: string;
  // If null, activate parent wallet:
  activateTokenIds: EdgeTokenId[];

  // Wallet if the user is paying with a different currency:
  paymentInfo?: {
    walletId: string;
    tokenId: EdgeTokenId;
  };
}

export type EdgeActivationApproveOptions = {
  metadata?: EdgeMetadata;
}

export type EdgeActivationResult = {
  +transactions: EdgeTransaction[];
}

export type EdgeActivationQuote = {
  +paymentWalletId: string;
  +paymentTokenId: EdgeTokenId;

  +fromNativeAmount: string;
  +networkFee: EdgeTxAmount & {
    /** @deprecated use contextual APIs to get the currency's pluginId */
    +currencyPluginId: string;
  };

  +approve: (
    opts?: EdgeActivationApproveOptions,
  ) => Promise<EdgeActivationResult>;
  close: () => Promise<void>;
}

export type EdgeCurrencyWallet = {
  +on: Subscriber<EdgeCurrencyWalletEvents>;
  +watch: Subscriber<EdgeCurrencyWallet>;

  // Data store:
  +created: Date | void;
  +disklet: Disklet;
  +id: string;
  +localDisklet: Disklet;
  +publicWalletInfo: EdgeWalletInfo;
  +sync: () => Promise<void>;
  +type: string;

  // Wallet name:
  +name: string | null;
  +renameWallet: (name: string) => Promise<void>;

  // Fiat currency option:
  +fiatCurrencyCode: string;
  +setFiatCurrencyCode: (fiatCurrencyCode: string) => Promise<void>;

  // Currency info:
  +currencyConfig: EdgeCurrencyConfig; // eslint-disable-line no-use-before-define
  +currencyInfo: EdgeCurrencyInfo;

  // Chain state:
  +balanceMap: EdgeBalanceMap;
  +balances: EdgeBalances;
  +blockHeight: number;
  +syncRatio: number;
  +unactivatedTokenIds: string[];

  // Running state:
  +changePaused: (paused: boolean) => Promise<void>;
  +paused: boolean;

  // Token management:
  // Available tokens can be found in `EdgeCurrencyConfig`.
  // This list is allowed to include missing or deleted `tokenIds`:
  +changeEnabledTokenIds: (tokenIds: string[]) => Promise<void>;
  +enabledTokenIds: string[];

  /* Tokens detected on chain */
  +detectedTokenIds: string[];

  // Transaction history:
  +getNumTransactions: (opts: EdgeTokenIdOptions) => Promise<number>;
  +getTransactions: (
    opts: EdgeGetTransactionsOptions,
  ) => Promise<EdgeTransaction[]>;
  +streamTransactions: (
    opts: EdgeStreamTransactionOptions,
  ) => AsyncGenerator<EdgeTransaction[]>;

  // Addresses:
  +getAddresses: (
    opts: EdgeGetReceiveAddressOptions,
  ) => Promise<EdgeAddress[]>;

  // Sending:
  +broadcastTx: (tx: EdgeTransaction) => Promise<EdgeTransaction>;
  +getMaxSpendable: (spendInfo: EdgeSpendInfo) => Promise<string>;
  +getPaymentProtocolInfo: (
    paymentProtocolUrl: string,
  ) => Promise<EdgePaymentProtocolInfo>;
  +makeSpend: (spendInfo: EdgeSpendInfo) => Promise<EdgeTransaction>;
  +saveTx: (tx: EdgeTransaction) => Promise<void>;
  +saveTxAction: (opts: EdgeSaveTxActionOptions) => Promise<void>;
  +saveTxMetadata: (opts: EdgeSaveTxMetadataOptions) => Promise<void>;
  +signTx: (tx: EdgeTransaction) => Promise<EdgeTransaction>;
  +sweepPrivateKeys: (
    edgeSpendInfo: EdgeSpendInfo,
  ) => Promise<EdgeTransaction>;

  // Signing:
  +signBytes: (
    buf: Uint8Array,
    opts?: EdgeSignMessageOptions,
  ) => Promise<string>;

  // Accelerating:
  +accelerate: (tx: EdgeTransaction) => Promise<EdgeTransaction | null>;

  // Staking:
  +stakingStatus: EdgeStakingStatus;

  // Wallet management:
  +dumpData: () => Promise<EdgeDataDump>;
  +resyncBlockchain: () => Promise<void>;

  // URI handling:
  +encodeUri: (obj: EdgeEncodeUri) => Promise<string>;
  +parseUri: (
    uri: string,
    currencyCode?: string,
  ) => Promise<EdgeParsedUri>;

  // Generic:
  +otherMethods: EdgeOtherMethods;

  /** @deprecated Use the information in EdgeCurrencyInfo / EdgeToken. */
  +denominationToNative: (
    denominatedAmount: string,
    currencyCode: string,
  ) => Promise<string>;

  /** @deprecated Use the information in EdgeCurrencyInfo / EdgeToken. */
  +nativeToDenomination: (
    nativeAmount: string,
    currencyCode: string,
  ) => Promise<string>;

  /** @deprecated Use `EdgeCurrencyWallet.getAddresses` instead */
  +getReceiveAddress: (
    opts: EdgeGetReceiveAddressOptions,
  ) => Promise<EdgeReceiveAddress>;

  /** @deprecated This was never implemented. */
  +lockReceiveAddress: (
    receiveAddress: EdgeReceiveAddress,
  ) => Promise<void>;

  /** @deprecated This was never implemented. */
  +saveReceiveAddress: (
    receiveAddress: EdgeReceiveAddress,
  ) => Promise<void>;

  /** @deprecated Use `signBytes` instead. */
  +signMessage: (
    message: string,
    opts?: EdgeSignMessageOptions,
  ) => Promise<string>;
}

export type EdgeMemoryWallet = {
  +watch: Subscriber<EdgeMemoryWallet>;
  +balanceMap: EdgeBalanceMap;
  +detectedTokenIds: string[];
  +syncRatio: number;
  +changeEnabledTokenIds: (tokenIds: string[]) => Promise<void>;
  +startEngine: () => Promise<void>;
  +getMaxSpendable: (spendInfo: EdgeSpendInfo) => Promise<string>;
  +makeSpend: (spendInfo: EdgeSpendInfo) => Promise<EdgeTransaction>;
  +signTx: (tx: EdgeTransaction) => Promise<EdgeTransaction>;
  +broadcastTx: (tx: EdgeTransaction) => Promise<EdgeTransaction>;
  +saveTx: (tx: EdgeTransaction) => Promise<void>;
  +close: () => Promise<void>;
}

// ---------------------------------------------------------------------
// swap plugin
// ---------------------------------------------------------------------

export type EdgeSwapPluginType = "DEX" | "CEX";

/**
 * Static data about a swap plugin.
 */
export type EdgeSwapInfo = {
  +pluginId: string;
  +displayName: string;
  +isDex?: boolean;

  /** @deprecated Use orderUri in EdgeTxAction */
  +orderUri?: string; // The orderId would be appended to this
  +supportEmail: string;
}

export type EdgeSwapRequest = {
  // Where?
  fromWallet: EdgeCurrencyWallet;
  toWallet: EdgeCurrencyWallet;

  // What?
  fromTokenId: EdgeTokenId;
  toTokenId: EdgeTokenId;

  // How much?
  nativeAmount: string;
  quoteFor: "from" | "max" | "to";
}

/**
 * If the user approves a quote, the plugin performs the transaction
 * and returns this as the result.
 */
export type EdgeSwapResult = {
  +orderId?: string;
  +destinationAddress?: string;
  +transaction: EdgeTransaction;
}

export type EdgeSwapApproveOptions = {
  metadata?: EdgeMetadata;
  savedAction?: EdgeTxAction;
}

/**
 * If a provider can satisfy a request, what is their price?
 */
export type EdgeSwapQuote = {
  +swapInfo: EdgeSwapInfo;
  +request: EdgeSwapRequest;

  +isEstimate: boolean;

  /**
   * This quote may be partially fulfilled with remaining source funds left
   * in wallet
   */
  +canBePartial?: boolean;

  /**
   * Maximum amount of time this quote will take to be fulfilled (in seconds)
   */
  +maxFulfillmentSeconds?: number;

  /** Worst-case receive amount */
  +minReceiveAmount?: string;

  +fromNativeAmount: string;
  +toNativeAmount: string;
  +networkFee: EdgeTxAmount & {
    /** @deprecated use tokenId */
    currencyCode: string;
  };

  +pluginId: string;
  +expirationDate?: Date;

  +approve: (opts?: EdgeSwapApproveOptions) => Promise<EdgeSwapResult>;
  +close: () => Promise<void>;
}

export type EdgeSwapPluginStatus = {
  needsActivation?: boolean;
}

export type EdgeSwapPlugin = {
  +swapInfo: EdgeSwapInfo;

  +checkSettings?: (userSettings: JsonObject) => EdgeSwapPluginStatus;
  +fetchSwapQuote: (
    request: EdgeSwapRequest,
    userSettings: JsonObject | void,
    opts: { infoPayload: JsonObject; promoCode?: string },
  ) => Promise<EdgeSwapQuote>;
}

// ---------------------------------------------------------------------
// account
// ---------------------------------------------------------------------

export type EdgeAccountOptions = {
  /**
   * If the login server returns a ChallengeError,
   * the user needs to visit the challenge URL and answer the question.
   * If the user succeeds, the challengeId will allow them to log in:
   */
  challengeId?: string;

  /**
   * The current time, if different from `new Date()`.
   * Useful for unit testing.
   */
  now?: Date;

  /** The user's OTP secret. */
  otpKey?: string;

  /** A 6-digit OTP derived from the OTP secret. */
  otp?: string;

  /** True to start wallets in the paused state. */
  pauseWallets?: boolean;
}

/**
 * A pending request to log in from a new device.
 */
export type EdgePendingVoucher = {
  voucherId: string;
  activates: Date;
  created: Date;
  deviceDescription?: string;
  ip: string;
  ipDescription: string;
}

// credentials ---------------------------------------------------------

export type ChangePinOptions = {
  /** Keeps the existing PIN if unspecified */
  pin?: string;

  /** Defaults to true if unspecified */
  enableLogin?: boolean;

  /**
   * True to configure the pin for the duress account.
   * Default is false.
   */
  forDuressAccount?: boolean;
}

export type ChangeUsernameOptions = {
  username: string;

  /**
   * Changing the username requires passing the password, if present.
   * If the account has no password, providing this will create one.
   */
  password?: string;
}

// currencies ----------------------------------------------------------

export type EdgeCreateCurrencyWalletOptions = {
  enabledTokenIds?: string[];
  fiatCurrencyCode?: string;
  name?: string;

  // Create a private key from some text:
  importText?: string;

  // Used to tell the currency plugin what keys to create:
  keyOptions?: JsonObject;

  // Used to copy wallet keys between accounts:
  keys?: JsonObject;

  // Set if we are sweeping one wallet into another:
  migratedFromWalletId?: string;
}

export type EdgeCreateCurrencyWallet = EdgeCreateCurrencyWalletOptions & {
  walletType: string;
};

export type EdgeCurrencyConfig = {
  +watch: Subscriber<EdgeCurrencyConfig>;

  +currencyInfo: EdgeCurrencyInfo;

  // Tokens:
  +allTokens: EdgeTokenMap;
  +builtinTokens: EdgeTokenMap;
  +customTokens: EdgeTokenMap;
  +getTokenId: (token: EdgeToken) => Promise<string>;
  +addCustomToken: (token: EdgeToken) => Promise<string>;
  +changeCustomToken: (
    tokenId: string,
    token: EdgeToken,
  ) => Promise<void>;
  +removeCustomToken: (tokenId: string) => Promise<void>;

  // Always-enabled tokens:
  +alwaysEnabledTokenIds: string[];
  +changeAlwaysEnabledTokenIds: (tokenIds: string[]) => Promise<void>;

  // User settings for this plugin:
  +userSettings: JsonObject | void;
  +changeUserSettings: (settings: JsonObject) => Promise<void>;

  // Utility methods:
  +importKey: (
    userInput: string,
    opts?: { keyOptions?: JsonObject },
  ) => Promise<JsonObject>;
  +otherMethods: EdgeOtherMethods;
}

// swap ----------------------------------------------------------------

/**
 * Information and settings for a currency swap plugin.
 */
export type EdgeSwapConfig = {
  +watch: Subscriber<EdgeSwapConfig>;

  +enabled: boolean;
  +needsActivation: boolean;
  +swapInfo: EdgeSwapInfo;
  +userSettings: JsonObject | void;

  +changeEnabled: (enabled: boolean) => Promise<void>;
  +changeUserSettings: (settings: JsonObject) => Promise<void>;
}

export type EdgeSwapRequestOptions = {
  preferPluginId?: string;
  preferType?: EdgeSwapPluginType;
  disabled?: EdgePluginMap<true>;
  promoCodes?: EdgePluginMap<string>;

  /**
   * If we have some quotes already, how long should we wait
   * for stragglers before we give up? Defaults to 20000ms.
   */
  slowResponseMs?: number;

  /**
   * If don't have any quotes yet, how long should we wait
   * before we give up? Defaults to Infinity.
   */
  noResponseMs?: number;
}

// edge login ----------------------------------------------------------

export type EdgeLoginRequest = {
  +appId: string;

  +displayName: string;
  +displayImageDarkUrl?: string;
  +displayImageLightUrl?: string;

  +approve: () => Promise<void>;
  +close: () => Promise<void>;
}

export type EdgeLobby = {
  +loginRequest: EdgeLoginRequest | void;
  // walletRequest: EdgeWalletRequest | void
}

// storage -------------------------------------------------------------

export type EdgeDataStore = {
  +deleteItem: (storeId: string, itemId: string) => Promise<void>;
  +deleteStore: (storeId: string) => Promise<void>;

  +listItemIds: (storeId: string) => Promise<string[]>;
  +listStoreIds: () => Promise<string[]>;

  +getItem: (storeId: string, itemId: string) => Promise<string>;
  +setItem: (
    storeId: string,
    itemId: string,
    value: string,
  ) => Promise<void>;
}

// account -------------------------------------------------------------

export type EdgeAccountEvents = {
  close: void;
}

export type EdgeAccount = {
  +on: Subscriber<EdgeAccountEvents>;
  +watch: Subscriber<EdgeAccount>;

  // Data store:
  +id: string;
  +type: string;
  +disklet: Disklet;
  +localDisklet: Disklet;
  +sync: () => Promise<void>;

  // Basic login information:
  +appId: string;
  +created: Date | void; // Not always known
  +lastLogin: Date;
  +loggedIn: boolean;
  +recoveryKey: string | void; // base58, for email backup
  +rootLoginId: string; // base58
  +username: string | void;

  // Duress mode:
  +canDuressLogin: boolean;
  +isDuressAccount: boolean;

  /** Gets the base58 decryption key for biometric login */
  +getLoginKey: () => Promise<string>; // base58

  // Special-purpose API's:
  +currencyConfig: EdgePluginMap<EdgeCurrencyConfig>;
  +swapConfig: EdgePluginMap<EdgeSwapConfig>;
  +dataStore: EdgeDataStore;

  // What login method was used?
  +edgeLogin: boolean;
  +keyLogin: boolean;
  +newAccount: boolean;
  +passwordLogin: boolean;
  +pinLogin: boolean;
  +recoveryLogin: boolean;

  // Change or create credentials:
  +changePassword: (password: string) => Promise<void>;
  +changePin: (opts: ChangePinOptions) => Promise<string>;
  +changeRecovery: (
    questions: string[],
    answers: string[],
  ) => Promise<string>;
  +changeUsername: (opts: ChangeUsernameOptions) => Promise<void>;

  // Verify existing credentials:
  +checkPassword: (password: string) => Promise<boolean>;
  +checkPin: (pin: string) => Promise<boolean>;
  +getPin: () => Promise<string | void>;

  // Remove credentials:
  +deletePassword: () => Promise<void>;
  +deletePin: () => Promise<void>;
  +deleteRecovery: () => Promise<void>;

  // OTP:
  +otpKey: string | void; // OTP is enabled if this exists
  +otpResetDate: Date | void; // A reset is requested if this exists
  +cancelOtpReset: () => Promise<void>;
  +disableOtp: () => Promise<void>;
  +enableOtp: (timeout?: number) => Promise<void>;
  +repairOtp: (otpKey: string) => Promise<void>;

  // 2fa bypass voucher approval / rejection:
  +pendingVouchers: EdgePendingVoucher[];
  +approveVoucher: (voucherId: string) => Promise<void>;
  +rejectVoucher: (voucherId: string) => Promise<void>;

  // Edge login approval:
  +fetchLobby: (lobbyId: string) => Promise<EdgeLobby>;

  // Login management:
  +deleteRemoteAccount: () => Promise<void>;
  +logout: () => Promise<void>;

  // Master wallet list:
  +allKeys: EdgeWalletInfoFull[];
  +changeWalletStates: (
    walletStates: EdgeWalletStates,
  ) => Promise<void>;
  +createWallet: (type: string, keys?: JsonObject) => Promise<string>;
  +getFirstWalletInfo: (type: string) => EdgeWalletInfoFull | void;
  +getWalletInfo: (id: string) => EdgeWalletInfoFull | void;
  +listWalletIds: () => string[];
  +listSplittableWalletTypes: (walletId: string) => Promise<string[]>;
  +splitWalletInfo: (
    walletId: string,
    newWalletType: string,
  ) => Promise<string>;

  // Key access:
  +getDisplayPrivateKey: (walletId: string) => Promise<string>;
  +getDisplayPublicKey: (walletId: string) => Promise<string>;
  +getRawPrivateKey: (walletId: string) => Promise<JsonObject>;
  +getRawPublicKey: (walletId: string) => Promise<JsonObject>;

  // Currency wallets:
  +activeWalletIds: string[];
  +archivedWalletIds: string[];
  +hiddenWalletIds: string[];
  +currencyWallets: { [walletId: string]: EdgeCurrencyWallet };
  +currencyWalletErrors: { [walletId: string]: Error };
  +createCurrencyWallet: (
    walletType: string,
    opts?: EdgeCreateCurrencyWalletOptions,
  ) => Promise<EdgeCurrencyWallet>;
  +createCurrencyWallets: (
    createWallets: EdgeCreateCurrencyWallet[],
  ) => Promise<Array<EdgeResult<EdgeCurrencyWallet>>>;
  +waitForCurrencyWallet: (
    walletId: string,
  ) => Promise<EdgeCurrencyWallet>;
  +waitForAllWallets: () => Promise<void>;
  +makeMemoryWallet: (
    walletType: string,
    opts?: EdgeCreateCurrencyWalletOptions,
  ) => Promise<EdgeMemoryWallet>;

  // Token & wallet activation:
  +getActivationAssets: (
    options: EdgeGetActivationAssetsOptions,
  ) => Promise<EdgeGetActivationAssetsResults>;
  +activateWallet: (
    options: EdgeActivationOptions,
  ) => Promise<EdgeActivationQuote>;

  // Swapping:
  +fetchSwapQuote: (
    request: EdgeSwapRequest,
    opts?: EdgeSwapRequestOptions,
  ) => Promise<EdgeSwapQuote>;
  +fetchSwapQuotes: (
    request: EdgeSwapRequest,
    opts?: EdgeSwapRequestOptions,
  ) => Promise<EdgeSwapQuote[]>;
}

// ---------------------------------------------------------------------
// context types
// ---------------------------------------------------------------------

export type EdgeCorePlugin = EdgeCurrencyPlugin | EdgeSwapPlugin;

export type EdgeCorePluginFactory = (
  env: EdgeCorePluginOptions,
) => EdgeCorePlugin;

export type EdgeCorePlugins = EdgePluginMap<
  EdgeCorePlugin | EdgeCorePluginFactory
>;

export type EdgeCorePluginsInit = EdgePluginMap<boolean | JsonObject>;

export type EdgeContextOptions = {
  apiKey?: string;
  apiSecret?: Uint8Array;
  appId: string;

  /**
   * Switches to a different login server, such as for testing.
   * Do not include `/api` in the path.
   */
  loginServer?: string | string[];

  changeServer?: string | string[];
  infoServer?: string | string[];
  syncServer?: string | string[];
  hideKeys?: boolean;

  // Intercepts crash reports:
  crashReporter?: EdgeCrashReporter;

  // A string to describe this phone or app:
  deviceDescription?: string;

  // Intercepts all console logging:
  onLog?: EdgeOnLog;
  logSettings?: $Rest<EdgeLogSettings, { ... }>;

  path?: string; // Only used on node.js
  plugins?: EdgeCorePluginsInit;

  /** True to load Airbitz user files from disk */
  airbitzSupport?: boolean;

  /**
   * True to skip updating the `EdgeCurrencyWallet.blockHeight` property.
   * This may improve performance by reducing bridge traffic,
   * but there will be no way to query the overall chain height.
   * The core will continue updating individual transactions
   * as their confirmation status changes.
   */
  skipBlockHeight?: boolean;

  /** @deprecated Use `loginServer` instead. */
  authServer?: string;
}

// parameters ----------------------------------------------------------

export type EdgeCreateAccountOptions = {
  username?: string;
  password?: string;
  pin?: string;
}

export type EdgeLoginMessage = {
  loginId: string; // base64
  otpResetPending: boolean;
  pendingVouchers: EdgePendingVoucher[];
  recovery2Corrupt: boolean;
  username?: string;
}

export type EdgePasswordRules = {
  secondsToCrack: number;
  tooShort: boolean;
  noNumber: boolean;
  noLowerCase: boolean;
  noUpperCase: boolean;
  passed: boolean;
}

/**
 * A barcode login request.
 *
 * The process begins by showing the user a QR code with the request id,
 * in the format `edge://edge/${id}`.
 *
 * Once the user sends their response, the state will move from "pending"
 * to "started" and the "username" property will hold the received username.
 *
 * Once the login finishes, the state will move from "started" to "done",
 * and the "account" property will hold the new account object.
 *
 * Otherwise, if something goes wrong, the state will move from "started"
 * to "error", and the "error" property will hold the error.
 *
 * Calling "cancelRequest" stops the process and sets the state to "closed".
 * This method is only callable in the "pending" and "started" states.
 *
 * Use the `watch('state', callback)` method to subscribe to state changes.
 */
export type EdgePendingEdgeLogin = {
  +watch: Subscriber<EdgePendingEdgeLogin>;
  +id: string;

  +state: "pending" | "started" | "done" | "error" | "closed";
  +username?: string; // Set in the "started" state
  +account?: EdgeAccount; // Set in the "done" state
  +error?: mixed; // Set in the "error" state

  +cancelRequest: () => Promise<void>;
}

export type EdgeUserInfo = {
  keyLoginEnabled: boolean;
  lastLogin?: Date;
  loginId: string; // base58
  pinLoginEnabled: boolean;
  recovery2Key?: string; // base58
  username?: string;
  voucherId?: string;
}

// context -------------------------------------------------------------

export type EdgeContextEvents = {
  close: void;
  error: any; // Will change to `mixed`
}

export type EdgeContext = {
  +on: Subscriber<EdgeContextEvents>;
  +watch: Subscriber<EdgeContext>;
  +close: () => Promise<void>;

  +appId: string;
  +clientId: string; // Unique base58 ID for each app installation

  // Local user management:
  localUsers: EdgeUserInfo[];
  +forgetAccount: (rootLoginId: string) => Promise<void>;

  // Account creation:
  /** Preemptively requests a CAPTCHA for account creation. */
  +fetchChallenge: () => Promise<{
    challengeId: string;
    /** If this is missing, the challenge is already solved. */
    challengeUri?: string;
  }>;
  +fixUsername: (username: string) => string;
  +usernameAvailable: (
    username: string,
    opts?: { challengeId?: string },
  ) => Promise<boolean>;
  +createAccount: (
    opts: EdgeCreateAccountOptions & EdgeAccountOptions,
  ) => Promise<EdgeAccount>;

  // Barcode login:
  +requestEdgeLogin: (
    opts?: EdgeAccountOptions,
  ) => Promise<EdgePendingEdgeLogin>;

  // Fingerprint login:
  +loginWithKey: (
    usernameOrLoginId: string,
    loginKey: string,
    opts?: EdgeAccountOptions & { useLoginId?: boolean },
  ) => Promise<EdgeAccount>;

  // Password login:
  +checkPasswordRules: (password: string) => EdgePasswordRules;
  +loginWithPassword: (
    username: string,
    password: string,
    opts?: EdgeAccountOptions,
  ) => Promise<EdgeAccount>;

  // PIN login:
  +loginWithPIN: (
    usernameOrLoginId: string,
    pin: string,
    opts?: EdgeAccountOptions & { useLoginId?: boolean },
  ) => Promise<EdgeAccount>;

  // Recovery2 login:
  +loginWithRecovery2: (
    recovery2Key: string,
    username: string,
    answers: string[],
    opts?: EdgeAccountOptions,
  ) => Promise<EdgeAccount>;
  +fetchRecovery2Questions: (
    recovery2Key: string,
    username: string,
  ) => Promise<string[]>;

  // OTP stuff:
  +requestOtpReset: (
    username: string,
    otpResetToken: string,
  ) => Promise<Date>;
  +fetchLoginMessages: () => Promise<EdgeLoginMessage[]>;

  // Background mode:
  +paused: boolean;
  +changePaused: (
    paused: boolean,
    opts?: { secondsDelay?: number },
  ) => Promise<void>;

  // Logging options:
  +logSettings: EdgeLogSettings;
  +changeLogSettings: (
    settings: $Rest<EdgeLogSettings, { ... }>,
  ) => Promise<void>;
}

// ---------------------------------------------------------------------
// fake mode
// ---------------------------------------------------------------------

export type EdgeFakeWorldOptions = {
  crashReporter?: EdgeCrashReporter;
  onLog?: EdgeOnLog;
}

export type EdgeFakeContextOptions = {
  // EdgeContextOptions:
  airbitzSupport?: boolean;
  apiKey?: string;
  apiSecret?: Uint8Array;
  appId: string;
  deviceDescription?: string;
  hideKeys?: boolean;
  logSettings?: $Rest<EdgeLogSettings, { ... }>;
  plugins?: EdgeCorePluginsInit;

  // Allows core plugins to access the real network except for the
  // login and sync servers, which remain emulated:
  allowNetworkAccess?: boolean;

  // Fake device options:
  cleanDevice?: boolean;

  /** Extra files to be saved on the fake device. */
  extraFiles?: { [path: string]: string };
}

/**
 * A block of JSON data that can be used to save & restore a user
 * on the fake unit-testing server.
 */
export type EdgeFakeUser = {
  username?: string;
  lastLogin?: Date;
  loginId: string; // base64
  loginKey: string; // base64
  repos: {
    [syncKey: string]: mixed; // Cleaned with asEdgeRepoDump
  };
  server: mixed; // Cleaned with asEdgeLoginDump
}

export type EdgeFakeWorld = {
  +close: () => Promise<void>;

  +makeEdgeContext: (
    opts: EdgeFakeContextOptions,
  ) => Promise<EdgeContext>;

  +goOffline: (offline?: boolean) => Promise<void>;
  +dumpFakeUser: (account: EdgeAccount) => Promise<EdgeFakeUser>;
}

// ---------------------------------------------------------------------
// deprecated types
// ---------------------------------------------------------------------

/** @deprecated use EdgeTxAmount instead */
export type EdgeNetworkFee = {
  +currencyCode: string;
  +nativeAmount: string;
}

/** @deprecated use EdgeTxAmount instead */
export type EdgeNetworkFee2 = {
  +nativeAmount: string;
  +currencyPluginId: string;
  +tokenId: EdgeTokenId;
}
