import { UnsubscribeFunction } from "callback-registry";

/**
 * Factory function that creates a new io.Connect instance.
 */
export type IOConnectCoreFactoryFunction = (config?: IOConnectCore.Config, ext?: IOConnectCore.Extension) => Promise<IOConnectCore.API>;
declare const IOConnectCoreFactory: IOConnectCoreFactoryFunction;
export default IOConnectCoreFactory;

// tslint:disable-next-line:no-namespace
export namespace IOConnectCore {

    /** Optional configuration object when initializing the io.Connect library. */
    export interface Config {
        /**
         * Application name. If not specified, the value depends on the hosting environment.
         * For the browser - `document.title + random number` (e.g., ClientList321333).
         * In desktop - `containerName + browserWindowName` (e.g., Internal.ClientList).
         */
        application?: string;

        /** Configurations for the io.Connect Gateway connection. */
        gateway?: GatewayConfig;

        /** Metrics configurations. */
        metrics?: boolean | MetricsConfig;

        /** Enable, disable and configure the Contexts API. */
        contexts?: boolean | ContextsConfig;

        /** Enable or disable the Pub/Sub API. */
        bus?: boolean;

        /** Defines logging levels per output target. */
        logger?: IOConnectCore.Logger.LogLevel | LoggerConfig;

        /** Pass this to override the build-in logger and handle logging on your own */
        customLogger?: IOConnectCore.CustomLogger;

        /**
         * Authentication can use one of the following flows:
         * - username/password;
         * - `token` - access tokens can be generated after successful login from the Auth Provider (e.g., Auth0);
         * - `gatewayToken` - Gateway tokens are time limited tokens generated by the Gateway after an explicit request. To generate one, use the `io.connection.authToken()` method;
         * - `sspi` - using `sessionId` and authentication challenge callback;
         */
        auth?: IOConnectCore.Auth | string;

        /**
         * Specify custom identity fields. Those can also override some of the system fields assigned
         */
        identity?: { [key: string]: string | number | boolean };
    }

    /** Configurations for the io.Connect Gateway connection. */
    export interface GatewayConfig {
        /** URL for the WebSocket connections to the Gateway. */
        ws?: string;

        /** Legacy (Version of the Gateway that you are connected to)  */
        protocolVersion?: number;

        /**
         * Reconnect interval in milliseconds.
         * @default 500
         */
        reconnectInterval?: number;

        /**
         * Number of reconnect attempts.
         * @default 10
         */
        reconnectAttempts?: number;

        /** A way to pass custom token provider for Gateway v.3 tokens. */
        gwTokenProvider?: GwTokenProvider;

        /**
         * @deprecated
         * Path to the shared worker file that contains glue0 shared worker related code
         */
        sharedWorker?: string;

        /**
         * An object containing the configuration settings when core is operating in a web platform environment
         */
        webPlatform?: WebPlatformConnection;

        /** Connect with GW in memory */
        inproc?: InprocGWSettings;

        /**
         * @ignore
         * if `protocolVersion` is 3, this is used to create the connection `replayer`
         * property. Allows out-of-band subscription and replaying of io.Connect messages.
         */
        replaySpecs?: IOConnectCore.Connection.MessageReplaySpec[];
    }

    export interface WebPlatformConnection {
        port: MessagePort;
        allowedOrigins?: string[];
        windowId?: string;
    }

    export interface InprocGWSettings {
        facade: IOConnectCore.Connection.GW3Facade;
    }

    /**
     * A way to pass custom token provider for Gateway v.3 tokens.
     * @ignore
     */
    export interface GwTokenProvider {
        get: () => string;
    }

    /** Metrics configurations. */
    export interface MetricsConfig {

        /**
         *  If `false` (default), an App system will be created on top level, and all other metrics will live in it.
         *  If `true`, an App system will be created, and all metrics will live on top level.
         */
        disableAutoAppSystem?: boolean;

        pagePerformanceMetrics?: PagePerformanceMetricsConfig;
    }

    /** Contexts configurations. */
    export interface ContextsConfig {
        /** Subscribes to all known contexts, which allows io.Connect to re-announce as many contexts as possible when a gateway reconnection happens */
        trackAllContexts?: boolean;

        /** Enables or disables re-announcing known contexts when a gateway reconnection happens */
        reAnnounceKnownContexts?: boolean;
    }

    export interface PagePerformanceMetricsConfig {
        enabled: boolean;
        initialPublishTimeout: number;
        publishInterval: number;
    }

    export interface Extension {
        /* Array of libs to be injected to io.Connect - use only in libraries that wrap `&commat;interopio/core`. */
        libs?: ExternalLib[];

        /* Allow to override version - use only in libraries that wrap `&commat;interopio/core` **/
        version?: string;

        /* Allow wrappers to enrich IOConnect object **/
        enrichGlue?: (glue: API) => void;

        /* Extension options object that will be merged to the config object of `&commat;interopio/core` **/
        extOptions?: any;
    }

    export interface ExternalLib {
        name: string;
        create: (core: API) => any;
    }

    /**
     * Authentication can use one of the following flows:
     * * username/password;
     * * `token` - access tokens can be generated after successful login from the Auth Provider (e.g., Auth0);
     * * `gatewayToken` - Gateway tokens are time limited tokens generated by the Gateway after an explicit request. To generate one, use the `io.connection.authToken()` method;
     * * `sspi` - using `sessionId` and authentication challenge callback;
     */
    export interface Auth {
        /** Username to be used */
        username?: string;

        /** Password to be used */
        password?: string;

        flowName?: "sspi";

        flowCallback?: (sessionId: string, token: any) => Promise<{ data: any }>;

        sessionId?: string;

        /**
         * Authenticate using token generated from the auth provider.
         */
        token?: string;

        /**
         * Authenticate using gatewayToken
         */
        gatewayToken?: string;

        /** GW auth provider to be used */
        provider?: string;

        /** A context object, which will be passed along to the GW auth provider to be used to request verification */
        providerContext?: any;
    }

