/*!
 * Copyright 2017 - 2020 by ChartIQ, Inc.
 * All rights reserved.
 */
/**
 * <h2>Deprecated: Linker Client (Finsemble Workspaces)</h2>
 *
 * The Linker API has been DEPRECATED. Please use <a href="/docs/connect-apps/interop/">FDC3 interop</a> which provides an industry standard API for exchanging
 * context between apps, including channel linking.
 *
 * The Linker API allows components to synchronize on a piece of data. For instance, an end user can use the Linker to link multiple components by stock symbol.
 * Use the Linker API to enable your components to participate in this synchronization.
 *
 * In order for components to be linked, the components must understand the data format that will be passed between them (the "context"), and agree on a label that identifies that format (the <code>dataType</code>).
 * For instance, components might choose to publish and subscribe to a `dataType` called "symbol".
 * They would then also need to agree what a "symbol" looks like, for instance, `{symbol: "IBM"}`.
 * The Linker API doesn't proscribe any specific format for context or set of labels.
 *
 * Behind the scenes, the Linker Service coordinates Linker activity between components. It keeps track of the available channels and channel assignments.
 * It uses a dedicated store (<a href="/docs/productivity/API-tutorials-and-refs/storage/DistributedStore">the Distributed Store</a>) to maintain this information and also persists the information to workspaces (<a href="/docs/reference/typedocs/modules/WorkspaceClient">Workspace Client</a>).
 *
 * The Linker Client frequently uses the parameters <code>windowIdentifier</code> and <code>componentType</code>. <a href="/docs/productivity/componentTypesAndWindowNames">Learn more about them here</a>.
 *
 * For more information, see the <a href="/docs/deprecated/Linking">Linking tutorial</a>.
 *
 * @module LinkerClient
 */
import WindowClient from "../clients/windowClient";
import DistributedStoreClient from "../clients/distributedStoreClient";
import Logger from "../clients/logger";
import deepEqual from "lodash/isEqual";
import { bringWindowsToFront } from "../common/Desktop";
import { hyperFocus } from "./deprecatedlauncherClient";
import { RouterClient } from "../clients/routerClient";
import SystemManagerClient from "../common/systemManagerClient";
Logger.system.debug("Starting LinkerClient");
const sysinfo = Logger.system.info;
const sysdebug = Logger.system.debug;
function makeKey(windowIdentifier) {
    return windowIdentifier.windowName.replace(/\./g, "_");
}
/**
 * A convenience function to send data to a callback and also return it
 * @private
 */
function asyncIt(data, cb) {
    if (cb)
        cb(null, data);
    return data;
}
/**
 * @phantom Don't display in Typedoc
 */
