/// <reference types="node" />
/// <reference types="@ethersproject/providers" />
/// <reference path="node.d.ts" />
/// <reference path="react-native.d.ts" />

import { EventEmitter } from 'events';

import Web3Api from './generated/web3Api';
import SolanaApi from './generated/solanaApi';

declare enum ErrorCode {
  OTHER_CAUSE = -1,
  INTERNAL_SERVER_ERROR = 1,
  CONNECTION_FAILED = 100,
  OBJECT_NOT_FOUND = 101,
  INVALID_QUERY = 102,
  INVALID_CLASS_NAME = 103,
  MISSING_OBJECT_ID = 104,
  INVALID_KEY_NAME = 105,
  INVALID_POINTER = 106,
  INVALID_JSON = 107,
  COMMAND_UNAVAILABLE = 108,
  NOT_INITIALIZED = 109,
  INCORRECT_TYPE = 111,
  INVALID_CHANNEL_NAME = 112,
  PUSH_MISCONFIGURED = 115,
  OBJECT_TOO_LARGE = 116,
  OPERATION_FORBIDDEN = 119,
  CACHE_MISS = 120,
  INVALID_NESTED_KEY = 121,
  INVALID_FILE_NAME = 122,
  INVALID_ACL = 123,
  TIMEOUT = 124,
  INVALID_EMAIL_ADDRESS = 125,
  MISSING_CONTENT_TYPE = 126,
  MISSING_CONTENT_LENGTH = 127,
  INVALID_CONTENT_LENGTH = 128,
  FILE_TOO_LARGE = 129,
  FILE_SAVE_ERROR = 130,
  DUPLICATE_VALUE = 137,
  INVALID_ROLE_NAME = 139,
  EXCEEDED_QUOTA = 140,
  SCRIPT_FAILED = 141,
  VALIDATION_ERROR = 142,
  INVALID_IMAGE_DATA = 150,
  UNSAVED_FILE_ERROR = 151,
  INVALID_PUSH_TIME_ERROR = 152,
  FILE_DELETE_ERROR = 153,
  REQUEST_LIMIT_EXCEEDED = 155,
  INVALID_EVENT_NAME = 160,
  USERNAME_MISSING = 200,
  PASSWORD_MISSING = 201,
  USERNAME_TAKEN = 202,
  EMAIL_TAKEN = 203,
  EMAIL_MISSING = 204,
  EMAIL_NOT_FOUND = 205,
  SESSION_MISSING = 206,
  MUST_CREATE_USER_THROUGH_SIGNUP = 207,
  ACCOUNT_ALREADY_LINKED = 208,
  INVALID_SESSION_TOKEN = 209,
  LINKED_ID_MISSING = 250,
  INVALID_LINKED_SESSION = 251,
  UNSUPPORTED_SERVICE = 252,
  AGGREGATE_ERROR = 600,
  FILE_READ_ERROR = 601,
  X_DOMAIN_REQUEST = 602,
}

declare enum EChains {
  ETH_MAINENT = '0x1',
  ETH_ROPSTEN = '0x3',
  ETH_GOERLI = '0x5',
  ETH_KOVAN = '0x2a',
  ETH_RINKBEY = '0x4',
  ETH_LOCALDEVCHAIN = '0x539',
  POLYGON_MAINNET = '0x89',
  POLYGON_MUMBAI = '0x13881',
  BSC_MAINNET = '0x38',
  BSC_TESTNET = '0x61',
  AVAX_MAINNET = '0xa86a',
  AVAX_TESTNET = '0xa869',
  FANTOM_MAINNET = '0xfa',
}

export namespace Moralis {
  type EthersWeb3Provider = import('ethers').providers.Web3Provider;
  type EthersExternalProvider = import('ethers').providers.ExternalProvider;
  type EthersTransaction = import('ethers').Transaction;
  type EthersResult = import('ethers/lib/utils').Result;
  type EthersLibrary = typeof import('ethers').ethers;

  let applicationId: string;
  let javaScriptKey: string | undefined;
  let liveQueryServerURL: string;
  let masterKey: string | undefined;
  let serverAuthToken: string | undefined;
  let serverAuthType: string | undefined;
  let serverURL: string;
  let secret: string;
  let encryptedUser: boolean;
  let isInitialized: boolean;
  let Plugins: Record<string, any>;
  let Chains: IChains;

  interface PluginSpecs {
    name: string;
    functions: string[];
  }

  interface StartOptions {
    serverUrl?: string;
    appId?: string;
    moralisSecret?: string;
    plugins?: PluginSpecs[];
    javascriptKey?: string;
    masterKey?: string;
  }

  let start: (options: StartOptions) => Promise<void>;

  interface BatchSizeOption {
    batchSize?: number;
  }

  interface CascadeSaveOption {
    /** If `false`, nested objects will not be saved (default is `true`). */
    cascadeSave?: boolean;
  }

  interface SuccessOption {
    success?: Function;
  }

  interface ErrorOption {
    error?: Function;
  }

  interface FullOptions {
    success?: Function;
    error?: Function;
    useMasterKey?: boolean;
    sessionToken?: string;
    installationId?: string;
    progress?: Function;
  }

  interface FileSaveOptions extends FullOptions {
    ipfs?: boolean;
  }

  interface RequestOptions {
    useMasterKey?: boolean;
    sessionToken?: string;
    installationId?: string;
    batchSize?: number;
    include?: string | string[];
    progress?: Function;
  }

  interface SuccessFailureOptions extends SuccessOption, ErrorOption {}

  interface SignUpOptions {
    useMasterKey?: boolean;
    installationId?: string;
  }

  interface SessionTokenOption {
    sessionToken?: string;
  }

  interface WaitOption {
    /**
     * Set to true to wait for the server to confirm success
     * before triggering an event.
     */
    wait?: boolean;
  }

  interface UseMasterKeyOption {
    /**
     * In Cloud Code and Node only, causes the Master Key to be used for this request.
     */
    useMasterKey?: boolean;
  }

  interface ScopeOptions extends SessionTokenOption, UseMasterKeyOption {}

  interface SilentOption {
    /**
     * Set to true to avoid firing the event.
     */
    silent?: boolean;
  }

  interface Pointer {
    __type: string;
    className: string;
    objectId: string;
  }

  interface AuthData {
    [key: string]: any;
  }

  /**
   * Interface declaration for Authentication Providers
   * https://docs.moralis.io/Moralis-SDK-JS/api/master/AuthProvider.html
   */
  interface AuthProvider {
    /**
     * Called when _linkWith isn't passed authData. Handle your own authentication here.
     */
    authenticate: () => void;
    /**
     * (Optional) Called when service is unlinked. Handle any cleanup here.
     */
    deauthenticate?: () => void;
    /**
     * Unique identifier for this Auth Provider.
     */
    getAuthType: () => string;
    /**
     * Called when auth data is syncronized. Can be used to determine if authData is still valid
     */
    restoreAuthentication: () => boolean;
  }

  interface BaseAttributes {
    createdAt: Date;
    objectId: string;
    updatedAt: Date;
    ACL: ACL;
  }

  interface JSONBaseAttributes {
    createdAt: string;
    objectId: string;
    updatedAt: string;
  }

  // MoralisWeb3 types

  let on: (eventName: string, listener: (args?: any) => void) => () => EventEmitter;
  let off: EventEmitter['off'];
  let once: EventEmitter['once'];
  let removeListener: EventEmitter['removeListener'];
  let addListener: EventEmitter['addListener'];
  let removeAllListeners: EventEmitter['removeAllListeners'];

  let web3: null | MoralisWeb3Provider;
  let chainId: string | null;
  let account: string | null;
  let network: string | null;
  let provider: unknown | null;
  let connectorType: string | null;
  let connector: any | null;
  let getChainId: () => string | null;

  let web3Library: EthersLibrary;

  // Core functions
  let enableWeb3: (options?: EnableOptions) => Promise<MoralisWeb3Provider>;
  let cleanup: () => Promise<void>;
  let authenticate: (options?: AuthenticationOptions) => Promise<User>;
  let link: (account: string, options?: LinkOptions) => Promise<User>;
  let unlink: (account: string) => Promise<User>;
  let deactivateWeb3: () => Promise<void>;
  let isWeb3Enabled: () => boolean;

  let switchNetwork: (chainId: string | number) => Promise<void>;
  let addNetwork: (
    chainId: string | number,
    chainName: string,
    currencyName: string,
    currencySymbol: string,
    rpcUrl: string,
    blockExplorerUrl: string | null
  ) => Promise<void>;
  let isMetaMaskInstalled: () => Promise<boolean>;

  let transfer: (options: TransferOptions) => Promise<TransferResult>;
  let executeFunction: (options: ExecuteFunctionOptions) => Promise<ExecuteFunctionResult>;

  // Plugins
  let initPlugins: (installedPlugins?: PluginSpecs[]) => Promise<void>;

  // Helper functions
  let getWeb3Provider: (options: Pick<EnableOptions, 'provider'>) => Web3Provider;
  let isDotAuth: (options: Pick<EnableOptions, 'provider'>) => boolean;
  let isElrondAuth: (options: Pick<EnableOptions, 'provider'>) => boolean;