    /**
     * Describes a custom logger object
     */
    export interface CustomLogger {
        debug(message?: any, ...optionalParams: any[]): void;
        error(message?: any, ...optionalParams: any[]): void;
        info(message?: any, ...optionalParams: any[]): void;
        log(message?: any, ...optionalParams: any[]): void;
        warn(message?: any, ...optionalParams: any[]): void;
    }

    export interface API {
        /** Connection library. */
        connection: IOConnectCore.Connection.API;

        logger: IOConnectCore.Logger.API;

        /** Interop library. */
        agm: IOConnectCore.Interop.API;

        /** Interop library. */
        interop: IOConnectCore.Interop.API;

        /** Pub/Sub library. */
        bus: IOConnectCore.Bus.API;

        /** Metrics library. */
        metrics: IOConnectCore.Metrics.API;

        /** Contexts library. */
        contexts: IOConnectCore.Contexts.API;

        /** Brings up the IOConnect Desktop feedback dialog. */
        feedback(info?: FeedbackInfo): void;

        /** Info object containing versions of all included libraries and io.Connect itself. */
        info: object;

        /** io.Connect version. */
        version: string;

        /**
         * @ignore
         * Performance data
         */
        performance?: object;

        /**
         * @ignore
         * Config as passed from the user
         */
        userConfig?: Config;

        /**
         * @ignore
         * The config used by the library. This is a transformed and possibly extended version of the userConfig object
         */
        config?: any;

        /**
         * Disposes the io.Connect API. This will remove all Interop methods and streams registered by the application.
         */
        done(): Promise<void>;
    }

    /**
     * Allows customizing the feedback form
     */
    export interface FeedbackInfo {
        /** Will be added to the description field in the feedback form */
        message?: string;
    }

    /**
     * @intro
     * The Logger API enables JavaScript applications to create a hierarchy of sub-loggers mapped to application components
     * where you can control the level of logging for each component. You can also route the output of log messages (depending on the logging level)
     * to a variety of targets - the developer console or an external output (usually a rolling file on the desktop, but actually any target the `log4net` library supports).
     *
     * The Logger API is accessible through the `io.logger` object.
     */
    export namespace Logger {

        export type LogLevel = "off" | "trace" | "debug" | "info" | "warn" | "error";

        export interface API {

            /** Name of the logger. */
            name: string;

            /** Version of the Logging API. */
            version?: string;

            /**
             * Creates a new logger which is a sub-logger of the current one.
             * @param name Name for the sub-logger.
             */
            subLogger(name: string): Logger.API;

            /**
             * Sets or gets the current threshold level for publishing to a file.
             * @param level Logger level.
             */
            publishLevel(level?: LogLevel): LogLevel | undefined;

            /**
             * Sets or gets the current threshold level for writing to the console.
             * @param level Logger level.
             */
            consoleLevel(level?: LogLevel): LogLevel | undefined;

            /**
             * Logging method.
             * @param message Message to log.
             * @param level Logging level for the message.
             */
            log(message: string, level?: LogLevel): void;

            /**
             * Method for logging messages at "trace" level.
             * @param message Message to log.
             */
            trace(message: string): void;

            /**
             * Method for logging messages at "debug" level.
             * @param message Message to log.
             */
            debug(message: string): void;

            /**
             * Method for logging messages at "info" level.
             * @param message Message to log.
             */
            info(message: string): void;

            /**
             * Method for logging messages at "warn" level.
             * @param message Message to log.
             */
            warn(message: string): void;

            /**
             * Method for logging messages at "error" level.
             * @param message Message to log.
             */
            error(message: string, err?: Error): void;

            /**
             * Checks if the logger can publish a log level
             * @param level
             */
            canPublish(level: LogLevel): boolean;
        }

    }

    /**
     * @intro
     * The Interop API enables applications to:
     *
     * - offer functionality to other applications by registering Interop methods;
     * - discover applications which offer methods;
     * - invoke methods on the user's desktop and across the network;
     * - stream and subscribe for real-time data using the streaming methods of the Interop API;
     *
     * Applications which offer methods and streams are called *Interop servers*, and applications which consume them - *Interop clients*, and collectively - *Interop instances*.
     *
     * ![Interop instances](../../../../images/interop/interop.gif)
     *
     * Any running instance of an application is identified by its *Interop instance*, which is a set of known key/value pairs uniquely identifying an application.
     *
     * The Interop API is accessible through the `io.interop` object.
     */
    export namespace Interop {
        /**
         * Which Interop server(s) to target when invoking Interop methods or subscribing to Interop streams.
         * @default "best"
         */
        export type InstanceTarget = "best" | "all" | "skipMine" | Instance | Instance[];
        /**
         * @docmenuorder 1
         */
        export interface API {

            /** Instance of the current application. */
            instance: Instance;

            /**
             * Registers a new Interop method.
             * @example
             * ```javascript
             * io.interop.register(
             *     {
             *         name: "Sum", // required - method name
             *         accepts: "int a, int b", // optional - parameters signature
             *         returns: "int answer" // optional - result signature
             *     },
             *     (args) => {   // required - handler function
             *         return { answer: args.a + args.b };
             *     }
             * );
             * ```
             * @param name  A unique string or a [`MethodDefinition`](#MethodDefinition) for the method to be registered.
             * @param handler The JavaScript function that will be called when the method is invoked.
             */
            register<T = any, R = any>(name: string | MethodDefinition, handler: (args: T, caller: Instance) => R | void | Promise<R>): Promise<void>;

            /**
             * @ignore
             * Registers a new async Interop method. Async methods can delay returning the result
             * from the method invocation.
             * @param name  A unique string or a [`MethodDefinition`](#MethodDefinition) for the method to be registered.
             * @param handler The JavaScript function that will be called when the method is invoked. Accepts two extra arguments - a `success` and an `error` callbacks.
             * To return a result, you must call the `success` callback, or the `error` callback for errors.
             */
            registerAsync<T = any, R = any>(name: string | MethodDefinition, handler: (args: T, caller: Instance, successCallback: (args?: R) => void, errorCallback: (error?: string | object) => void) => void): Promise<void>;