export class LinkerClient {
    constructor() {
        this.stateChangeListeners = [];
        // Linker Data
        this.allChannels = [];
        this.channels = {};
        this.clients = {};
        this.channelListenerList = [];
        // Used to keep track of each router listener that is enabled
        this.dataListenerList = {};
        /**
         * Handles listeners (looks to see if there is a listener for a specific data type)
         * @private
         */
        this.handleListeners = (err, data) => {
            // Need to do this better. Get newest items so we don't create it every time
            const listeners = this.dataListenerList[data.data.type];
            if (listeners && listeners.length > 0) {
                for (let i = 0; i < listeners.length; i++) {
                    listeners[i](data.data.data, {
                        data: data.data.data,
                        header: data.header,
                        originatedHere: data.originatedHere,
                    });
                }
            }
        };
        /**
         * @private
         */
        this.onClose = (cb) => {
            var _a, _b;
            const wi = WindowClient.getWindowIdentifier();
            const key = makeKey(wi);
            if (this.clients[key]) {
                this.clients[key] = null;
                this.updateClientInStore(key);
            }
            (_a = this.linkerStore) === null || _a === void 0 ? void 0 : _a.removeListener({ field: `clients.${key}` }, this.clientsKeyValueChangeStoreListener, (err, success) => {
                if (!success) {
                    Logger.system.debug("Linker Client Onclose: clientsKeyValueChangeStoreListener removeListener failed", key);
                }
            });
            (_b = this.linkerStore) === null || _b === void 0 ? void 0 : _b.removeListener({ field: '' }, this.anyFieldChangeStoreListener, (err, success) => {
                if (!success) {
                    Logger.system.debug("Linker Client Onclose: anyFieldChangeStoreListener removeListener failed");
                }
            });
            Logger.system.debug("LINKER-4 cleanup", key);
            cb && cb();
        };
        /*
         * Start backward compatibility
         * @private
         */
        this.groups = this.channels;
        this.allGroups = this.allChannels;
        this.onLinksUpdate = {
            push: (cb) => {
                this.stateChangeListeners.push((err, response) => {
                    if (response) {
                        response.groups = response.channels;
                    }
                    cb(err, { groups: response });
                });
            },
        };
        this.linkerWindow = null;
        this.loading = false;
        this.clientsKeyValueChangeStoreListener = this.clientsKeyValueChangeStoreListener.bind(this);
        this.anyFieldChangeStoreListener = this.anyFieldChangeStoreListener.bind(this);
    }
    /**
     * 1/24/19 this is Brad guessing. This function has never existed. This is the first commit where 'getChannel' is referenced...but not defined.
     *
     * https://github.com/ChartIQ/finsemble/blob/ad25aa219c5fac60c277bccf35dea43568da2a07/src/clients/linkerClient.js
     *
     * No idea how it ever worked.
     * @private
     *
     */
    getChannel(name) {
        return this.allChannels.filter((channel) => channel.name === name).length > 0;
    }
    /**
     * @deprecated The LinkerClient has been deprecated.
     *
     * Create a new Linker channel. This channel will be available *globally*.
     *
     * ```javascript
     * LinkerClient.createChannel({name: "red", color: "#ff0000", border: "#ffffff"}, callback)
     * ```
     * @param {object} params
     * @param cb - Optional. Callback to retrieve returned results asynchronously.
     * @return {Array.<string>} Returns an array of all available channels
     * @private
     * @memberof LinkerClient
     */
    createChannel(params, cb) {
        var _a;
        Logger.system.deprecated("This method is deprecated and will be removed in a future release. Please use FSBL.Interop which provides channel linking automatically.");
        sysinfo("LinkerClient.createChannel", "PARAMS", params);
        if (!params.name) {
            sysdebug("LinkerClient.createChannel: Name is required");
            return asyncIt(this.allChannels, cb);
        }
        if (this.getChannel(params.name)) {
            sysdebug(`LinkerClient.createChannel: Channel ${params.name} Already Exists`);
            return asyncIt(this.allChannels, cb);
        }
        this.allChannels.push(params);
        this.allGroups = this.allChannels; // backward compatibility
        (_a = this.linkerStore) === null || _a === void 0 ? void 0 : _a.set(["channels"], this.allChannels);
        return asyncIt(this.allChannels, cb);
    }
    /**
     * Remove a Linker channel. It will be removed globally. Any component that is currently assigned to this channel will be unassigned.
     *
     * ```javascript
     * FSBL.Clients.LinkerClient.removeChannel("group1")
     * ```
     * @param {string} name - The name of the channel to remove
     * @param cb - Optional. Callback to retrieve returned results asynchronously
     * @returns {Array.<string>} Returns an array of available channels
     * @private
     * @memberof LinkerClient
     */
    removeChannel(name, cb) {
        var _a, _b;
        sysinfo("LinkerClient.removeChannel", "NAME", name);
        Logger.system.deprecated("This method is deprecated and will be removed in a future release. Please use FSBL.Interop which provides channel linking automatically.");
        if (!this.getChannel(name)) {
            sysdebug(`Channel ${name}does not exist`, null);
            return asyncIt(this.allChannels, cb);
        }
        const channels = this.allChannels;
        for (let i = 0; i < channels.length; i++) {
            if (name === channels[i].name) {
                channels.splice(i, 1);
                break;
            }
        }
        (_a = this.linkerStore) === null || _a === void 0 ? void 0 : _a.set(["channels"], this.allChannels);
        const { clients } = this;
        for (const c in clients) {
            const client = clients[c];
            for (const channel in client.channels) {
                if (name === channel) {
                    delete client[channel];
                    break;
                }
            }
        }
        (_b = this.linkerStore) === null || _b === void 0 ? void 0 : _b.set(["clients"], this.clients);
        return asyncIt(this.allChannels, cb);
    }
    /**
     * Convenience function to update the client information in the store.
     * @private
     */
    updateClientInStore(key) {
        var _a;
        (_a = this.linkerStore) === null || _a === void 0 ? void 0 : _a.set([
            'clients', `${key}`,
        ], this.clients[key]);
    }
    /**
     * @deprecated The LinkerClient has been deprecated.
     *
     * Add a component to a Linker channel programmatically. Components will begin receiving any new contexts published to this channel but will <b>not</b> receive the currently established context.
     *
     * ```javascript
     * FSBL.Clients.LinkerClient.linkToChannel("group3", null); // Link current window to channel.
     * FSBL.Clients.LinkerClient.linkToChannel("group3", windowIdentifier); // Link the requested window to channel.
     * ```
     * @param {string} channel - The name of the channel to link our component to, or an array of names.
     * @param windowIdentifier -  Optional. windowIdentifier for the component. If null, it defaults to the current window.
     * @param cb - Optional. Callback to retrieve returned results asynchronously.
     * @return {LinkerState} The new state: linked channels, all channels
     * @memberof LinkerClient
     */
    linkToChannel(channel, windowIdentifier, cb) {
        Logger.system.deprecated("This method is deprecated and will be removed in a future release. Please use FSBL.Interop which provides channel linking automatically.");
        sysinfo("LinkerClient.linkToChannel", "CHANNEL", channel, "COMPONENT", windowIdentifier);
        if (!windowIdentifier)
            windowIdentifier = WindowClient.getWindowIdentifier();
        const key = makeKey(windowIdentifier);
        if (!this.clients[key]) {
            this.clients[key] = {
                client: windowIdentifier,
                channels: {},
            };
        }
        if (Array.isArray(channel)) {
            for (let i = 0; i < channel.length; i++) {
                this.clients[key].channels[channel[i]] = true;
            }
        }
        else {
            this.clients[key].channels[channel] = true;
        }
        this.updateClientInStore(key);
        return asyncIt(this.getState(windowIdentifier), cb);
    }
    /**
     * @deprecated The LinkerClient has been deprecated.
     *
     * Unlinks a component from a Linker channel.
     *
     * ```javascript
     * FSBL.Clients.LinkerClient.unlinkFromChannel("group1", null); // Unlink the current window from channel "group1"
     * FSBL.Clients.LinkerClient.unlinkFromChannel("group1", windowIdentifier) // Unlink the requested window from channel "group1"
     * ```
     * @param {string} channel - Channel to remove, or an array of channels. If null, then all channels will be removed.
     * @param windowIdentifier -  windowIdentifier for the window. Defaults to current window if left null.
     * @param cb - Optional. Callback to retrieve returned results asynchronously.
     * @return {LinkerState} Returns the new state: linked channels, all channels
     * @memberof LinkerClient
     */
    unlinkFromChannel(channel, windowIdentifier, cb) {
        Logger.system.deprecated("This method is deprecated and will be removed in a future release. Please use FSBL.Interop which provides channel linking automatically.");
        sysinfo("LinkerClient.unlinkFromChannel", "CHANNEL", channel, "WINDOW IDENTIFIER", windowIdentifier);
        if (!windowIdentifier)
            windowIdentifier = WindowClient.getWindowIdentifier();
        const key = makeKey(windowIdentifier);
        const componentEntry = this.clients[key];
        if (Array.isArray(channel)) {
            // Delete an array of channels
            for (let i = 0; i < channel.length; i++) {
                delete componentEntry.channels[channel[i]];
            }
        }
        else if (!channel) {
            // Delete all channels
            for (const name in componentEntry.channels) {
                delete componentEntry.channels[name];
            }
        }
        else {
            // Cannot access componentEntry.channels[channel] if channel is an array.
            if (!componentEntry || !componentEntry.channels[channel]) {
                const component = this.clients[key];
                sysdebug(`Component was not in specified channel ${channel}`, component, component.channels[channel]);
                return asyncIt(this.getState(windowIdentifier), cb);
            }
            // Delete a specific channel
            delete componentEntry.channels[channel];
        }
        this.updateClientInStore(key);
        return asyncIt(this.getState(windowIdentifier), cb);
    }
    /**
     * @deprecated The LinkerClient has been deprecated.
     *
     * Returns all available Linker channels.
     * ```javascript
     * let groups = FSBL.Clients.LinkerClient.getAllChannels();
     * ```
     *
     * ```javascript
     * FSBL.Clients.LinkerClient.getAllChannels((err, channels) => { ... });
     * ```
     * @param cb Optional. Callback to retrieve returned results asynchronously.
     * @return {array} An array of all channels. Each array item is {name:channelName} plus any other optional fields such as color.
     * @memberof LinkerClient
     */
    getAllChannels(cb) {
        sysinfo("LinkerClient.getAllChannels");
        return asyncIt(this.allChannels, cb);
    }
    /**
     * Retrieve all channels linked to the requested component. Also returns all available channels.
     *
     * ```javascript
     * var state=LinkerClient.getState(windowIdentifier)
     * ```
     * @param windowIdentifier Retrieve all channels for identified window. If null, will retrieve for the current window.
     * @param cb Optional. Callback to retrieve returned results asynchronously
     * @return {LinkerState} The current state: linked channels, all channels
     * @private
     */
    getState(windowIdentifier, cb) {
        var _a;
        sysinfo("LinkerClient.getState", "WINDOW IDENTIFIER", windowIdentifier);
        const state = {
            channels: [],
            allChannels: this.allChannels,
        };
        if (!windowIdentifier)
            windowIdentifier = WindowClient.getWindowIdentifier();
        if (!Object.keys(this.clients).length) {
            return asyncIt(state, cb);
        }
        const key = makeKey(windowIdentifier);
        const componentEntry = this.clients[key];
        if (!componentEntry) {
            return asyncIt(state, cb);
        }
        // Create an array of channel descriptors, one for each linked channel
        // Convert {"purple": true, "green":true} to [{"name":"purple"},{"name":"green"}]
        state.channels = this.allChannels.filter((value) => componentEntry.channels && componentEntry.channels[value.name] === true);
        Logger.system.debug("LINKER-3", this.allChannels, "state.channels", state.channels, "key", key, "windowIdentifier", windowIdentifier, "componentEntry", componentEntry);
        // Cleanup code in case of an oops. If we're accessing this component, it must be alive. Make sure the store reflects this.
        if (!componentEntry.active) {
            componentEntry.active = true;
            (_a = this.linkerStore) === null || _a === void 0 ? void 0 : _a.set([
                'clients', `${key}`
            ], componentEntry);
        }
        return asyncIt(state, cb);
    }
    /**
     * @deprecated The LinkerClient has been deprecated.
     *
     * Remove all listeners for the specified dataType.
     *
     * ```javascript
     * FSBL.Clients.LinkerClient.unsubscribe("symbol");
     * ```
     * @param {String}  dataType - The data type to which the component is subscribed.
     * @param {function} listener - Optional. The function that was passed to subscribe. If not specified, all listeners will be deleted.
     * @memberof LinkerClient
     */
    unsubscribe(dataType, listener) {
        Logger.system.deprecated("This method is deprecated and will be removed in a future release. Please use FSBL.Interop which provides channel linking automatically.");
        sysinfo("LinkerClient.unsubscribe", "DATA TYPE", dataType);
        if (this.dataListenerList[dataType]) {
            if (!listener) {
                delete this.dataListenerList[dataType];
            }
            else {
                this.dataListenerList[dataType] = this.dataListenerList[dataType].filter((fn) => fn !== listener);
            }
        }
    }
    /**
     * @deprecated The LinkerClient has been deprecated.
     *
     * Publish a piece of data. The data will be published to <b>all channels</b> that the component is linked to. Foreign components that are linked to those channels will receive the data if they have subscribed to this dataType. They can then use that data to synchronize their internal state. See <a href="#subscribe">LinkerClient.subscribe()</a>.
     *
     * ```javascript
     * FSBL.Clients.LinkerClient.publish({ dataType:"symbol", data:"AAPL" })
     * ```
     * @param {String}  params.dataType - A string representing the data type being sent.
     * @param {any}  params.data - The data ("context") being transmitted.
     * @param {array<string>} params.channels - Optional. Specifies which channels publish this piece of data. This overrides the default which is to publish to all linked channels.
     * @param cb - Optional. Callback to retrieve returned results asynchronously.
     * @memberof LinkerClient
     */
    publish(params, cb) {
        sysinfo("LinkerClient.publish", "PARAMS", params);
        let { channels } = params;
        if (!channels) {
            channels = Object.keys(this.channels);
        }
        for (let i = 0; i < channels.length; i++) {
            const channel = channels[i];
            RouterClient.transmit(`${channel}.${params.dataType}`, {
                type: params.dataType,
                data: params.data,
            });
            RouterClient.transmit(channel, {
                type: params.dataType,
                data: params.data,
            });
        }
        return asyncIt({}, cb);
    }
    /**
     * @deprecated The LinkerClient has been deprecated.
     *
     * Registers a client for a specific data type that is sent to a channel. Calling `subscribe` multiple times adds additional handlers. A component may be subscribed to a maximum of six channels at the same time.
     * Please note that the signature of the .Net version of this function differs slightly.
     *
     * ```javascript
     * FSBL.Clients.LinkerClient.subscribe("symbol", (data, response) => {
     * 	if (!response.originatedHere()) {
     * 		const data = response.data;
     * 		// Logic to perform with data
     * 	}
     * });
     * ```
     * @param {String} dataType A string representing the data type to subscribe to.
     * @param {Function} listener A function `(data, response)` to be called once the Linker receives the specific data. Use <b>response.originatedHere()</b> to determine if the calling window is also the publisher
     * @memberof LinkerClient
     */
    subscribe(dataType, listener) {
        Logger.system.deprecated("This method is deprecated and will be removed in a future release. Please use FSBL.Interop which provides channel linking automatically.");
        sysinfo("LinkerClient.subscribe", "DATA TYPE", dataType);
        if (this.dataListenerList[dataType]) {
            if (!this.dataListenerList[dataType].includes(listener)) {
                this.dataListenerList[dataType].push(listener);
            }
            return;
        }
        this.dataListenerList[dataType] = [listener];
    }
    /**
     * @deprecated The LinkerClient has been deprecated.
     *
     * Retrieves an array of all components with links that match the given parameters. If no parameters are specified, all windows with established links will be returned.
     *
     * Get all components linked to a given component:
     * ```javascript
     * FSBL.Clients.LinkerClient.getLinkedComponents({ windowIdentifier: wi });
     * ```
     *
     * Get all components linked to channel "group 1":
     * ```javascript
     * FSBL.Clients.LinkerClient.getLinkedComponents({ channels: ['group1'] });
     * // Response format: [{windowName: 'Window Name', componentType: 'Component Type', uuid: 'uuid', channels: ['group1'] }]
     * ```
     * @param {object} params Optional
     * @param {Array.<string>} params.channels Restrict to these channels.
     * @param {Array.<string>} params.componentTypes Restrict to these componentTypes. The componentType is a key in the finsemble.components configuration object.
     * @param {windowIdentifier} params.windowIdentifier Restrict to this component.
     * @param cb Optional. Callback to retrieve returned results asynchronously.
     * @returns {array} An array of linked components, their windows, and their linked channels.
     * @memberof LinkerClient
     */
    getLinkedComponents(params, cb) {
        Logger.system.deprecated("This method is deprecated and will be removed in a future release. Please use FSBL.Interop which provides channel linking automatically.");
        sysinfo("LinkerClient.getLinkedComponents", "PARAMS", params);
        const linkedWindows = [];
        // Fix params
        if (!params) {
            params = {};
        }
        if (params.channels && !Array.isArray(params.channels)) {
            params.channels = [params.channels];
        }
        if (params.componentTypes && !Array.isArray(params.componentTypes)) {
            params.componentTypes = [params.componentTypes];
        }
        // If we have a client
        if (params.windowIdentifier) {
            const key = makeKey(params.windowIdentifier);
            const myChannels = Object.keys(this.clients[key].channels);
            if (!params.channels) {
                // If no channels are specified, use the window identifier channels
                params.channels = myChannels;
            }
            else {
                // Otherwise use an intersection of params.channels and the component's channels
                params.channels = params.channels.filter((o) => myChannels.includes(o));
            }
        }
        // if no channels, assume all channels
        if (!params.channels)
            params.channels = this.getAllChannels().map((o) => o.name);
        // Get all active
        for (const c in this.clients) {
            let component = this.clients[c];
            //Clients are cleared on workspace switch/shutdown. Component will be undefined
            // for any components not linked to a channel in this session
            if (!component)
                continue;
            if (!component.channels) {
                // frame fix
                component = component[Object.keys(component)[0]];
            }
            const clientMatchesChannels = Object.keys(component.channels).filter((o) => { var _a; return (_a = params === null || params === void 0 ? void 0 : params.channels) === null || _a === void 0 ? void 0 : _a.includes(o); }).length;
            let clientMatchesComponentTypes = true;
            if (params.componentTypes)
                clientMatchesComponentTypes = params.componentTypes.includes(component.client.componentType);
            if (component.active && clientMatchesChannels && clientMatchesComponentTypes) {
                linkedWindows.push({
                    windowName: component.client.windowName,
                    componentType: component.client.componentType,
                    uuid: component.client.uuid,
                    channels: Object.keys(component.channels),
                });
            }
        }
        return asyncIt(linkedWindows, cb);
    }
    /**
     * Adds new listeners for channels when channels are updated
     * @private
     */
    updateListeners() {
        // Remove listeners
        for (let i = this.channelListenerList.length - 1; i >= 0; i--) {
            const channel = this.channelListenerList[i];
            const channels = Object.keys(this.channels);
            if (!channels.filter((g) => g == channel).length) {
                RouterClient.removeListener(channel, this.handleListeners);
                this.channelListenerList.splice(i, 1);
            }
        }
        // Setup new listeners if needed
        const channels = Object.keys(this.channels);
        for (let i = 0; i < channels.length; i++) {
            const channel = channels[i];
            if (!this.channelListenerList.includes(channel)) {
                RouterClient.addListener(channel, this.handleListeners);
                this.channelListenerList.push(channel);
            }
        }
        // Send state update to any registered external listeners
        const state = this.getState();
        for (let i = 0; i < this.stateChangeListeners.length; i++) {
            this.stateChangeListeners[i](null, state);
        }
    }
    /**
     * @deprecated The LinkerClient has been deprecated.
     *
     * Use this method to register a callback which will be called whenever the state of the Linker changes. This will be called whenever a user links or unlinks your component to a channel.
     *
     * ```javascript
     * FSBL.Clients.LinkerClient.onStateChange(function(err, response){
     *    if(response.channels){
     * 		console.log("Printout of channel status ", response.channels);
     * 	}
     * });
     * ```
     * @param {function} cb  returns { null, LinkerClient~state }
     * @memberof LinkerClient
     */
    onStateChange(cb) {
        this.stateChangeListeners.push(cb);
    }
    /**
     * Persists the current linker state. When the window is restored, that state will be available and restored (in initialize).
     * @private
     * @param {object} state The state enabled for this Linker instance
     */
    persistState(state) {
        WindowClient.setComponentState({
            field: "Finsemble_Linker",
            value: state,
        });
    }
    /**
     * Callback function for linkerStore.addListener({ field: "clients." + key }). Updates channels and persistState.
     *
     * @param {object} response contains changed listener data
     * @private
     */
    clientsKeyValueChangeStoreListener(_, response) {
        sysdebug("My Channels Updated");
        if (!response.value)
            return;
        const responseChannels = response.value.channels || response.value.groups;
        const areChannelsEqual = deepEqual(responseChannels, this.channels);
        // If channels change, save, this prevents initial empty channels from saving.
        if ((responseChannels && Object.keys(responseChannels).length !== 0 && !areChannelsEqual) ||
            (Object.keys(responseChannels).length === 0 && Object.keys(this.channels).length !== 0)) {
            this.persistState(responseChannels);
            this.channels = responseChannels;
            this.updateListeners();
        }
    }
    /**
     * Callback function for linkerStore.addListener({}).  Updates allChannels, allGroups, and clients.
     *
     * @param {object} response contains changed listener data
     * @private
     */
    anyFieldChangeStoreListener(_, response) {
        const { values } = response.value;
        this.allChannels = values.channels;
        if (values.groups)
            this.allChannels = values.groups; // backward compatibility
        Logger.system.debug("LINKER-2", this.allChannels);
        this.allGroups = this.allChannels; // backward compatibility
        this.clients = values.clients;
        this.updateListeners();
    }
    // load all linkages and register listeners for updated data.
    /**
     * @private
     */
    start(cb) {
        return new Promise(async (resolve) => {
            await SystemManagerClient.waitForStartup("linkerService");
            this.dontPersistYet = true;
            sysdebug("LinkerClient Loading Channels");
            const wi = WindowClient.getWindowIdentifier();
            const key = makeKey(wi);
            // Connect to the global linker store. This is shared by all instances.
            DistributedStoreClient.getStore({ store: "Finsemble_Linker", global: true }, (err, linkerStore) => {
                this.linkerStore = linkerStore;
                linkerStore === null || linkerStore === void 0 ? void 0 : linkerStore.addListener({ field: `clients.${key}` }, this.clientsKeyValueChangeStoreListener);
                linkerStore === null || linkerStore === void 0 ? void 0 : linkerStore.addListener({}, this.anyFieldChangeStoreListener);
                // Get all the available channels for Linkers
                linkerStore === null || linkerStore === void 0 ? void 0 : linkerStore.getMultiple([["channels"], [`clients.${key}`]], (getLinkerValuesError, values) => {
                    if (values && values.channels) {
                        this.allChannels = values.channels;
                        Logger.system.debug("LINKER-1", this.allChannels);
                        this.allGroups = this.allChannels; // backward compatibility
                    }
                    // Now get the linker state (which channels are enabled) for this instance. The windowClient will have retrieved this from Storage.
                    // Use this to initialize our channel state.
                    WindowClient.getComponentState({ field: "Finsemble_Linker" }, (getComponentStateError, linkerData) => {
                        var _a;
                        this.channels = [];
                        this.clients[key] = {
                            client: wi,
                            active: true,
                            channels: {},
                        };
                        if (linkerData) {
                            this.channels = linkerData;
                            this.clients[key].channels = linkerData;
                        }
                        else if ((_a = values[`clients.${key}`]) === null || _a === void 0 ? void 0 : _a.channels) {
                            this.clients[key].channels = values[`clients.${key}`].channels;
                        }
                        // Feed back to the distributed store the state that we got out of storage.
                        this.updateClientInStore(key);
                        // If we've just been spawned, then check to see if we were passed any overrides
                        const spawnData = WindowClient.getSpawnData();
                        if (spawnData && spawnData.linker) {
                            let existingLinks = spawnData.linker.channels;
                            if (spawnData.linker.groups)
                                existingLinks = spawnData.linker.groups; // backward compatibility
                            this.linkToChannel(existingLinks, wi);
                        }
                        else {
                            this.updateListeners();
                        }
                        cb === null || cb === void 0 ? void 0 : cb();
                        resolve();
                    });
                });
            });
        });
    }
    initialize() {
        Logger.system.deprecated("It is no longer necessary to call LinkerClient.initialize()");
    }
    /**
     * Minimize all windows except those on specified channel
     * @param {string} channel
     * @private
     */
    hyperFocus(channel) {
        // var windowNames = this.getLinkedComponents({ channels: channel }).map(c => c.windowName);
        hyperFocus({
            windowList: this.getLinkedComponents({ channels: channel }),
        });
    }
    /**
     * Bring all windows in specified channel to the front
     * @param {params} object
     * @param {params.channel} channel to btf.
     * @param {params.restoreWindows} whether to restore windows that are minimized prior to calling bring to front.
     * @private
     */
    bringAllToFront(params) {
        const { channel } = params;
        // var windowNames = this.getLinkedComponents({ channels: channel }).map(c => c.windowName);
        bringWindowsToFront({
            windowList: this.getLinkedComponents({ channels: channel }),
        });
    }
    /**
     * Creates a linker channel
     * @param {linkerGroup} group The linker channel to create
     * @param {Function} cb The callback
     * @private
     */
    createGroup(group, cb) {
        return this.createChannel(group, cb);
    }
    /**
     * Removes a linker channel from the list of available channels
     * @param {string} groupName The name of the channel to delete
     * @param {Function} cb The callback
     * @private
     */
    deleteGroup(groupName, cb) {
        return this.removeChannel(groupName, cb);
    }
    /**
     * Adds a given window to given channel
     * @param {string} groupName The channel name
     * @param {WindowIdentifier} client The window to add to the given channel
     * @param {Function} cb The callback (optional)
     * @private
     */
    addToGroup(groupName, client, cb) {
        const state = this.linkToChannel(groupName, client);
        if (cb)
            cb(null, state);
        return state;
    }
    /**
     * Remove a given window from a linker channel
     * @param {string} groupName The name of the linker channel to disconnect from
     * @param {WindowIdentifier} client The window to remove from the given channel
     * @param {Function} cb The callback (optional)
     * @returns The response from removing the window from the given channel
     * @private
     */
    removeFromGroup(groupName, client, cb) {
        const state = this.unlinkFromChannel(groupName, client);
        if (cb)
            cb(null, state);
        return state;
    }
    /**
     * Gets a list of all linker channels
     * @param {Function} cb The callback
     * @returns An array of linker channel names
     * @private
     */
    getAllGroups(cb) {
        const channels = this.getAllChannels();
        if (cb)
            cb(channels);
        return channels;
    }
    /**
     * Asynchronously returns the list of all linked channels of a given window
     * @param {WindowIdentifier} [client] The window to find linked groups of
     * @param {Function} cb The callback (optional)
     * @returns Asynchronously returns list of channels
     * @private
     */
    getGroups(client, cb) {
        const state = this.getState(client);
        state.groups = state.channels;
        return asyncIt(state, cb);
    }
    /**
     * @deprecated The LinkerClient has been deprecated.
     *
     * Remove all listeners for the specified dataType.
     *
     * ```javascript
     * FSBL.Clients.LinkerClient.unsubscribe("symbol");
     * ```
     * @param {String}  dataType - The data type to which the component is subscribed.
     * @memberof LinkerClient
     */
    unSubscribe(dataType, cb) {
        this.unsubscribe(dataType, cb);
    }
    /**
     * @deprecated The LinkerClient has been deprecated.
     *
     * Retrieves an array of all components with links that match the given parameters. If no parameters are specified, all windows with established links will be returned.
     *
     * Get all components linked to a given component:
     * ```javascript
     * FSBL.Clients.LinkerClient.getLinkedWindows({windowIdentifier: wi});
     * ```
     *
     * Get all Windows linked to channel "group1":
     * ```javascript
     * FSBL.Clients.LinkerClient.getLinkedComponents({ channels: ['group1'] });
     * // Response format: [{ windowName: 'Window Name', componentType: 'Component Type', uuid: 'uuid', channels: ['group1'] }]
     * ```
     * @param {object} params Optional
     * @param {Array.<string>} params.channels Restrict to these channels.
     * @param {Array.<string>} params.componentTypes Restrict to these componentTypes. The componentType is a key in the finsemble.components configuration object.
     * @param {windowIdentifier} params.windowIdentifier Restrict to this component.
     * @param {windowIdentifier} params.client Alias for windowIdentifier.
     * @param {Array.<string>} params.groups Alias for channels.
     * @param cb - Optional. Callback to retrieve returned results asynchronously.
     * @returns {array} An array of linked components, their windows, and their linked channels.
     * @memberof LinkerClient
     */
    getLinkedWindows(params, cb) {
        if (!params) {
            params = {};
        }
        params.groups = params.channels;
        params.windowIdentifier = params.client;
        return this.getLinkedComponents(params, cb);
    }
    /**
     * Asynchronously retrieves a window's windowIdentifier
     * @param {any} [params] Parameters
     * @param {Function} cb The callback
     * @private
     */
    windowIdentifier(params, cb) {
        return asyncIt(WindowClient.getWindowIdentifier(), cb);
    }
    /**
     * @deprecated The LinkerClient has been deprecated.
     *
     * Opens the Linker window.
     * @param {Function} cb The callback to be invoked after the method completes successfully.
     * @memberof LinkerClient
     */
    openLinkerWindow(cb) {
        if (this.loading) {
            return;
        } // If in process of loading then return. This prevents double clicks on the icon.
        const showLinkerWindowInner = () => {
            RouterClient.query("Finsemble.LinkerWindow.Show", {
                groups: this.getGroups().groups,
                windowIdentifier: WindowClient.getWindowIdentifier(),
                windowBounds: WindowClient.getWindowBounds(),
            }, () => { });
        };
        if (this.linkerWindow) {
            this.linkerWindow.isShowing((showing) => {
                if (showing) {
                    this.linkerWindow.hide();
                }
                else {
                    showLinkerWindowInner();
                }
            });
            return;
        }
        showLinkerWindowInner();
    }
}
const linkerClient = new LinkerClient();
/**
 * @ignore
 */
export default linkerClient;
/**
 * Callback that returns a list of channels in the responseMessage
 * @callback LinkerChannelsCB
 * @param {Object} err Error message, or null if no error
 * @param {Array.<string>} channels List of channels
 */
/**
 * Callback that returns a new LinkerState
 * @callback LinkerStateCB
 * @param {Object} err Error message, or null if no error
 * @param {Array.<string>} channels List of all channels linked to the requested component
 * @param {Array.<string>} allChannels List of all available channels
 */
/**
 * A list of enabled channels and a list of all channels
 * @callback LinkerState
 * @param {Array.<string>} channels List of all channels linked to the requested component
 * @param {Array.<string>} allChannels List of all available channels
 */
//# sourceMappingURL=linkerClient.js.map