  // Convenience functions
  /** @deprecated use the functions from Moralis.Web3API instead */
  let getAllERC20: (options: CommonConvenienceOptions) => Promise<ERC20Result[]>;
  /** @deprecated use the functions from Moralis.Web3API instead */
  let getERC20: (
    options: CommonConvenienceOptions & {
      symbol?: string;
      tokenAddress?: string;
    }
  ) => Promise<ERC20Result>;
  /** @deprecated use the functions from Moralis.Web3API instead */
  let getNFTs: (options: CommonConvenienceOptions) => Promise<NFTResult[]>;
  /** @deprecated use the functions from Moralis.Web3API instead */
  let getNFTsCount: (options: CommonConvenienceOptions) => Promise<CountResult>;
  /** @deprecated use the functions from Moralis.Web3API instead */
  let getTransactions: (
    options: CommonConvenienceOptions & { order?: SortOrder }
  ) => Promise<TransactionResult[]>;
  /** @deprecated use the functions from Moralis.Web3API instead */
  let getTransactionsCount: (options: CommonConvenienceOptions) => Promise<CountResult>;

  // Eth listeners
  let getSigningData: () => string;

  let onAccountChanged: (callback: Web3AccountChangedEventCallback) => () => EventEmitter;
  let onChainChanged: (callback: Web3ChainChangedEventCallback) => () => EventEmitter;
  let onConnect: (callback: Web3ConnectEventCallback) => () => EventEmitter;
  let onDisconnect: (callback: Web3DisconnectEventCallback) => () => EventEmitter;
  let onWeb3Enabled: (callback: Web3EnabledEventCallback) => () => EventEmitter;
  let onWeb3Deactivated: (callback: Web3DeactivatedEventCallback) => () => EventEmitter;

  class CommonWeb3Provider {
    activate: () => Promise<unknown>;
    deactivate: () => Promise<void>;
  }
  class MoralisInjectedProvider extends CommonWeb3Provider {
    type: 'injected';
  }
  class MoralisWalletConnectProvider extends CommonWeb3Provider {
    type: 'WalletConnect';
  }

  type Web3ProviderType =
    | 'metamask'
    | 'walletconnect'
    | 'walletConnect'
    | 'wc'
    | 'magicLink'
    | 'web3Auth';
  type AuthenticationType = 'evm' | 'dot' | 'polkadot' | 'kusama' | 'erd' | 'elrond' | 'sol';
  type Web3Provider = MoralisWalletConnectProvider | MoralisInjectedProvider;

  interface CommonEnableOptions {
    // TODO: include proidr in specified connector interfaces as tyoe guard
    provider?: Web3ProviderType;
    connector?: Connector;
    anyNetwork?: boolean;
  }
  interface WalletConnectWeb3ConnectorEnableOptions extends CommonEnableOptions {
    chainId?: number;
    mobileLinks?: string[];
    newSession?: boolean;
  }
  interface NetworkWeb3ConnectorEnableOptions extends CommonEnableOptions {
    urls?: Record<string, string>;
    chainId?: number;
    privateKey?: string;
  }

  interface MagicWeb3ConnectorEnableOptions extends CommonEnableOptions {
    email: string;
    apiKey: string;
    network: string;
    newSession?: string;
  }

  interface Web3AuthWeb3ConnectorEnableOptions extends CommonEnableOptions {
    chainId?: number;
    clientId: string;
    theme?: string;
    appLogo?: string;
    loginMethodsOrder?: string[];
  }

  interface Web3InjectedConnectorEnableOptions extends CommonEnableOptions {}

  type EnableOptions =
    | Web3InjectedConnectorEnableOptions
    | WalletConnectWeb3ConnectorEnableOptions
    | NetworkWeb3ConnectorEnableOptions
    | MagicWeb3ConnectorEnableOptions
    | Web3AuthWeb3ConnectorEnableOptions;
  type AuthenticationOptions = EnableOptions & {
    type?: AuthenticationType;
    signingMessage?: string;
  };
  // type EnableOptions = Omit<AuthenticationOptions, 'type' | 'signingMessage'>;
  type LinkOptions = Object.SaveOptions;

  type EthChain = 'eth' | 'mainnet' | '0x1';
  type RopstenChain = 'testnet' | 'ropsten' | '0x3';
  type RinkebyChain = 'rinkeby' | '0x4';
  type GoerliChain = 'goerli' | '0x5';
  type KovanChain = 'kovan' | '0x2a';
  type BscChain = 'bsc' | 'binance' | 'binance smart chain' | '0x38';
  type BscTestChain = 'bsc testnet' | 'binance testnet' | 'binance smart chain testnet' | ' 0x61';
  type PolygonChain = 'matic' | 'polygon' | '0x89';
  type MumbaiChain = 'mumbai' | 'matic testnet' | 'polygon testnet' | '0x13881';
  type FantomChain = 'fantom' | 'ftm' | '0xfa';
  type AvalancheChain = 'avalanche' | 'avax' | '0xa86a';
  type AvalancheTestChain = 'avalanche testnet' | 'avax testnet' | '0xa869' | 'fuji';
  type LocalDevChain = 'ganache' | 'hardhat' | 'localdevchain' | 'local devchain' | 'dev' | '0x539';
  type Chain =
    | EthChain
    | RopstenChain
    | RinkebyChain
    | GoerliChain
    | KovanChain
    | BscChain
    | BscTestChain
    | PolygonChain
    | MumbaiChain
    | FantomChain
    | AvalancheChain
    | AvalancheTestChain
    | LocalDevChain;

  interface CommonConvenienceOptions {
    chain?: Chain;
    address?: string;
  }

  interface ERC20Result {
    balance: string;
    contractType?: 'ERC20';
    decimals: number;
    name: string | null;
    symbol: string | null;
  }

  type SortOrder = 'asc' | 'desc';
  type NFTContractType = 'ERC721' | 'ERC1155';
  interface NFTResult {
    amount: number;
    contract_type: NFTContractType;
    name: string | null;
    symbol: string | null;
    token_address: string;
    token_id: string;
    token_uri: string | null;
  }
  interface TransactionResult {
    block_hash: string;
    block_number: string;
    block_timestamp: string;
    from_address: string;
    gas: string;
    gas_price: string;
    hash: string;
    input: string;
    nonce: string;
    receipt_contract_address: string | null;
    receipt_cumulative_gas_used: string;
    receipt_gas_used: string;
    receipt_root: string | null;
    receipt_status: string;
    to_address: string;
    transaction_index: string;
    value: string;
  }
  interface CountResult {
    result: number;
  }

  type TransferType = 'native' | 'erc20' | 'erc721' | 'erc1155';
  type TransferSystem = 'evm';
  interface TransferOptions {
    type?: TransferType;
    receiver?: string;
    contractAddress?: string;
    /** @deprecated use contractAddress field instead */
    contract_address?: string;
    amount?: string;
    tokenId?: number | string;
    /** @deprecated use tokenId field instead */
    token_id?: number | string;
    system?: TransferSystem;
  }

  type TransferResult = EthersTransaction;

  type ExecuteFunctionParams = Record<string, any>;
  interface ExecuteFunctionOptions {
    contractAddress: string;
    abi: object;
    functionName: string;
    msgValue?: string;
    params?: ExecuteFunctionParams;
  }
  type ExecuteFunctionCallResult = EthersResult;
  type ExecuteFunctionSendResult = EthersTransaction;
  type ExecuteFunctionResult = ExecuteFunctionCallResult | ExecuteFunctionSendResult;

  // ETH listeners
  interface ConnectInfo {
    chainId: string;
  }

  interface Connector {
    type: string;
    network: string;
    account: null | string;
    chainId: null | string;
    activate: () => Promise<{
      provider: Provider;
      account?: string | null;
      chainId?: string | null;
    }>;
    deactivate: () => Promise<void>;
    [key: string]: any;
  }

  type Provider = EthersExternalProvider;
  interface EnableInfo {
    chainId: null | string;
    account: null | string;
    connector: Connector;
    provider: Provider;
    web3: MoralisWeb3Provider;
  }

  interface DeactivateInfo {
    connector: Connector;
    provider: Provider;
  }

  interface ProviderRpcError extends Error {
    message: string;
    code: number;
    data?: unknown;
  }

  interface ProviderMessage {
    type: string;
    data: unknown;
  }

  enum Web3Events {
    CONNECT = 'connect',
    DISCONNECT = 'disconnect',
    ACCOUNTS_CHANGED = 'accountsChanged',
    CHAIN_CHANGED = 'chainChanged',
    MESSAGE = 'message',
  }

  enum EthereumEvents {
    CONNECT = 'connect',
    DISCONNECT = 'disconnect',
    CHAIN_CHANGED = 'chainChanged',
    ACCOUNTS_CHANGED = 'accountsChanged',
    MESSAGE = 'message',
  }

  enum ConnectorEvents {
    CONNECT = 'connect',
    DISCONNECT = 'disconnect',
    CHAIN_CHANGED = 'chainChanged',
    ACCOUNT_CHANGED = 'accountChanged',
  }

  type Web3AccountChangedEventCallback = (account: string | null) => void;
  type Web3ChainChangedEventCallback = (chainId: string | null) => void;
  type Web3ConnectEventCallback = (connectInfo: ConnectInfo) => void;
  type Web3DisconnectEventCallback = (error: ProviderRpcError) => void;
  type Web3EnabledEventCallback = (enableInfo: EnableInfo) => void;
  type Web3DeactivatedEventCallback = (chainId: DeactivateInfo) => void;