            /**
             * Unregisters an Interop method.
             * @param definition The unique `name` or the [`MethodDefinition`](#MethodDefinition) of the method to be unregistered.
             */
            unregister(definition: string | MethodDefinition): void;

            /**
             * Invokes an Interop method with some arguments on target servers.
             * @example
             * ```javascript
             * io.interop.invoke(
             *     "Sum",
             *     { a: 37, b: 5 }) // everything else is optional
             *     .then(successResult => {
             *         console.log(`37 + 5 = ${successResult.returned.answer}`);
             *     })
             *     .catch(err => {
             *         console.error(`Failed to execute Sum ${err.message}`);
             *     });
             * ```
             * @param method The unique `name` or the [`MethodDefinition`](#MethodDefinition) of the method to be invoked.
             * @param argumentObj A plain JavaScript object (or JSON) holding key/value pairs passed as named arguments to the handler of the registered Interop method.
             * @param target Specifies which servers to target. Can be one of: "best", "all", [`Instance`](#Instance), `Instance[]`.
             * @param options An optional [`InvokeOptions`] object specifying the timeouts to discover a method and to wait for a method reply.
             * @param success An [`InvokeSuccessHandler`](#InvokeSuccessHandler) handler to be called if the invocation succeeds.
             * @param error An [`InvokeErrorHandler`](#InvokeErrorHandler) handler to be called in case of error.
             */
            invoke<T = any>(method: string | MethodDefinition, argumentObj?: object, target?: InstanceTarget, options?: InvokeOptions, success?: InvokeSuccessHandler<T>, error?: InvokeErrorHandler): Promise<InvocationResult<T>>;

            /**
             * Creates a new Interop stream.
             * @example
             * ```javascript
             * io.interop.createStream(
             *     {
             *         name: "MarketData.LastTrades",
             *         displayName: "Publishes last trades for a symbol",
             *         objectTypes: ["Symbol"],
             *         accepts: "String symbol",
             *         returns: "String symbol, Double lastTradePrice, Int lastTradeSize"
             *     })
             *     .then((stream) =>
             *         setInterval(() =>
             *             stream.push(
             *                 {
             *                     symbol: "GOOG",
             *                     lastTradePrice: 700.91,
             *                     lastTradeSize: 10500
             *                 }),
             *             5000)
             *     )
             *     .catch(console.error);
             * ```
             * @param methodDefinition A unique string or a [`MethodDefinition`](#MethodDefinition) for the stream to be registered.
             * @param options The [`StreamOptions`](#StreamOptions) object allows you to pass several optional callbacks which let your application
             * handle subscriptions in a more detailed manner.
             * @param successCallback An optional handler to be called if the creation of the stream succeeds.
             * @param errorCallback An optional handler to be called in case of an error when creating a stream.
             */
            createStream(methodDefinition: string | MethodDefinition, options?: StreamOptions, successCallback?: (args?: object) => void, errorCallback?: (error?: string | object) => void): Promise<Stream>;

            /** Subscribes to an Interop stream.
             * @example
             * ```javascript
             * io.interop.subscribe(
             *     "MarketData.LastTrades",
             *     {
             *     	   arguments: { symbol: "GOOG" },
             *     	   target: "all"
             *     })
             *     .then((subscription) => {
             *     	    // use subscription
             *     })
             *     .catch((error) => {
             *     	    // subscription rejected or failed
             *     });
             * ```
             * @param methodDefinition The unique `name` or the [`MethodDefinition`](#MethodDefinition) of the stream to subscribe to.
             * @param parameters An optional [`SubscriptionParams`](#SubscriptionParams) object with parameters.
             */
            subscribe(methodDefinition: string | MethodDefinition, parameters?: SubscriptionParams): Promise<Subscription>;

            /**
             * Returns all Interop aware applications.
             * Optionally, the list can be filtered to return only servers providing specific Interop method(s)
             * by passing a `methodFilter`.
             * @param filter An object describing a filter matching one or more Interop methods.
             */
            servers(filter?: MethodFilter): Instance[];

            /**
             * Returns all methods that match the passed filter.
             * If no filter is specified, returns all methods.
             * @param filter An object describing a filter matching one or more Interop methods. If string will match the method by name
             */
            methods(filter?: MethodFilter | string): Method[];

            /**
             * Subscribes to the event which fires when a method is added for the first time by any application.
             * @param callback A handler to be called when the event fires.
             */
            methodAdded(callback: (method: Method) => void): UnsubscribeFunction;

            /** Subscribes to the event which fires when a method is removed from the last application offering it.
             * @param callback A handler to be called when the event fires.
             */
            methodRemoved(callback: (method: Method) => void): UnsubscribeFunction;

            /** Subscribes to the event which fires when an application offering methods is discovered.
             * @param callback A handler to be called when the event fires.
             */
            serverAdded(callback: (server: Instance) => void): UnsubscribeFunction;

            /** Subscribes to the event which fires when an app offering methods stops offering them or exits.
             * @param callback A handler to be called when the event fires.
             */
            serverRemoved(callback: (server: Instance) => void): UnsubscribeFunction;

            /**
             * Subscribes to the event which fires when an application starts offering a method. This will be called every time a server starts offering the method,
             * whereas [`methodAdded()`](#API-methodAdded) will be called only the first time the method is registered.
             */
            serverMethodAdded(callback: (info: {
                server: Instance;
                method: Method;
            }) => void): UnsubscribeFunction;

            /**
             * Subscribes for the event which fires when a server stops offering a method. This will be called every time a server stops offering the method,
             * whereas [`methodRemoved()`](#API-methodRemoved) will be called only when the method is removed from the last application offering it.
             * @param callback A handler to be called when the event fires.
             */
            serverMethodRemoved(
                callback: (
                    info: {
                        server: Instance;
                        method: Method;
                    }
                ) => void): UnsubscribeFunction;

            /**
             * Returns all Interop methods registered by a server.
             * @param server An Interop [`Instance`](#Instance) identifying an application.
             */
            methodsForInstance(server: Instance): Method[];

            /**
             * Wait for a method to be available. If the method is already registered this will resolve immediately
             * otherwise will wait until the method appears
             * @param name Name of the method to wait for
             */
            waitForMethod(name: string): Promise<Method>;
        }

        /** Optional object with parameters passed to [`subscribe()`](#API-subscribe) when subscribing to a stream. */
        export interface SubscriptionParams {

            /**
             * Timeout to discover the stream, if not immediately available.
             * @default 30000
             */
            waitTimeoutMs?: number;

            /** Specifies which servers to target. Can be one of: "best", "all", [`Instance`](#Instance), `Instance[]`. */
            target?: InstanceTarget;

            /** A plain JavaScript object (or JSON) holding key/value pairs passed as named arguments to the handler of the registered Interop stream. */
            arguments?: object;

            /**
             * Timeout to wait for a stream reply.
             * @default 30000
             */
            methodResponseTimeout?: number;

            /** Subscribe for the event which fires when new data is received. */
            onData?: (data: StreamData) => void;

            /** Subscribe for the event which fires when the subscription is closed. */
            onClosed?: () => void;

            /** Subscribe for the event which your subscription is connected to a server */
            onConnected?: (server: Instance, reconnect: boolean) => void;
        }

        /** A subscription request object handled by the server that has created the stream. It can be accepted or rejected. */
        export interface SubscriptionRequest {

            /** Instance of the application that wants to subscribe to the stream. */
            instance: Instance;

            /** Arguments passed with the subscription request. */
            arguments: any;

            /** Accepts the subscription request. */
            accept(): void;

            /**
             * Accepts the request on a stream branch.
             * @param branchKey Key of the branch on which to accept the request.
             */
            acceptOnBranch(branchKey: string): void;

            /**
             * Rejects the request.
             * @param reason Reason for rejecting the request.
             */
            reject(reason?: string): void;
        }
        /** Optional handlers that can be supplied when creating streams. */
        export interface StreamOptions {

            /**
             * Subscribes for subscription requests. These can be accepted, rejected or accepted on a custom branch.
             * If this handler is attached, each request should be explicitly accepted.
             */
            subscriptionRequestHandler?: (request: SubscriptionRequest) => void;

            /** Subscribes to the event which fires when a stream subscription is added. */
            subscriptionAddedHandler?: (request: StreamSubscription) => void;

            /** Subscribes to the event which fires when a stream subscription is removed. */
            subscriptionRemovedHandler?: (request: StreamSubscription) => void;
        }

        /**
         * Object describing an Interop stream.
         */
        export interface Stream {

            /** Stream definition object. */
            definition: MethodDefinition;

            /** Name of the stream. */
            name: string;

            /**
             * Push data to the stream. If a `branches` argument is passed, the data will be sent to the specified stream branch(es) only.
             * @param data Data to push.
             * @param branches To which branch(es) to push data.
             */
            push(data: object, branches?: string | string[]): void;

            /**
             * Returns the list of available stream branches.
             *
             * @param key Branch key.
             */
            branches(): StreamBranch[];
            branches(key: string): StreamBranch | undefined;
            branches(key?: string): StreamBranch | StreamBranch[] | undefined;

            /**
             * Returns a branch by key.
             * @param key
             */
            branch(key: string): StreamBranch | undefined;

            /** Returns a list of active subscriptions to the stream. */
            subscriptions(): StreamSubscription[];

            /** Closes the stream. This will close all subscriptions. */
            close(): void;
        }

        /** A stream branch created by the application. */
        export interface StreamBranch {

            /** Branch key. */
            key: string;

            /** All subscriptions to that branch. */
            subscriptions(): StreamSubscription[];

            /** Closes the stream branch. */
            close(): void;

            /**
             * Pushes data to this branch only.
             * @param data Data to push.
             */
            push(data: object): void;
        }
        /** An object describing a subscription to an Interop stream. */
        export interface StreamSubscription {

            /** Arguments used when the subscription was made. */
            arguments: any;

            /** The key of the stream branch to which the subscription was made. */
            branchKey: string;

            /** Instance of the subscriber. */
            instance: Instance;

            /** The stream to which the subscription was made. */
            stream: Stream;

            /** Closes the subscription. This will not close the stream. */
            close(): void;

            /**
             * Pushes data to this subscription only.
             * @param data Data to push.
             */
            push(data: object): void;
        }

        /** Stream subscription made by an application. */
        export interface Subscription {

            /** Arguments used to make the subscription. */
            requestArguments: object;

            /**
             * @deprecated use servers
             * Instance of the application providing the stream.
             */
            serverInstance: Instance;

            /** Instances of the applications providing the stream, that we have subscribed to */
            servers: Instance[];

            /** Stream definition. */
            stream: MethodDefinition;

            /** Subscribe for the event which fires when new data is received. */
            onData(callback: (data: StreamData) => void): void;

            /** Subscribe for the event which fires when the subscription is closed. */
            onClosed(callback: (info: OnClosedInfo) => void): void;

            /** Subscribe for the event which fires if the subscription fails. */
            onFailed(callback: (err: any) => void): void;

            /** Subscribe for the event which your subscription is connected to a server */
            onConnected(callback: (server: Instance, reconnect: boolean) => void): void;

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

        /** Addition information around subscription being closed */
        export interface OnClosedInfo {
            message: string;
            requestArguments: object;
            server: Instance;
            stream: MethodDefinition;
        }

        /** Stream data received by the subscriber. */
        export interface StreamData {

            /** Data from the stream. */
            data: any;

            /** Instance of the application publishing the stream. */
            server: Instance;

            /** Arguments used when the subscription was made. */
            requestArguments?: any;

            /** Message from the publisher of the stream. */
            message?: string;

            /** If `true`, the data was sent to this application only. */
            private: boolean;
        }

        /** An object describing an Interop method registered by an application. */
        export interface MethodDefinition {

            /** The name of the method. Must be unique. */
            name: string;

            /** The entities this method is meant to work with. */
            objectTypes?: string[];

            /** The actual name of the method, used in UI applications. */
            displayName?: string;

            /** Signature describing the parameters that the method expects. */
            accepts?: string;

            /** Signature describing the properties of the object the method returns. */
            returns?: string;

            /** Description of what the method does. Useful for documentation purposes and for UI clients. */
            description?: string;