  type MoralisWeb3Provider = EthersWeb3Provider;

  /**
   * The Moralis Web3 Provider.
   */
  /** @deprecated Any function on Moralis.Web3.xx can also be called on Moralis.xx, use that one for proper type definitions */
  let Web3: any;

  /**
   * The Moralis Web3API.
   */
  class Web3API extends Web3Api {}

  /**
   * The Moralis SolanaAPI.
   */
  class SolanaAPI extends SolanaApi {}

  /**
   * The Moralis Web3API.
   */
  class Units {
    static ETH: (value: number | string) => string;
    static Token: (value: number | string, decimals?: number) => string;
    static FromWei: (value: number | string, decimals?: number) => string;
  }

  /**
   * Creates a new ACL.
   * If no argument is given, the ACL has no permissions for anyone.
   * If the argument is a Moralis.User, the ACL will have read and write
   *   permission for only that user.
   * If the argument is any other JSON object, that object will be interpretted
   *   as a serialized ACL created with toJSON().
   * @see Moralis.Object#setACL
   *
   * <p>An ACL, or Access Control List can be added to any
   * <code>Moralis.Object</code> to restrict access to only a subset of users
   * of your application.</p>
   */
  class ACL {
    permissionsById: any;

    constructor(arg1?: any);

    setPublicReadAccess(allowed: boolean): void;
    getPublicReadAccess(): boolean;

    setPublicWriteAccess(allowed: boolean): void;
    getPublicWriteAccess(): boolean;

    setReadAccess(userId: User | string, allowed: boolean): void;
    getReadAccess(userId: User | string): boolean;

    setWriteAccess(userId: User | string, allowed: boolean): void;
    getWriteAccess(userId: User | string): boolean;

    setRoleReadAccess(role: Role | string, allowed: boolean): void;
    getRoleReadAccess(role: Role | string): boolean;

    setRoleWriteAccess(role: Role | string, allowed: boolean): void;
    getRoleWriteAccess(role: Role | string): boolean;

    toJSON(): any;
  }

  /**
   * A Moralis.File is a local representation of a file that is saved to the Moralis
   * cloud.
   * @param name The file's name. This will be prefixed by a unique
   *     value once the file has finished saving. The file name must begin with
   *     an alphanumeric character, and consist of alphanumeric characters,
   *     periods, spaces, underscores, or dashes.
   * @param data The data for the file, as either:
   *     1. an Array of byte value Numbers, or
   *     2. an Object like { base64: "..." } with a base64-encoded String.
   *     3. a File object selected with a file upload control. (3) only works
   *        in Firefox 3.6+, Safari 6.0.2+, Chrome 7+, and IE 10+.
   *        For example:<pre>
   * var fileUploadControl = $("#profilePhotoFileUpload")[0];
   * if (fileUploadControl.files.length > 0) {
   *   var file = fileUploadControl.files[0];
   *   var name = "photo.jpg";
   *   var moralisFile = new Moralis.File(name, file);
   *   moralisFile.save().then(function() {
   *     // The file has been saved to Moralis.
   *   }, function(error) {
   *     // The file either could not be read, or could not be saved to Moralis.
   *   });
   * }</pre>
   * @param type Optional Content-Type header to use for the file. If
   *     this is omitted, the content type will be inferred from the name's
   *     extension.
   */
  class File {
    constructor(
      name: string,
      data: number[] | { base64: string } | { size: number; type: string } | { uri: string },
      type?: string
    );
    /**
     * Return the data for the file, downloading it if not already present.
     * Data is present if initialized with Byte Array, Base64 or Saved with Uri.
     * Data is cleared if saved with File object selected with a file upload control
     *
     * @returns Promise that is resolved with base64 data
     */
    getData(): Promise<string>;
    url(options?: { forceSecure?: boolean }): string;
    metadata(): Record<string, any>;
    tags(): Record<string, any>;
    name(): string;
    save(options?: FileSaveOptions): Promise<File>;
    saveIPFS(options?: FileSaveOptions): Promise<File>;
    cancel(): void;
    destroy(): Promise<File>;
    toJSON(): { __type: string; name: string; url: string };
    equals(other: File): boolean;
    setMetadata(metadata: Record<string, any>): void;
    addMetadata(key: string, value: any): void;
    setTags(tags: Record<string, any>): void;
    addTag(key: string, value: any): void;
    readonly _url: string;
  }

  /**
   * Creates a new GeoPoint with any of the following forms:<br>
   *   <pre>
   *   new GeoPoint(otherGeoPoint)
   *   new GeoPoint(30, 30)
   *   new GeoPoint([30, 30])
   *   new GeoPoint({latitude: 30, longitude: 30})
   *   new GeoPoint()  // defaults to (0, 0)
   *   </pre>
   *
   * <p>Represents a latitude / longitude point that may be associated
   * with a key in a MoralisObject or used as a reference point for geo queries.
   * This allows proximity-based queries on the key.</p>
   *
   * <p>Only one key in a class may contain a GeoPoint.</p>
   *
   * <p>Example:<pre>
   *   var point = new Moralis.GeoPoint(30.0, -20.0);
   *   var object = new Moralis.Object("PlaceObject");
   *   object.set("location", point);
   *   object.save();</pre></p>
   */
  class GeoPoint {
    latitude: number;
    longitude: number;

    constructor(latitude: number, longitude: number);
    constructor(coords?: { latitude: number; longitude: number } | [number, number]);

    current(options?: SuccessFailureOptions): GeoPoint;
    radiansTo(point: GeoPoint): number;
    kilometersTo(point: GeoPoint): number;
    milesTo(point: GeoPoint): number;
    toJSON(): any;
  }

  /**
   * A class that is used to access all of the children of a many-to-many relationship.
   * Each instance of Moralis.Relation is associated with a particular parent object and key.
   */
  class Relation<S extends Object = Object, T extends Object = Object> {
    parent: S;
    key: string;
    targetClassName: string;

    constructor(parent?: S, key?: string);

    // Adds a Moralis.Object or an array of Moralis.Objects to the relation.
    add(object: T | T[]): void;

    // Returns a Moralis.Query that is limited to objects in this relation.
    query(): Query<T>;

    // Removes a Moralis.Object or an array of Moralis.Objects from this relation.
    remove(object: T | T[]): void;

    toJSON(): any;
  }

  interface Attributes {
    [key: string]: any;
  }

  /**
   * Creates a new model with defined attributes. A client id (cid) is
   * automatically generated and assigned for you.
   *
   * <p>You won't normally call this method directly.  It is recommended that
   * you use a subclass of <code>Moralis.Object</code> instead, created by calling
   * <code>extend</code>.</p>
   *
   * <p>However, if you don't want to use a subclass, or aren't sure which
   * subclass is appropriate, you can use this form:<pre>
   *     var object = new Moralis.Object("ClassName");
   * </pre>
   * That is basically equivalent to:<pre>
   *     var MyClass = Moralis.Object.extend("ClassName");
   *     var object = new MyClass();
   * </pre></p>
   *
   * @param attributes The initial set of data to store in the object.
   * @param options The options for this object instance.
   * @see Moralis.Object.extend
   *
   *
   * Creates a new model with defined attributes.
   */
  interface Object<T extends Attributes = Attributes> {
    id: string;
    createdAt: Date;
    updatedAt: Date;
    attributes: T;
    className: string;