            /** Method version. */
            version?: number;

            /** If `true`, the method is a stream. */
            supportsStreaming?: boolean;

            /** Optional flags attached to the method */
            flags?: { [key: string]: any };

            /** Returns all servers that provide the method. */
            getServers?(): Instance[];
        }

        /** An object describing a filter matching one or more Interop methods. */
        export interface MethodFilter {

            /** The name of the method. Must be unique. */
            name?: string;

            /** The entities this method is meant to work with. */
            objectTypes?: string[];

            /** The actual name of the method, used in UI applications. */
            displayName?: string;

            /** Signature describing the parameters that the method expects. */
            accepts?: string;

            /** Signature describing the properties of the object the method returns. */
            returns?: string;

            /** Description of what the method does. Useful for documentation purposes and for UI clients. */
            description?: string;
        }

        /** An interop method */
        export interface Method extends MethodDefinition {
            /** The entities this method is meant to work with. */
            objectTypes: string[];

            /** If `true`, the method is a stream. */
            supportsStreaming: boolean;

            /** Optional flags attached to the method */
            flags: { [key: string]: any };

            /** Returns all servers that provide the method. */
            getServers(): Instance[];
        }

        /** Each Interop application is identified by its Interop instance, which is a set of known key/value pairs. */
        export interface Instance {

            /** Unique application name. */
            application?: string;

            /** Application name */
            applicationName?: string;

            /** Process ID of the instance. */
            pid?: number;

            /** Name of the machine the instance is running on. */
            machine?: string;

            /** Name of the user who has started the instance. */
            user?: string;

            /** Environment in which the application is running. */
            environment?: string;

            /** Region in which the application is running. */
            region?: string;

            /** Service string of the application. */
            service?: string;

            /** (IOConnect Desktop) Unique string identifying the application. */
            instance?: string;

            /** (IOConnect Desktop) Window ID of the instance. Only set if running in a IOConnect window. */
            windowId?: string;

            /** (IOConnect Desktop) Gateway peer ID of the instance. */
            peerId?: string;

            /** (IOConnect Desktop) A flag indicating whether the instance is running on a local machine or not. Taken into account when a Gateway mesh is present - local instances are preferred when invoking methods. */
            isLocal?: boolean;

            /** API version */
            api?: string;

            /** Returns all methods registered by that instance. */
            getMethods?(): Method[];

            /** Returns all streams registered by that instance. */
            getStreams?(): Method[];
        }

        /** Method invocation options. */
        export interface InvokeOptions {

            /**
             * Timeout to discover the method, if not immediately available.
             * @default 30000
             */
            waitTimeoutMs?: number;

            /**
             * Timeout to wait for a method reply.
             * @default 30000
             */
            methodResponseTimeoutMs?: number;
        }

        /** Result from a method invocation. */
        export interface InvocationResultCore<T> {

            /** Returned object. */
            returned: T;

            /** Method definition of the method that was invoked. */
            method: MethodDefinition;

            /** Instance of the application that executed the method. */
            executed_by?: Instance;

            /** Arguments of the invocation. */
            called_with: any;

            /** Message from the application that executed the method. */
            message: string;

            /** Status sent by the application that executed the method. */
            status?: number;
        }

        /** Extends [`InvocationResultCore`](#InvocationResultCore). Results from a method invocation. */
        export interface InvocationResult<T = any> extends InvocationResultCore<T> {

            /** An array of invocation results. */
            all_return_values?: Array<InvocationResultCore<T>>;

            /** An array of error objects. */
            all_errors?: any[];
        }

        /**
         * Handler to be called if the method invocation succeeds.
         * @param result Result from the method invocation.
         */
        export type InvokeSuccessHandler<T> = (result: InvocationResult<T>) => void;

        /**
         * Handler to be called in case of method invocation error.
         * @param error An error object.
         */
        export type InvokeErrorHandler = (error: {
            method: MethodDefinition;
            called_with: object;
            executed_by: Instance;
            message: string;
            status: number;
            returned: object;
        }) => void;
    }

    /**
     * Interop legacy alias
     * @ignore
     */
    export import AGM = Interop;

    /**
     * @ignore
     * @intro
     * The io.Connect Gateway is a transport with domain specific protocols. It enables the communication between applications running in **io.Connect Desktop**.
     * By default, it uses WebSockets for delivering messages to applications.
     *
     * The Connection module is used to provide connectivity between the io.Connect JavaScript modules (Interop, Metrics, etc.) and the io.Connect Gateway.
     * Applications can use different events (`connected`, `disconnected`) to show connectivity status.
     */
    export namespace Connection {
        /**
         * Settings used to initialize connection library
         *
         * @ignore
         */

        /** @ignore */

        /**
         * Connection to the io.Connect Gateway.
         */
        export interface API {
            peerId: string;
            token: string;
            info: object;
            resolvedIdentity: object;
            availableDomains: GWDomainInfo[];
            gatewayToken?: string;
            replayer?: MessageReplayer;
            isConnected: boolean;

            /**
             * Protocol version of the current connection.
             */
            protocolVersion: number;
            /**
             * Send a new message using the connection
             *
             * @ignore
             */
            send(message: object, options?: SendMessageOptions): Promise<void>;
            /**
             * Subscribe for messages. Returns an object that can be used to unsubscribe
             *
             * @ignore
             */
            on<T>(type: string, messageHandler: (msg: T) => void): {
                type: string;
                id: number;
            };
            /**
             * Cancel subscription for message types
             *
             * @ignore
             */
            off(info: {
                type: string;
                id: number;
            }): void;

            /** @ignore */
            logout(): void;

            /**
             * Subscribes for the event which fires when the connection has managed to login (only for connections that support authentication).
             * @param callback Event handler function.
             */
            loggedIn(callback: (() => void)): () => void;

            /**
             * Subscribes for the event which fires when connected.
             * @param callback Event handler function.
             */
            connected(callback: (server: string) => void): () => void;

            /**
             * Subscribes for the event which fires when disconnected.
             */
            disconnected(callback: () => void): () => void;

            /**
             * Creates a domain wrapper used to handles domain session lifetime and events for a given connection/domain pair
             */
            domain(domain: string, successMessages?: string[], errorMessages?: string[]): GW3DomainSession;

            /**
             * Generates a new token that can be passed to another application and used to authenticate as the same user.
             * The token is one off and has time restricted validity.
             * The returned token can be then used when initializing IO Connect:
             *
             * ```javascript
             * IOConnectCore({
             *  gateway: {
             *    gatewayToken: token
             *  }
             * })
             * ```
             */
            authToken(): Promise<string>;

            reconnect(): Promise<void>;

            /** @ignore */
            switchTransport(settings: TransportSwitchSettings): Promise<{ success: boolean }>;

            /** @ignore */
            onLibReAnnounced(callback: (lib: { name: "interop" | "contexts" }) => void): UnsubscribeFunction;

            /** @ignore */
            setLibReAnnounced(lib: { name: "interop" | "contexts" }): void;
        }
        /**
         * GW3 domain session
         *
         * @ignore
         */
        export interface GW3DomainSession {
            peerId: string;
            domain: string;
            /**
             * Joins the domain.
             */
            join(options?: object): Promise<object>;
            /**
             * Leaves the domain.
             */
            leave(): Promise<void>;
            /**
             * Subscribe for join events (for the specific domain).
             * The wasReconnect flag indicates if this is auto join after connection drop
             */
            onJoined(callback: (wasReconnect: boolean) => void): any;
            /**
             * Subscribe for leave events (for the specific domain).
             */
            onLeft(callback: () => void): any;
            /**
             * Send a message to GW.
             * A promise that is resolved when a success result for the is received or rejected if the GW returns an error
             */
            send<T>(msg: object, tag?: object, options?: SendMessageOptions): Promise<T>;
            /**
             * Use this to send a message to GW if you don't care about the result.
             * You may pass requestId or it will be generated internally
             */
            sendFireAndForget(msg: {
                request_id?: string;
                [key: string]: any;
            }): Promise<void>;
            /**
             * Subscribe for messages from GW
             */
            on<T>(type: string, callback: (msg: T) => void): void;
            loggedIn(callback: (() => void)): void;
            connected(callback: (server: string) => void): void;
            disconnected(callback: () => void): void;
        }

        /** @ignore */
        export interface TransportSwitchSettings {
            type: "default" | "secondary";
            transportConfig?: {
                auth: IOConnectCore.Auth;
                url: string;
            };
        }

        /** @ignore */
        export interface GW3Facade {
            connect(handler: (client: GW3Client, msg: object) => void): Promise<GW3Client>;
        }

        /** @ignore */
        export interface GW3Client {
            send(msg: object): void;
        }
        /** @ignore */
        export interface SendMessageOptions {
            skipPeerId?: boolean;
            retryInterval?: number;
            maxRetries?: number;
        }

        /**
         * Allows out-of-band subscription to Gateway messages in `protocolVersion: 3`.
         * @ignore
         */
        export interface MessageReplaySpec {
            /**
             * Used to identify a set of message types,
             * like "context" for ["create-context", "subscribe-context", "subscribed-context", ...]
             */
            name: string;

            /**
             * The types of the messages corresponding to this name.
             */
            types: string[];
        }
        /**
         * @ignore
         */
        export interface MessageReplayer {
            init(connection: Connection.API): void;

            processMessage(type: string, msg: object): void;

            drain(name: string, callback: (msg: object) => void): void;
        }

        /**
         * @ignore
         */
        export interface GWDomainInfo {
            uri: string;
            version: number;
        }
    }

    /**
     * @intro
     * To improve the efficiency of your business processes, you often find the need to collect extensive data about the daily workflows and routines within your company.
     * You want to see the specific actions your employees take and the choices they make when achieving outstanding results or when performing poorly,
     * so that you can optimize your processes or tools. Metrics data is also useful for technical purposes like monitoring how well your hardware infrastructure handles the workload
     * or to track the performance of your applications.
     *
     * The Metrics API is accessible through the `io.metrics` object.
     */
    export namespace Metrics {
        /** @docmenuorder 1 */

        export interface API extends System {
            /**
             * The feature metric is under the subsystem with name "reporting".
             * @param feature The main feature you want to gather information about.
             * @param action The specific action you want to track.
             * @param value The payload of the metric - the value(s) you are interested in.
             *
             * @example
             * ```javascript
             * io.metrics.featureMetric("export", "exportToExcel", "file.xls")
             * ```
             */
            featureMetric(feature: string, action: string, value: string): void;
        }

        /** Metrics systems repository. */
        export interface Repository {

            /** Returns the root metrics system. */
            root: System;
        }

        /** A metrics system. A system can be created in another system as a subsystem. */
        export interface System {

            /** Returns the name of the system. */
            name: string;

            /** Returns the description of the system. */
            description: string;

            /** Returns the repository of the system. */
            repo: Repository;

            /** Returns the parent system of the current system. */
            parent?: System;

            /** An array of parent system names, starting with the root system name. */
            path: string[];

            /** ID of the system in the format `RootName/ParentName/.../CurrentSystemName`. */
            id: string;

            /** The root system in the repository. */
            root: System;

            /** Returns an array of the subsystems. */
            subSystems: System[];

            /** Returns an array of metrics. */
            metrics: Metric[];

            /**
             * Creates a new subsystem.
             * @param name Name for the subsystem.
             * @param description Description for the subsystem.
             */
            subSystem(name: string, description?: string): System;

            /** Returns the system state. */
            getState(): State;

            /**
             * Updates the state of the system.
             * @param state Value for the state.
             * @param description Description for the state.
             */
            setState(state: number, description?: string): void;

            /** Returns the aggregate system state represented as an array of the states of all subsystems. */
            getAggregateState(): SystemStateInfo[];

            /**
             * Creates a new Number Metric.
             * @param definition Metric definition.
             * @param value Metric value.
             */
            numberMetric(definition: string | MetricDefinition, value: number): NumberMetric;

            /**
             * Creates a new Object Metric.
             * @param definition Metric definition.
             * @param value Metric value.
             */
            objectMetric(definition: string | MetricDefinition, value: any): ObjectMetric;