    add<K extends { [K in keyof T]: T[K] extends any[] ? K : never }[keyof T]>(
      attr: K,
      item: T[K][number]
    ): this | false;
    addAll<K extends { [K in keyof T]: T[K] extends any[] ? K : never }[keyof T]>(
      attr: K,
      items: T[K]
    ): this | false;
    addAllUnique: this['addAll'];
    addUnique: this['add'];
    clear(options: any): any;
    clone(): this;
    destroy(options?: Object.DestroyOptions): Promise<this>;
    dirty(attr?: Extract<keyof T, string>): boolean;
    dirtyKeys(): string[];
    equals<T extends Object>(other: T): boolean;
    escape(attr: Extract<keyof T, string>): string;
    existed(): boolean;
    exists(options?: RequestOptions): Promise<boolean>;
    fetch(options?: Object.FetchOptions): Promise<this>;
    fetchFromLocalDatastore(): Promise<this>;
    fetchWithInclude<K extends Extract<keyof T, string>>(
      keys: K | Array<K | K[]>,
      options?: RequestOptions
    ): Promise<this>;
    get<K extends Extract<keyof T, string>>(attr: K): T[K];
    getACL(): ACL | undefined;
    has(attr: Extract<keyof T, string>): boolean;
    increment(attr: Extract<keyof T, string>, amount?: number): this | false;
    decrement(attr: Extract<keyof T, string>, amount?: number): this | false;
    initialize(): void;
    isDataAvailable(): boolean;
    isNew(): boolean;
    isPinned(): Promise<boolean>;
    isValid(): boolean;
    newInstance(): this;
    op(attr: Extract<keyof T, string>): any;
    pin(): Promise<void>;
    pinWithName(name: string): Promise<void>;
    relation<R extends Object, K extends Extract<keyof T, string> = Extract<keyof T, string>>(
      attr: T[K] extends Relation ? K : never
    ): Relation<this, R>;
    remove: this['add'];
    removeAll: this['addAll'];
    revert(...keys: Array<Extract<keyof T, string>>): void;
    save<K extends Extract<keyof T, string>>(
      attrs?:
        | (((x: T) => void) extends (x: Attributes) => void
            ? Partial<T>
            : {
                [key in K]: T[key];
              })
        | null,
      options?: Object.SaveOptions
    ): Promise<this>;
    save<K extends Extract<keyof T, string>>(
      key: K,
      value: T[K],
      options?: Object.SaveOptions
    ): Promise<this>;
    set<K extends Extract<keyof T, string>>(
      attrs: ((x: T) => void) extends (x: Attributes) => void
        ? Partial<T>
        : {
            [key in K]: T[key];
          },
      options?: Object.SetOptions
    ): this | false;
    set<K extends Extract<keyof T, string>>(
      key: K,
      value: T[K],
      options?: Object.SetOptions
    ): this | false;
    setACL(acl: ACL, options?: SuccessFailureOptions): this | false;
    toJSON(): Object.ToJSON<T> & JSONBaseAttributes;
    toPointer(): Pointer;
    unPin(): Promise<void>;
    unPinWithName(name: string): Promise<void>;
    unset(attr: Extract<keyof T, string>, options?: any): this | false;
    validate(attrs: Attributes, options?: SuccessFailureOptions): Error | false;
  }
  interface ObjectStatic<T extends Object = Object> {
    createWithoutData(id: string): T;
    destroyAll<T extends Object>(list: T[], options?: Object.DestroyAllOptions): Promise<T[]>;
    extend(className: string | { className: string }, protoProps?: any, classProps?: any): any;
    fetchAll<T extends Object>(list: T[], options: Object.FetchAllOptions): Promise<T[]>;
    fetchAllIfNeeded<T extends Object>(list: T[], options?: Object.FetchAllOptions): Promise<T[]>;
    fetchAllIfNeededWithInclude<T extends Object>(
      list: T[],
      keys: keyof T['attributes'] | Array<keyof T['attributes']>,
      options?: RequestOptions
    ): Promise<T[]>;
    fetchAllWithInclude<T extends Object>(
      list: T[],
      keys: keyof T['attributes'] | Array<keyof T['attributes']>,
      options?: RequestOptions
    ): Promise<T[]>;
    fromJSON(json: any, override?: boolean): T;
    pinAll(objects: Object[]): Promise<void>;
    pinAllWithName(name: string, objects: Object[]): Promise<void>;
    registerSubclass(className: string, clazz: new (options?: any) => T): void;
    saveAll<T extends readonly Object[]>(list: T, options?: Object.SaveAllOptions): Promise<T>;
    unPinAll(objects: Object[]): Promise<void>;
    unPinAllObjects(): Promise<void>;
    unPinAllObjectsWithName(name: string): Promise<void>;
    unPinAllWithName(name: string, objects: Object[]): Promise<void>;
  }
  interface ObjectConstructor extends ObjectStatic {
    new <T extends Attributes>(className: string, attributes: T, options?: any): Object<T>;
    new (className?: string, attributes?: Attributes, options?: any): Object;
  }
  const Object: ObjectConstructor;

  namespace Object {
    interface DestroyOptions extends SuccessFailureOptions, WaitOption, ScopeOptions {}

    interface DestroyAllOptions extends BatchSizeOption, ScopeOptions {}

    interface FetchAllOptions extends SuccessFailureOptions, ScopeOptions {}

    interface FetchOptions extends SuccessFailureOptions, ScopeOptions {}

    interface SaveOptions
      extends CascadeSaveOption,
        SuccessFailureOptions,
        SilentOption,
        ScopeOptions,
        WaitOption {}

    interface SaveAllOptions extends BatchSizeOption, ScopeOptions {}

    interface SetOptions extends ErrorOption, SilentOption {
      promise?: any;
    }

    // From https://github.com/moralis-community/Moralis-SDK-JS/blob/master/src/encode.js
    type Encode<T> = T extends Object
      ? ReturnType<T['toJSON']> | Pointer
      : T extends ACL | GeoPoint | Polygon | Relation | File
      ? ReturnType<T['toJSON']>
      : T extends Date
      ? { __type: 'Date'; iso: string }
      : T extends RegExp
      ? string
      : T extends Array<infer R>
      ? any[]
      : T extends object
      ? ToJSON<T>
      : T;

    type ToJSON<T> = {
      [K in keyof T]: Encode<T[K]>;
    };
  }

  class Polygon {
    constructor(arg1: GeoPoint[] | number[][]);
    containsPoint(point: GeoPoint): boolean;
    equals(other: any): boolean;
    toJSON(): any;
  }

  /**
   * Every Moralis application installed on a device registered for
   * push notifications has an associated Installation object.
   */
  interface Installation<T extends Attributes = Attributes> extends Object<T> {
    badge: any;
    channels: string[];
    timeZone: any;
    deviceType: string;
    pushType: string;
    installationId: string;
    deviceToken: string;
    channelUris: string;
    appName: string;
    appVersion: string;
    parseVersion: string;
    appIdentifier: string;
  }
  interface InstallationConstructor extends ObjectStatic<Installation> {
    new <T extends Attributes>(attributes: T): Installation<T>;
    new (): Installation;
  }
  const Installation: InstallationConstructor;

  /**
   * Creates a new Moralis.Query for the given Moralis.Object subclass.
   * @param objectClass -
   *   An instance of a subclass of Moralis.Object, or a Moralis className string.
   *
   * <p>Moralis.Query defines a query that is used to fetch Moralis.Objects. The
   * most common use case is finding all objects that match a query through the
   * <code>find</code> method. For example, this sample code fetches all objects
   * of class <code>MyClass</code>. It calls a different function depending on
   * whether the fetch succeeded or not.
   *
   * <pre>
   * var query = new Moralis.Query(MyClass);
   * query.find({
   *   success: function(results) {
   *     // results is an array of Moralis.Object.
   *   },
   *
   *   error: function(error) {
   *     // error is an instance of Moralis.Error.
   *   }
   * });</pre></p>
   *
   * <p>A Moralis.Query can also be used to retrieve a single object whose id is
   * known, through the get method. For example, this sample code fetches an
   * object of class <code>MyClass</code> and id <code>myId</code>. It calls a
   * different function depending on whether the fetch succeeded or not.
   *
   * <pre>
   * var query = new Moralis.Query(MyClass);
   * query.get(myId, {
   *   success: function(object) {
   *     // object is an instance of Moralis.Object.
   *   },
   *
   *   error: function(object, error) {
   *     // error is an instance of Moralis.Error.
   *   }
   * });</pre></p>
   *
   * <p>A Moralis.Query can also be used to count the number of objects that match
   * the query without retrieving all of those objects. For example, this
   * sample code counts the number of objects of the class <code>MyClass</code>
   * <pre>
   * var query = new Moralis.Query(MyClass);
   * query.count({
   *   success: function(number) {
   *     // There are number instances of MyClass.
   *   },
   *
   *   error: function(error) {
   *     // error is an instance of Moralis.Error.
   *   }
   * });</pre></p>
   */
  class Query<T extends Object = Object> {
    objectClass: any;
    className: string;

    constructor(objectClass: string | (new (...args: any[]) => T | Object));

    static and<U extends Object>(...args: Array<Query<U>>): Query<U>;
    static fromJSON<U extends Object>(className: string | (new () => U), json: any): Query<U>;
    static nor<U extends Object>(...args: Array<Query<U>>): Query<U>;
    static or<U extends Object>(...var_args: Array<Query<U>>): Query<U>;