            /**
             * Creates a new String Metric.
             * @param definition Metric definition.
             * @param value Metric value.
             */
            stringMetric(definition: string | MetricDefinition, value: string): StringMetric;

            /**
             * Creates a new Timestamp Metric.
             * @param definition Metric definition.
             * @param value Metric value.
             */
            timestampMetric(definition: string | MetricDefinition, value: any): TimestampMetric;
        }

        /** State of the metric system. */
        export interface State {
            /** Code number for the state. */
            state?: number;
            /** Description of the state. */
            description?: string;
        }

        /** Metric definition. */
        export interface MetricDefinition {
            /** Name of the metric. */
            name: string;
            /** Description for the metric. */
            description?: string;
        }

        /** Basic metric. */
        export interface Metric {

            /** Returns the name of the metric. */
            name: string;

            /** Returns the description of the metric. */
            description?: string;

            /** Returns the system of the metric. */
            system: System;

            /** Returns the repository of the metric. */
            repo: Repository;

            /** Returns the ID of the metric. */
            id: string;

            /** Returns the type of the metric. */
            type: number;

            /** An array of parent system names, starting with the root system name. */
            path: string[];

            /** Returns the value of the metric. */
            value: any;

            /** Updates the value of the metric. */
            update(value: any): Promise<void>;
        }

        /** Count metric. */
        export interface CountMetric extends Metric {

            /** Returns the value of the metric. */
            value: number;

            /**
             * Updates the value of the metric.
             * @param value Value with which to update the metric.
             */
            update(value: number): Promise<void>;

            /**
             * Increments the value of the metric by the specified number.
             * @param num Number by which to increment the metric.
             */
            incrementBy(num: number): void;

            /** Increments the value of the metric by 1. */
            increment(): void;

            /** Decrements the value of the metric by 1. */
            decrement(): void;

            /**
             * Decrements the value of the metric by the specified number.
             * @param num Number by which to decrement the metric.
             */
            decrementBy(num: number): void;
        }

        /** Number metric. */
        export interface NumberMetric extends Metric {

            /** Returns the value of the metric. */
            value: number;

            /**
             * Updates the value of the metric.
             * @param value Value with which to update the metric.
             */
            update(value: number): Promise<void>;

            /**
             * Increments the value of the metric by the specified number.
             * @param num Number by which to increment the metric.
             */
            incrementBy(num: number): void;

            /** Increments the value of the metric by 1. */
            increment(): void;

            /**  Decrements the value of the metric by 1 */
            decrement(): void;

            /**
             * Decrements the value of the metric by the specified number.
             * @param num Number by which to decrement the metric.
             */
            decrementBy(num: number): void;
        }

        /** Object metric. */
        export interface ObjectMetric extends Metric {

            /** Returns the value of the metric. */
            value: any;

            /**
             * Updates the value of the metric.
             * @param value Value with which to update the metric.
             */
            update(value: any): Promise<void>;
        }

        /** String metric. */
        export interface StringMetric extends Metric {

            /** Returns the value of the metric. */
            value: string;

            /**
             * Updates the value of the metric.
             * @param value Value with which to update the metric.
             */
            update(value: string): Promise<void>;
        }

        /** Timestamp metric. */
        export interface TimestampMetric extends Metric {

            /** Returns the value of the metric. */
            value: any;

            /**
             * Updates the value of the metric.
             * @param value Value with which to update the metric.
             */
            update(value: Date): Promise<void>;

            /** Updates the metric with the current date and time. */
            now(): void;

        }

        /** Aggregate system state. */
        export interface SystemStateInfo extends State {

            /** Name of the metrics system. */
            name?: string;

            /** An array of parent system names, starting with the root system name. */
            path?: string[];
        }
    }

    /**
     * @docname Shared Contexts
     * @intro
     * A shared context is a named object (holding a `map` of key/value pairs) that stores cross application data.
     * The context object can hold any cross-application data. Any application can update a context or subscribe for context updates
     * and react to them by using the name of the context.
     *
     * The Shared Contexts API offers a simple and effective solution for sharing data between your applications.
     * Imagine you have an application showing a list of clients and an application showing client portfolios.
     * What you need, is your "Portfolio" app to show the portfolio of a specific client that the user has selected from the "Clients" app.
     * You can easily achieve this in a few simple steps by using the Shared Contexts API:
     *
     * - instruct the "Clients" app to publish updates to a context object holding the `id` of the currently selected client;
     * - instruct the "Portfolio" app to subscribe for updates of that same context object and specify how the "Portfolio" app should handle the received data in order to update its current state;
     *
     * The Shared Contexts API is accessible through the `io.contexts` object.
     */
    export namespace Contexts {
        export interface API {
            /**
             * Flag indicating if setPath operation is supported.
             * @ignore
             */
            setPathSupported: boolean;

            /**
             * Returns all existing context names. Using the context name you can subscribe for context changes, updates or set context values.
             */
            all(): string[];

            /**
             * Updates a context with the supplied object. This method updates only the specified context properties. Any other existing context properties will remain intact.
             * If the context does not exist, the `update()` method will create it.
             *
             * @example
             * ```javascript
             * io.contexts.update("app-styling",
             *    {
             *        backgroundColor: "red",
             *        alternativeColor: "green"
             *    });
             * ```
             * @param name Name of the context to be updated.
             * @param data The object that will be applied to the context.
             */
            update(name: string, data: any): Promise<void>;

            /**
             * Replaces a context. All properties of the specified context object will be removed and replaced with the ones supplied in the `data` parameter.
             * @param name Name of the context to be replaced.
             * @param data The object that will be applied to the context.
             */
            set(name: string, data: any): Promise<void>;

            /**
             * Sets a path in the context to some value. Use this to update values that are not on top level in the context.
             * @param name Name of the context to be updated
             * @param path Path to be updated. Path should be in the format "prop1.prop2"
             * @param data The object that will be applied to the path
             */
            setPath(name: string, path: string, data: any): Promise<void>;

            /**
             * Sets multiple paths in the context to some values in a single command.
             * @param name Name of the context to be updated
             * @param paths Array of paths and their values to be updated. Path should be in the format "prop1.prop2"
             */
            setPaths(name: string, paths: PathValue[]): Promise<void>;

            /**
             * Subscribes for context events. Returns an unsubscribe function which you can use to stop receiving context updates.
             * @param name Name of the context to which you want to subscribe.
             * @param callback Function that will handle the updates.
             */
            subscribe(name: string, callback: (data: any, delta: any, removed: string[], unsubscribe: () => void, extraData?: any) => void): Promise<() => void>;

            /**
             * Return the context data immediately or asynchronously as soon as any data becomes available.
             * @param name Name of the context from which you want to get data.
             */
            get(name: string): Promise<any>;

            /**
             * Destroys a context and all the data associated with it
             * @param name Name of the context to be removed
             */
            destroy(name: string): Promise<any>;
        }

        export interface PathValue {
            path: string;
            value: any;
        }
    }

    /**
     * @docname Pub Sub
     * @docmenuorder 3
     * @intro
     * The Pub/Sub API enables applications to:
     *
     * - publish messages on a specific topic;
     * - subscribe for messages on a specific topic;
     *
     * When an application publishes a message on a specific topic, the Pub/Sub API delivers it to other applications that have subscribed to that topic.
     *
     * The "raw Pub/Sub" support allows **io.Connect Desktop** to work with applications already using a Pub/Sub technology.
     * Before writing new Pub/Sub based code, please consider the higher level Interop services provided by **io.Connect Destop**: Request/Response, Streaming, Discovery and Shared Contexts.
     * Utilizing these services, instead of creating them from scratch, can save you time and also provide you with a more robust service that can interact with applications by different dev teams and vendors.
     *
     * The Pub/Sub API is accessible through the `io.bus` object.
     */
    export namespace Bus {
        export interface API {
            /**
             * Publishes the provided data on a specific topic.
             * An optional object can be provided to publish a message to specific peers.
             *
             * @example
             * ```javascript
             * io.bus.publish("prices", { RIC: "VOD.L", price: 21.2 })
             * ```
             * @param topic Topic on which to publish the message.
             * @param data Data to publish.
             * @param options An optional object with message options.
             */
            publish(
                topic: string,
                data: object,
                options?: MessageOptions
            ): void;

            /**
             * Subscribe for receiving data published on specific topic on the message bus. The provided callback will be invoked for each received message.
             * An optional object can be provided to receive messages published only by specific peers.
             *
             * Returns a `Promise` which resolves if the subscription is successful. The `Promise` resolves with a subscription object which can be used to unsubscribe and stop receiving messages.
             *
             * @example
             * ```javascript
             * io.bus.subscribe(
             *     "prices",
             *     function (data, topic, source) {
             *         console.log(data, topic, source);
             *   })
             * ```
             * @param topic Topic to which to subscribe.
             * @param callback Function that will handle the received data.
             * @param options An optional object with message options.
             */
            subscribe(
                topic: string,
                callback: (data: object, topic: string, source: IOConnectCore.Interop.Instance) => void,
                options?: MessageOptions
            ): Promise<Subscription>;
        }

        /**
         * If `routingKey` is provided when publishing a message, the message will be delivered only to peers that have provided the same routing key
         * or no routing key during subscription.
         * If `routingKey` is provided when subscribing for a topic, only messages that have been published with a matching routing key or no routing key
         * at all will be received by the subscriber.
         * If `target` is provided when publishing a message, the message will be delivered only to subscribers for which all properties of the target object
         * match the respective fields on the subscribers identity.
         * If `target` is provided when subscribing for a topic, the provided object will be compared to the identity of the publishers of the messages
         * and only if they match will the messages be received by the subscriber.
         */
        export interface MessageOptions {

            /** Routing key specified by the publisher/subscriber. */
            routingKey?: string;

            /** Target specified by the publisher/subscriber. */
            target?: object;
        }

        /**
         * Subscription object that can be used to unsubscribe from a topic and stop receiving data.
         */
        export interface Subscription {
            unsubscribe: () => Promise<void>;
        }
    }

    /** @ignore */
    export interface IOConnectDesktopObject {
        version: string;
    }

    /** @ignore */
    export interface GDObject {
        /** Id of the window */
        windowId: string;
        /** Name of the application running in the window */
        appName: string;
        /** Name of the application running in the window */
        applicationName: string;
        application: string;
        /** Instance of the application running in the window */
        appInstanceId: string;
        gwURL: string;
        pid: number;
        env: {
            env: string;
            machineName: string;
            region: string;
            windowsUserDomain: string;
            windowsUserId: string;
            windowsUserName: string;
        };
        activityInfo: {
            activityId: string,
            activityType: string,
            windowType: string,
            windowName: string,
            gwToken: string,
            isOwner: boolean
        };
        metrics?: {
            pagePerformanceMetrics: {
                enabled: boolean;
                initialPublishTimeout: number;
                publishInterval: number;
            }
        };
        consoleLogLevel: IOConnectCore.Logger.LogLevel | undefined;
        updatePerfData: (perf: object) => void;
        getMetricsPublishingEnabled: () => boolean;
        getGWToken: () => Promise<string>;
        getWindowInfo: (id: string) => {
            applicationName: string;
            activityId?: string;
            activityWindowId?: string;
        };
    }

    export interface LoggerConfig {
        /** Console logging level. */
        console?: IOConnectCore.Logger.LogLevel;
        /** File logging level. To allow logging to files in your apps, you must set the `"allowLogging"` top-level property to `true` in the app definition. */
        publish?: IOConnectCore.Logger.LogLevel;
    }
}

declare global {
    interface Window {
        glueDesktop?: IOConnectCore.IOConnectDesktopObject;
        glue42gd?: IOConnectCore.GDObject;
        gdPreloadPromise: Promise<IOConnectCore.GDObject>;
        glue42electron?: {
            context: unknown;
            env: string;
            region: string;
            gwToken: string;
            gwURL: string;
            instanceId: string;
            application: string;
            pid: number;
        };
    }
}