    addAscending<K extends keyof T['attributes'] | keyof BaseAttributes>(key: K | K[]): this;
    addDescending<K extends keyof T['attributes'] | keyof BaseAttributes>(key: K | K[]): this;
    ascending<K extends keyof T['attributes'] | keyof BaseAttributes>(key: K | K[]): this;
    aggregate<V = any>(pipeline: Query.AggregationOptions | Query.AggregationOptions[]): Promise<V>;
    containedBy<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      values: Array<T['attributes'][K] | (T['attributes'][K] extends Object ? string : never)>
    ): this;
    containedIn<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      values: Array<T['attributes'][K] | (T['attributes'][K] extends Object ? string : never)>
    ): this;
    contains<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      substring: string
    ): this;
    containsAll<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      values: any[]
    ): this;
    containsAllStartingWith<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      values: any[]
    ): this;
    count(options?: Query.CountOptions): Promise<number>;
    descending<K extends keyof T['attributes'] | keyof BaseAttributes>(key: K | K[]): this;
    doesNotExist<K extends keyof T['attributes'] | keyof BaseAttributes>(key: K): this;
    doesNotMatchKeyInQuery<
      U extends Object,
      K extends keyof T['attributes'] | keyof BaseAttributes,
      X extends Extract<keyof U['attributes'], string>
    >(key: K, queryKey: X, query: Query<U>): this;
    doesNotMatchQuery<U extends Object, K extends keyof T['attributes']>(
      key: K,
      query: Query<U>
    ): this;
    distinct<K extends keyof T['attributes'], V = T['attributes'][K]>(key: K): Promise<V[]>;
    eachBatch(
      callback: (objs: T[]) => PromiseLike<void> | void,
      options?: Query.BatchOptions
    ): Promise<void>;
    each(
      callback: (obj: T) => PromiseLike<void> | void,
      options?: Query.BatchOptions
    ): Promise<void>;
    hint(value: string | object): this;
    explain(explain: boolean): this;
    map<U>(
      callback: (currentObject: T, index: number, query: Query) => PromiseLike<U> | U,
      options?: Query.BatchOptions
    ): Promise<U[]>;
    reduce(
      callback: (accumulator: T, currentObject: T, index: number) => PromiseLike<T> | T,
      initialValue?: undefined,
      options?: Query.BatchOptions
    ): Promise<T>;
    reduce<U>(
      callback: (accumulator: U, currentObject: T, index: number) => PromiseLike<U> | U,
      initialValue: U,
      options?: Query.BatchOptions
    ): Promise<U>;
    filter(
      callback: (currentObject: T, index: number, query: Query) => PromiseLike<boolean> | boolean,
      options?: Query.BatchOptions
    ): Promise<T[]>;
    endsWith<K extends keyof T['attributes'] | keyof BaseAttributes>(key: K, suffix: string): this;
    equalTo<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      value:
        | T['attributes'][K]
        | (T['attributes'][K] extends Object
            ? Pointer
            : T['attributes'][K] extends Array<infer E>
            ? E
            : never)
    ): this;
    exclude<K extends keyof T['attributes'] | keyof BaseAttributes>(...keys: K[]): this;
    exists<K extends keyof T['attributes'] | keyof BaseAttributes>(key: K): this;
    find(options?: Query.FindOptions): Promise<T[]>;
    first(options?: Query.FirstOptions): Promise<T | undefined>;
    fromNetwork(): this;
    fromLocalDatastore(): this;
    fromPin(): this;
    fromPinWithName(name: string): this;
    cancel(): this;
    fullText<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      value: string,
      options?: Query.FullTextOptions
    ): this;
    get(objectId: string, options?: Query.GetOptions): Promise<T>;
    greaterThan<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      value: T['attributes'][K]
    ): this;
    greaterThanOrEqualTo<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      value: T['attributes'][K]
    ): this;
    include<K extends keyof T['attributes'] | keyof BaseAttributes>(...key: K[]): this;
    include<K extends keyof T['attributes'] | keyof BaseAttributes>(key: K[]): this;
    includeAll(): Query<T>;
    lessThan<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      value: T['attributes'][K]
    ): this;
    lessThanOrEqualTo<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      value: T['attributes'][K]
    ): this;
    limit(n: number): Query<T>;
    matches<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      regex: RegExp,
      modifiers?: string
    ): this;
    matchesKeyInQuery<
      U extends Object,
      K extends keyof T['attributes'],
      X extends Extract<keyof U['attributes'], string>
    >(key: K, queryKey: X, query: Query<U>): this;
    matchesQuery<U extends Object, K extends keyof T['attributes']>(key: K, query: Query<U>): this;
    near<K extends keyof T['attributes'] | keyof BaseAttributes>(key: K, point: GeoPoint): this;
    notContainedIn<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      values: Array<T['attributes'][K]>
    ): this;
    notEqualTo<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      value:
        | T['attributes'][K]
        | (T['attributes'][K] extends Object
            ? Pointer
            : T['attributes'][K] extends Array<infer E>
            ? E
            : never)
    ): this;
    polygonContains<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      point: GeoPoint
    ): this;
    select<K extends keyof T['attributes'] | keyof BaseAttributes>(...keys: K[]): this;
    select<K extends keyof T['attributes'] | keyof BaseAttributes>(keys: K[]): this;
    skip(n: number): Query<T>;
    sortByTextScore(): this;
    startsWith<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      prefix: string
    ): this;
    subscribe(): Promise<LiveQuerySubscription>;
    toJSON(): any;
    withJSON(json: any): this;
    withCount(includeCount?: boolean): this;
    withinGeoBox<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      southwest: GeoPoint,
      northeast: GeoPoint
    ): this;
    withinKilometers<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      point: GeoPoint,
      maxDistance: number,
      sorted?: boolean
    ): this;
    withinMiles<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      point: GeoPoint,
      maxDistance: number,
      sorted?: boolean
    ): this;
    withinPolygon<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      points: number[][]
    ): this;
    withinRadians<K extends keyof T['attributes'] | keyof BaseAttributes>(
      key: K,
      point: GeoPoint,
      maxDistance: number
    ): this;
  }

  namespace Query {
    interface EachOptions extends SuccessFailureOptions, ScopeOptions {}
    interface CountOptions extends SuccessFailureOptions, ScopeOptions {}
    interface FindOptions extends SuccessFailureOptions, ScopeOptions {}
    interface FirstOptions extends SuccessFailureOptions, ScopeOptions {}
    interface GetOptions extends SuccessFailureOptions, ScopeOptions {}

    // According to http://docs.moralis.io/rest/guide/#aggregate-queries
    interface AggregationOptions {
      group?: { objectId?: string; [key: string]: any };
      match?: { [key: string]: any };
      project?: { [key: string]: any };
      limit?: number;
      skip?: number;
      // Sort documentation https://docs.mongodb.com/v3.2/reference/operator/aggregation/sort/#pipe._S_sort
      sort?: { [key: string]: 1 | -1 };
      // Sample documentation: https://docs.mongodb.com/v3.2/reference/operator/aggregation/sample/
      sample?: { size: number };
    }

    // According to https://docs.moralis.io/Moralis-SDK-JS/api/2.1.0/Moralis.Query.html#fullText
    interface FullTextOptions {
      language?: string;
      caseSensitive?: boolean;
      diacriticSensitive?: boolean;
    }

    interface BatchOptions extends FullOptions {
      batchSize?: number;
    }
  }

  /**
   * Represents a LiveQuery Subscription.
   *
   * @see https://docs.moralis.io/js/guide/#live-queries
   * @see NodeJS.EventEmitter
   *
   * Events list
   * ---
   * `open` - when you call `query.subscribe()`, we send a subscribe request to
   * the LiveQuery server, when we get the confirmation from the LiveQuery server,
   * this event will be emitted. When the client loses WebSocket connection to the
   * LiveQuery server, we will try to auto reconnect the LiveQuery server. If we
   * reconnect the LiveQuery server and successfully resubscribe the MoralisQuery,
   * you'll also get this event.
   *
   * ```
   * subscription.on('open', () => {});
   * ```
   * ---
   * `create` - when a new MoralisObject is created and it fulfills the MoralisQuery you subscribe,
   * you'll get this event. The object is the MoralisObject which is created.
   *
   * ```
   * subscription.on('create', (object: Moralis.Object) => {});
   * ```
   * ---
   * `update` event - when an existing MoralisObject which fulfills the MoralisQuery you subscribe
   * is updated (The MoralisObject fulfills the MoralisQuery before and after changes),
   * you'll get this event. The object is the MoralisObject which is updated.
   * Its content is the latest value of the MoralisObject.
   *
   * ```
   * subscription.on('update', (object: Moralis.Object) => {});
   * ```
   * ---
   * `enter` event - when an existing MoralisObject's old value doesn't fulfill the MoralisQuery
   * but its new value fulfills the MoralisQuery, you'll get this event. The object is the
   * MoralisObject which enters the MoralisQuery. Its content is the latest value of the MoralisObject.
   *
   * ```
   * subscription.on('enter', (object: Moralis.Object) => {});
   * ```
   * ---
   * `update` event - when an existing MoralisObject's old value fulfills the MoralisQuery but its new value
   * doesn't fulfill the MoralisQuery, you'll get this event. The object is the MoralisObject
   * which leaves the MoralisQuery. Its content is the latest value of the MoralisObject.
   *
   * ```
   * subscription.on('leave', (object: Moralis.Object) => {});
   * ```
   * ---
   * `delete` event - when an existing MoralisObject which fulfills the MoralisQuery is deleted, you'll
   * get this event. The object is the MoralisObject which is deleted.
   *
   * ```
   * subscription.on('delete', (object: Moralis.Object) => {});
   * ```
   * ---
   * `close` event - when the client loses the WebSocket connection to the LiveQuery
   * server and we stop receiving events, you'll get this event.
   *
   * ```
   * subscription.on('close', () => {});
   * ```
   */
  class LiveQuerySubscription extends EventEmitter {
    /**
     * Creates an instance of LiveQuerySubscription.
     *
     * @param id
     * @param query
     * @param [sessionToken]
     */
    constructor(id: string, query: string, sessionToken?: string);

    on(
      event: 'open' | 'create' | 'update' | 'enter' | 'leave' | 'delete' | 'close',
      listener: (object: Object) => void
    ): this;

    /**
     * Closes the subscription.
     *
     */
    unsubscribe(): void;
  }

  /**
   * Represents a Role on the Moralis server. Roles represent groupings of
   * Users for the purposes of granting permissions (e.g. specifying an ACL
   * for an Object). Roles are specified by their sets of child users and
   * child roles, all of which are granted any permissions that the parent
   * role has.
   *
   * <p>Roles must have a name (which cannot be changed after creation of the
   * role), and must specify an ACL.</p>
   * A Moralis.Role is a local representation of a role persisted to the Moralis
   * cloud.
   */
  interface Role<T extends Attributes = Attributes> extends Object<T> {
    getRoles(): Relation<Role, Role>;
    getUsers<U extends User>(): Relation<Role, U>;
    getName(): string;
    setName(name: string, options?: SuccessFailureOptions): any;
  }
  interface RoleConstructor extends ObjectStatic<Role> {
    new <T extends Attributes>(name: string, acl: ACL): Role<Partial<T>>;
    new (name: string, acl: ACL): Role;
  }
  const Role: RoleConstructor;

  class Config {
    static get(options?: UseMasterKeyOption): Promise<Config>;
    static current(): Config;
    static save(attr: any, options?: { [attr: string]: boolean }): Promise<Config>;

    get(attr: string): any;
    escape(attr: string): any;
  }

  interface Session<T extends Attributes = Attributes> extends Object<T> {
    getSessionToken(): string;
    isCurrentSessionRevocable(): boolean;
  }
  interface SessionConstructor extends ObjectStatic<Session> {
    new <T extends Attributes>(attributes: T): Session<T>;
    new (): Session;

    current(): Promise<Session>;
  }
  const Session: SessionConstructor;

  /**
   *
   * <p>A Moralis.User object is a local representation of a user persisted to the
   * Moralis cloud. This class is a subclass of a Moralis.Object, and retains the
   * same functionality of a Moralis.Object, but also extends it with various
   * user specific methods, like authentication, signing up, and validation of
   * uniqueness.</p>
   */
  interface User<T extends Attributes = Attributes> extends Object<T> {
    signUp(attrs?: any, options?: SignUpOptions): Promise<this>;
    logIn(options?: FullOptions): Promise<this>;
    authenticated(): boolean;
    isCurrent(): boolean;

    getEmail(): string | undefined;
    setEmail(email: string, options?: SuccessFailureOptions): boolean;

    getUsername(): string | undefined;
    setUsername(username: string, options?: SuccessFailureOptions): boolean;

    setPassword(password: string, options?: SuccessFailureOptions): boolean;
    getSessionToken(): string;

    linkWith: (
      provider: string | AuthProvider,
      options: { authData?: AuthData },
      saveOpts?: FullOptions
    ) => Promise<this>;
    _linkWith: (
      provider: string | AuthProvider,
      options: { authData?: AuthData },
      saveOpts?: FullOptions
    ) => Promise<this>;
    _isLinked: (provider: string | AuthProvider) => boolean;
    _unlinkFrom: (provider: string | AuthProvider, options?: FullOptions) => Promise<this>;
  }
  interface UserConstructor extends ObjectStatic<User> {
    new <T extends Attributes>(attributes: T): User<T>;
    new (attributes?: Attributes): User;

    allowCustomUserClass(isAllowed: boolean): void;
    become<T extends User>(sessionToken: string, options?: UseMasterKeyOption): Promise<T>;
    me<T extends User>(sessionToken: string, options?: UseMasterKeyOption): Promise<T>;
    current<T extends User>(): T | undefined;
    currentAsync<T extends User>(): Promise<T | null>;
    signUp<T extends User>(
      username: string,
      password: string,
      attrs: any,
      options?: SignUpOptions
    ): Promise<T>;
    logIn<T extends User>(username: string, password: string, options?: FullOptions): Promise<T>;
    logOut<T extends User>(): Promise<T>;
    requestPasswordReset<T extends User>(
      email: string,
      options?: SuccessFailureOptions
    ): Promise<T>;
    extend(protoProps?: any, classProps?: any): any;
    hydrate<T extends User>(userJSON: any): Promise<T>;
    enableUnsafeCurrentUser(): void;
    logInWith<T extends User>(
      provider: string | AuthProvider,
      options: { authData?: AuthData },
      saveOpts?: FullOptions
    ): Promise<T>;
    _registerAuthenticationProvider: (provider: AuthProvider) => void;
  }
  const User: UserConstructor;

  /**
   * A Moralis.Schema object is for handling schema data from Moralis.
   * All the schemas methods require MasterKey.
   *
   * @param className Moralis Class string
   *
   * https://docs.moralis.io/Moralis-SDK-JS/api/master/Moralis.Schema.html
   *
   * ```
   * const schema = new Moralis.Schema('MyClass');
   * schema.addString('field');
   * schema.addIndex('index_name', { field: 1 });
   * schema.save();
   * ```
   */
  class Schema<T extends Object = any> {
    constructor(className: string);

    /**
     * Static method to get all schemas
     *
     * @return A promise that is resolved with the result when
     * the query completes.
     */
    static all(): Promise<Schema[]>;

    addArray(key: Schema.AttrType<T, any[]>, options?: Schema.FieldOptions<any[]>): this;
    addBoolean(key: Schema.AttrType<T, boolean>, options?: Schema.FieldOptions<boolean>): this;
    addDate(key: Schema.AttrType<T, Date>, options?: Schema.FieldOptions<Date>): this;
    addField<T extends Schema.TYPE = any>(
      name: string,
      type?: T,
      options?: Schema.FieldOptions
    ): this;
    addFile(key: Schema.AttrType<T, File>, options?: Schema.FieldOptions<File>): this;
    addGeoPoint(key: Schema.AttrType<T, GeoPoint>, options?: Schema.FieldOptions<GeoPoint>): this;

    /**
     * Adding an Index to Create / Update a Schema
     * @param name Name of the field that will be created on Moralis
     * @param index `{ 'field': value }` where `field` should exist in the schema before using addIndex.
     * @return Returns the schema, so you can chain this call.
     * @example
     * ```
     * schema.addIndex('index_name', {'field': 1});
     * ```
     */
    addIndex(name: string, index: Schema.Index): this;

    addNumber(key: Schema.AttrType<T, number>, options?: Schema.FieldOptions<number>): this;

    addObject(key: Schema.AttrType<T, object>, options?: Schema.FieldOptions<object>): this;

    /**
     * Adding Pointer Field
     * @param name Name of the field that will be created on Moralis
     * @param targetClass  Name of the target Pointer Class
     * @return Returns the schema, so you can chain this call.
     */
    addPointer(
      key: Schema.AttrType<T, Object | Pointer>,
      targetClass: string,
      options?: Schema.FieldOptions<Pointer>
    ): this;

    addPolygon(key: Schema.AttrType<T, Polygon>, options?: Schema.FieldOptions<Polygon>): this;

    /**
     * Adding Relation Field
     * @param name Name of the field that will be created on Moralis
     * @param targetClass  Name of the target Pointer Class
     * @return Returns the schema, so you can chain this call.
     */
    addRelation(
      key: Schema.AttrType<T, Relation>,
      targetClass: string,
      options?: Schema.FieldOptions<Relation>
    ): this;

    addString(key: Schema.AttrType<T, string>, options?: Schema.FieldOptions<string>): this;

    /**
     * Removing a Schema from Moralis Can only be used on Schema without objects
     * @returns A promise that is resolved with the result when the query completes.
     */
    // @TODO Fix Promise<any>
    delete(): Promise<any>;

    /**
     * Deleting a Field to Update on a Schema
     * @param name Name of the field
     * @return Returns the schema, so you can chain this call.
     */
    deleteField(name: string): this;

    /**
     * Deleting a Index Field to Update on a Schema
     * @param name Name of the index field
     * @return Returns the schema, so you can chain this call.
     */
    deleteIndex(name: string): this;

    /**
     * Get the Schema from Moralis
     */
    get(): Promise<Schema>;

    /**
     * Removes all objects from a Schema (class) in  EXERCISE CAUTION, running this will delete all objects for this schema and cannot be reversed
     */
    // TODO Fix Promise<any>
    purge(): Promise<any>;

    /**
     * Create a new Schema on Moralis
     */
    save(): Promise<Schema>;

    /**
     * Sets Class Level Permissions when creating / updating a Schema.
     * EXERCISE CAUTION, running this may override CLP for this schema and cannot be reversed
     */
    setCLP(clp: Schema.CLP): this;

    /**
     * Update a Schema on Moralis
     */
    update(): Promise<Schema>;
  }

  namespace Schema {
    type TYPE =
      | 'String'
      | 'Number'
      | 'Boolean'
      | 'Date'
      | 'File'
      | 'GeoPoint'
      | 'Polygon'
      | 'Array'
      | 'Object'
      | 'Pointer'
      | 'Relation';
    type FieldType =
      | string
      | number
      | boolean
      | Date
      | File
      | GeoPoint
      | Polygon
      | any[]
      | object
      | Pointer
      | Relation;
    type AttrType<T extends Object, V> = Extract<
      {
        [K in keyof T['attributes']]: T['attributes'][K] extends V ? K : never;
      }[keyof T['attributes']],
      string
    >;

    interface FieldOptions<T extends FieldType = any> {
      required?: boolean;
      defaultValue?: T;
    }

    interface Index {
      [fieldName: string]: TYPE;
    }

    /**
     * The id of a `_User` object or a role name prefixed by `'role:'`.
     * @example
     *  '*': false, // public
     *  requiresAuthentication: false,
     * 'role:Admin': true,
     *  'idOfASpecificUser': true
     */
    interface CLPField {
      '*'?: boolean;
      requiresAuthentication?: boolean;
      /** `role:Admin` */
      [userIdOrRoleName: string]: boolean | undefined;
    }

    interface CLP {
      find?: CLPField;
      get?: CLPField;
      count?: CLPField;
      create?: CLPField;
      update?: CLPField;
      delete?: CLPField;
      addField?: CLPField;
      /** Array of fields that point to a `_User` object's ID or a `Role` object's name */
      readUserFields?: string[];
      /** Array of fields that point to a `_User` object's ID or a `Role` object's name */
      writeUserFields?: string[];
    }
  }

  namespace Analytics {
    function track(name: string, dimensions: any): Promise<any>;
  }

  /**
   * Provides utility functions for working with Anonymously logged-in users.
   */
  namespace AnonymousUtils {
    function isLinked(user: User): boolean;
    function link(user: User, options?: ScopeOptions): Promise<User>;
    function logIn(options?: ScopeOptions): Promise<User>;
  }

  /**
   * Provides a set of utilities for using Moralis with Facebook.
   * Provides a set of utilities for using Moralis with Facebook.
   */
  namespace FacebookUtils {
    function init(options?: any): void;
    function isLinked(user: User): boolean;
    function link(user: User, permissions: any, options?: SuccessFailureOptions): void;
    function logIn(permissions: any, options?: FullOptions): void;
    function unlink(user: User, options?: SuccessFailureOptions): void;
  }

  /**
   * Contains functions for calling and declaring
   * <a href="/docs/cloud_code_guide#functions">cloud functions</a>.
   * <p><strong><em>
   *   Some functions are only available from Cloud Code.
   * </em></strong></p>
   */
  namespace Cloud {
    interface CookieOptions {
      domain?: string;
      expires?: Date;
      httpOnly?: boolean;
      maxAge?: number;
      path?: string;
      secure?: boolean;
    }

    interface HttpResponse {
      buffer?: Buffer;
      cookies?: any;
      data?: any;
      headers?: any;
      status?: number;
      text?: string;
    }

    interface JobRequest<T extends Params = Params> {
      params: T;
      message: (response: any) => void;
    }

    interface Params {
      [key: string]: any;
    }

    interface FunctionRequest<T extends Params = Params> {
      installationId?: string;
      master?: boolean;
      params: T;
      user?: User;
    }

    interface Cookie {
      name?: string;
      options?: CookieOptions;
      value?: string;
    }

    interface TriggerRequest<T = Object> {
      installationId?: string;
      master?: boolean;
      user?: User;
      ip: string;
      headers: any;
      triggerName: string;
      log: any;
      object: T;
      original?: T;
    }

    interface AfterSaveRequest<T = Object> extends TriggerRequest<T> {
      context: object;
    }
    interface AfterDeleteRequest<T = Object> extends TriggerRequest<T> {} // tslint:disable-line no-empty-interface
    interface BeforeDeleteRequest<T = Object> extends TriggerRequest<T> {} // tslint:disable-line no-empty-interface
    interface BeforeSaveRequest<T = Object> extends TriggerRequest<T> {
      context: object;
    }

    interface FileTriggerRequest extends TriggerRequest<File> {
      file: File;
      fileSize: number;
      contentLength: number;
    }

    // Read preference describes how MongoDB driver route read operations to the members of a replica set.
    enum ReadPreferenceOption {
      Primary = 'PRIMARY',
      PrimaryPreferred = 'PRIMARY_PREFERRED',
      Secondary = 'SECONDARY',
      SecondaryPreferred = 'SECONDARY_PREFERRED',
      Nearest = 'NEAREST',
    }

    interface BeforeFindRequest<T extends Object = Object> extends TriggerRequest<T> {
      query: Query<T>;
      count: boolean;
      isGet: boolean;
      readPreference?: ReadPreferenceOption;
    }

    interface AfterFindRequest<T = Object> extends TriggerRequest<T> {
      objects: T[];
    }

    function afterDelete<T extends Object = Object>(
      arg1: { new (): T } | string,
      func?: (request: AfterDeleteRequest<T>) => Promise<void> | void
    ): void;
    function afterSave<T extends Object = Object>(
      arg1: { new (): T } | string,
      func?: (request: AfterSaveRequest<T>) => Promise<void> | void
    ): void;
    function beforeDelete<T extends Object = Object>(
      arg1: { new (): T } | string,
      func?: (request: BeforeDeleteRequest<T>) => Promise<void> | void
    ): void;
    function beforeSave<T extends Object = Object>(
      arg1: { new (): T } | string,
      func?: (request: BeforeSaveRequest<T>) => Promise<void> | void
    ): void;
    function beforeFind<T extends Object = Object>(
      arg1: { new (): T } | string,
      func?: (request: BeforeFindRequest<T>) => Promise<Query<T>> | Promise<void> | Query<T> | void
    ): void;
    function afterFind<T extends Object = Object>(
      arg1: { new (): T } | string,
      func?: (request: AfterFindRequest<T>) => any
    ): void;

    function beforeLogin(func?: (request: TriggerRequest<User>) => PromiseLike<void> | void): void;
    function afterLogin(func?: (request: TriggerRequest<User>) => PromiseLike<void> | void): void;
    function afterLogout(
      func?: (request: TriggerRequest<Session>) => PromiseLike<void> | void
    ): void;

    function beforeSaveFile(func?: (request: FileTriggerRequest) => PromiseLike<File> | void): void;
    function afterSaveFile(func?: (request: FileTriggerRequest) => PromiseLike<void> | void): void;
    function beforeDeleteFile(
      func?: (request: FileTriggerRequest) => PromiseLike<void> | void
    ): void;
    function afterDeleteFile(
      func?: (request: FileTriggerRequest) => PromiseLike<void> | void
    ): void;

    function define(name: string, func: (request: FunctionRequest) => any): void;
    function define<T extends () => any>(
      name: string,
      func: (request: FunctionRequest<{}>) => Promise<ReturnType<T>> | ReturnType<T>
    ): void;
    function define<
      T extends (param: { [P in keyof Parameters<T>[0]]: Parameters<T>[0][P] }) => any
    >(
      name: string,
      func: (request: FunctionRequest<Parameters<T>[0]>) => Promise<ReturnType<T>> | ReturnType<T>
    ): void;
    /**
     * Gets data for the current set of cloud jobs.
     * @returns A promise that will be resolved with the result of the function.
     */
    function getJobsData(): Promise<Object>;
    /**
     * Gets job status by Id
     * @param jobStatusId The Id of Job Status.
     * @returns Status of Job.
     */
    function getJobStatus(jobStatusId: string): Promise<Object>;
    function httpRequest(options: HTTPOptions): Promise<HttpResponse>;
    function job(name: string, func?: (request: JobRequest) => Promise<void> | void): HttpResponse;
    function run(name: string, data?: Params, options?: RunOptions): Promise<any>;
    function run<T extends () => any>(
      name: string,
      data?: null,
      options?: RunOptions
    ): Promise<ReturnType<T>>;
    function run<T extends (param: { [P in keyof Parameters<T>[0]]: Parameters<T>[0][P] }) => any>(
      name: string,
      data: Parameters<T>[0],
      options?: RunOptions
    ): Promise<ReturnType<T>>;
    /**
     * Starts a given cloud job, which will process asynchronously.
     * @param jobName The function name.
     * @param data The parameters to send to the cloud function.
     * @returns A promise that will be resolved with the jobStatusId of the job.
     */
    function startJob(jobName: string, data: any): Promise<string>;
    function useMasterKey(): void;

    interface RunOptions extends SuccessFailureOptions, ScopeOptions {}

    /**
     * To use this Cloud Module in Cloud Code, you must require 'buffer' in your JavaScript file.
     *
     *     import Buffer = require("buffer").Buffer;
     */
    let HTTPOptions: new () => HTTPOptions;
    interface HTTPOptions {
      /**
       * The body of the request.
       * If it is a JSON object, then the Content-Type set in the headers must be application/x-www-form-urlencoded or application/json.
       * You can also set this to a Buffer object to send raw bytes.
       * If you use a Buffer, you should also set the Content-Type header explicitly to describe what these bytes represent.
       */
      body?: string | Buffer | object;
      /**
       * Defaults to 'false'.
       */
      followRedirects?: boolean;
      /**
       * The headers for the request.
       */
      headers?: {
        [headerName: string]: string | number | boolean;
      };
      /**
       * The method of the request (i.e GET, POST, etc).
       */
      method?: string;
      /**
       * The query portion of the url.
       */
      params?: any;
      /**
       * The url to send the request to.
       */
      url: string;

      success?: (response: any) => void;
      error?: (response: any) => void;
    }
  }

  interface IChains {
    ETH_MAINET: EChains.ETH_MAINENT;
    ETH_ROPSTEN: EChains.ETH_ROPSTEN;
    ETH_GOERLI: EChains.ETH_GOERLI;
    ETH_KOVAN: EChains.ETH_KOVAN;
    ETH_RINKBEY: EChains.ETH_RINKBEY;
    ETH_LOCALDEVCHAIN: EChains.ETH_LOCALDEVCHAIN;
    POLYGON_MAINNET: EChains.POLYGON_MAINNET;
    POLYGON_MUMBAI: EChains.POLYGON_MUMBAI;
    BSC_MAINNET: EChains.BSC_MAINNET;
    BSC_TESTNET: EChains.BSC_TESTNET;
    AVAX_MAINNET: EChains.AVAX_MAINNET;
    AVAX_TESTNET: EChains.AVAX_TESTNET;
    FANTOM_MAINNET: EChains.FANTOM_MAINNET;
  }

  class Error {
    static OTHER_CAUSE: ErrorCode.OTHER_CAUSE;
    static INTERNAL_SERVER_ERROR: ErrorCode.INTERNAL_SERVER_ERROR;
    static CONNECTION_FAILED: ErrorCode.CONNECTION_FAILED;
    static OBJECT_NOT_FOUND: ErrorCode.OBJECT_NOT_FOUND;
    static INVALID_QUERY: ErrorCode.INVALID_QUERY;
    static INVALID_CLASS_NAME: ErrorCode.INVALID_CLASS_NAME;
    static MISSING_OBJECT_ID: ErrorCode.MISSING_OBJECT_ID;
    static INVALID_KEY_NAME: ErrorCode.INVALID_KEY_NAME;
    static INVALID_POINTER: ErrorCode.INVALID_POINTER;
    static INVALID_JSON: ErrorCode.INVALID_JSON;
    static COMMAND_UNAVAILABLE: ErrorCode.COMMAND_UNAVAILABLE;
    static NOT_INITIALIZED: ErrorCode.NOT_INITIALIZED;
    static INCORRECT_TYPE: ErrorCode.INCORRECT_TYPE;
    static INVALID_CHANNEL_NAME: ErrorCode.INVALID_CHANNEL_NAME;
    static PUSH_MISCONFIGURED: ErrorCode.PUSH_MISCONFIGURED;
    static OBJECT_TOO_LARGE: ErrorCode.OBJECT_TOO_LARGE;
    static OPERATION_FORBIDDEN: ErrorCode.OPERATION_FORBIDDEN;
    static CACHE_MISS: ErrorCode.CACHE_MISS;
    static INVALID_NESTED_KEY: ErrorCode.INVALID_NESTED_KEY;
    static INVALID_FILE_NAME: ErrorCode.INVALID_FILE_NAME;
    static INVALID_ACL: ErrorCode.INVALID_ACL;
    static TIMEOUT: ErrorCode.TIMEOUT;
    static INVALID_EMAIL_ADDRESS: ErrorCode.INVALID_EMAIL_ADDRESS;
    static MISSING_CONTENT_TYPE: ErrorCode.MISSING_CONTENT_TYPE;
    static MISSING_CONTENT_LENGTH: ErrorCode.MISSING_CONTENT_LENGTH;
    static INVALID_CONTENT_LENGTH: ErrorCode.INVALID_CONTENT_LENGTH;
    static FILE_TOO_LARGE: ErrorCode.FILE_TOO_LARGE;
    static FILE_SAVE_ERROR: ErrorCode.FILE_SAVE_ERROR;
    static DUPLICATE_VALUE: ErrorCode.DUPLICATE_VALUE;
    static INVALID_ROLE_NAME: ErrorCode.INVALID_ROLE_NAME;
    static EXCEEDED_QUOTA: ErrorCode.EXCEEDED_QUOTA;
    static SCRIPT_FAILED: ErrorCode.SCRIPT_FAILED;
    static VALIDATION_ERROR: ErrorCode.VALIDATION_ERROR;
    static INVALID_IMAGE_DATA: ErrorCode.INVALID_IMAGE_DATA;
    static UNSAVED_FILE_ERROR: ErrorCode.UNSAVED_FILE_ERROR;
    static INVALID_PUSH_TIME_ERROR: ErrorCode.INVALID_PUSH_TIME_ERROR;
    static FILE_DELETE_ERROR: ErrorCode.FILE_DELETE_ERROR;
    static REQUEST_LIMIT_EXCEEDED: ErrorCode.REQUEST_LIMIT_EXCEEDED;
    static INVALID_EVENT_NAME: ErrorCode.INVALID_EVENT_NAME;
    static USERNAME_MISSING: ErrorCode.USERNAME_MISSING;
    static PASSWORD_MISSING: ErrorCode.PASSWORD_MISSING;
    static USERNAME_TAKEN: ErrorCode.USERNAME_TAKEN;
    static EMAIL_TAKEN: ErrorCode.EMAIL_TAKEN;
    static EMAIL_MISSING: ErrorCode.EMAIL_MISSING;
    static EMAIL_NOT_FOUND: ErrorCode.EMAIL_NOT_FOUND;
    static SESSION_MISSING: ErrorCode.SESSION_MISSING;
    static MUST_CREATE_USER_THROUGH_SIGNUP: ErrorCode.MUST_CREATE_USER_THROUGH_SIGNUP;
    static ACCOUNT_ALREADY_LINKED: ErrorCode.ACCOUNT_ALREADY_LINKED;
    static INVALID_SESSION_TOKEN: ErrorCode.INVALID_SESSION_TOKEN;
    static LINKED_ID_MISSING: ErrorCode.LINKED_ID_MISSING;
    static INVALID_LINKED_SESSION: ErrorCode.INVALID_LINKED_SESSION;
    static UNSUPPORTED_SERVICE: ErrorCode.UNSUPPORTED_SERVICE;
    static AGGREGATE_ERROR: ErrorCode.AGGREGATE_ERROR;
    static FILE_READ_ERROR: ErrorCode.FILE_READ_ERROR;
    static X_DOMAIN_REQUEST: ErrorCode.X_DOMAIN_REQUEST;

    code: ErrorCode;
    message: string;

    constructor(code: ErrorCode, message: string);
  }

  /**
   * A Moralis.Op is an atomic operation that can be applied to a field in a
   * Moralis.Object. For example, calling <code>object.set("foo", "bar")</code>
   * is an example of a Moralis.Op.Set. Calling <code>object.unset("foo")</code>
   * is a Moralis.Op.Unset. These operations are stored in a Moralis.Object and
   * sent to the server as part of <code>object.save()</code> operations.
   * Instances of Moralis.Op should be immutable.
   *
   * You should not create subclasses of Moralis.Op or instantiate Moralis.Op
   * directly.
   */
  namespace Op {
    interface BaseOperation {
      objects(): any[];
    }

    interface Add extends BaseOperation {
      toJSON(): any;
    }

    interface AddUnique extends BaseOperation {
      toJSON(): any;
    }

    interface Increment {
      amount: number;
      toJSON(): any;
    }

    interface Relation {
      added(): Object[];
      removed: Object[];
      toJSON(): any;
    }

    interface Set {
      value(): any;
      toJSON(): any;
    }

    interface Unset {
      toJSON(): any;
    }
  }

  /**
   * Contains functions to deal with Push in Moralis
   */
  namespace Push {
    function send<T>(data: PushData, options?: SendOptions): Promise<T>;

    interface PushData {
      channels?: string[];
      push_time?: Date;
      expiration_time?: Date;
      expiration_interval?: number;
      where?: Query<Installation>;
      data?: any;
      alert?: string;
      badge?: string;
      sound?: string;
      title?: string;
      notification?: any;
      content_available?: any;
    }

    interface SendOptions extends UseMasterKeyOption {
      success?: () => void;
      error?: (error: Error) => void;
    }
  }

  /**
   * Call this method first to set up your authentication tokens for Moralis.
   * You can get your keys from the Data Browser on moralis.io.
   * @param applicationId Your Moralis Application ID.
   * @param javaScriptKey (optional) Your Moralis JavaScript Key (Not needed for moralis-server)
   * @param masterKey (optional) Your Moralis Master Key. (Node.js only!)
   */
  function initialize(applicationId: string, javaScriptKey?: string, masterKey?: string): void;

  /**
   * Use this to set custom headers
   * The headers will be sent with every moralis request
   */
  namespace CoreManager {
    function set(key: string, value: any): void;
    function get(key: string): void;
  }

  /**
   * Additionally on React-Native / Expo environments, add AsyncStorage from 'react-native' package
   * @param AsyncStorage AsyncStorage from 'react-native' package
   */
  function setAsyncStorage(AsyncStorage: any): void;

  /**
   * Gets all contents from Local Datastore.
   */
  function dumpLocalDatastore(): Promise<{ [key: string]: any }>;

  /**
   * Enable pinning in your application.
   * This must be called before your application can use pinning.
   */
  function enableLocalDatastore(): void;

  /**
   * Flag that indicates whether Local Datastore is enabled.
   */
  function isLocalDatastoreEnabled(): boolean;

  function setLocalDatastoreController(controller: any): void;

  /**
   * Call this method to set your LocalDatastoreStorage engine
   * If using React-Native use {@link Moralis.setAsyncStorage Moralis.setAsyncStorage()}
   * @param controller a data storage.
   */
  function setLocalDatastoreController(controller: any): void;

  /**
   * Enable the current user encryption.
   * This must be called before login any user.
   */
  function enableEncryptedUser(): void;

  /**
   * Flag that indicates whether Encrypted User is enabled.
   */
  function isEncryptedUserEnabled(): boolean;
}

export default Moralis;
export type Moralis = typeof Moralis;
