/*!
 * Copyright 2017 - 2020 by ChartIQ, Inc.
 * All rights reserved.
 */
/**
 * The Dialog Manager Client simplifies interacting with dialog windows by spawning them and getting data back from them.
 * In this context, a dialog window is simply a child window spawned to interact with the user, such as a confirmation dialog.
 * Functions are provided here for both the parent-window side and the dialog/child-window side.
 *
 *`FSBL.Clients.DialogManager` is always pre-initialized with one instance of the Dialog Manager in the Finsemble Library (making it essentially, a singleton when referenced in the same window). This means when developing Finsemble components, you directly access the Dialog Manager without using the constructor (e.g., `FSBL.Clients.DialogManager.spawnDialog(...);`). **The constructor is not exposed to components.**
 *
 * @module DialogManagerClient
 */
import { LauncherClient } from "./launcherClient";
import { FinsembleWindow } from "../common/FinsembleWindow";
import { Logger } from "./logger";
import { System } from "../common/system";
import { getMyWindowIdentifier } from "../common/util";
import { RouterClient } from "./routerClient";
import { singleton } from "../common/Singleton";
import { nameMap as legacyUINameMap } from "../deprecated/legacyUIComponentNames";
import { DialogManagerClientController } from "./controller/DialogManagerClientController";
import uuidv4 from "uuid-random";
const getControllerInstance = async () => {
    const controller = singleton("DialogManagerClient", DialogManagerClientController);
    await controller.initializeOnce();
    return controller;
};
/**
 * Generates a string for the "onReady" for a given channel.
 * @param {object} identifier
 */
const generateDialogReadyChannel = (identifier) => {
    const concat = identifier.windowName;
    return `Dialog.${concat}.ready`;
};
// ///////////////////////////////////////////
// Functions relating to the modal scrim
// ///////////////////////////////////////////
/**
 * @language javascript, java
 * @deprecated
 * Registers this window as a modal that can be used by the dialog manager. This will set
 * the window as a "scrim" – taking up the entire size of the monitor and being invisible or lightly opaque.
 *
 * Sometimes, clicking anywhere on this window will dismiss the modal.
 */
export const registerModal = async () => {
    var _a, _b;
    Logger.system.deprecated("DialogManagerClient.registerModal() is deprecated.");
    const controller = await getControllerInstance();
    const info = await System.getMonitorInfo();
    const { virtualScreen } = info;
    (_a = controller.finsembleWindow) === null || _a === void 0 ? void 0 : _a.setBounds({
        bounds: {
            ...virtualScreen,
            width: virtualScreen.right - virtualScreen.left,
            height: virtualScreen.bottom - virtualScreen.top,
        },
    });
    const myWindowIdentifier = await getMyWindowIdentifier();
    await ((_b = controller.DialogStore) === null || _b === void 0 ? void 0 : _b.set(["modalIdentifier"], myWindowIdentifier));
    return null;
};
/**
 * @language javascript, java
 * @deprecated
 * Shows a semi-transparent black modal behind the dialog.
 * @param {function} cb The callback to be invoked after the method completes successfully.
 */
export const showModal = async (cb) => {
    Logger.system.deprecated("DialogManagerClient.showModal() is deprecated.");
    const controller = await getControllerInstance();
    if (!controller.DialogStore) {
        const error = `DialogMangerClient.showModal() store not yet initialized}`;
        Logger.system.error(error);
        cb === null || cb === void 0 ? void 0 : cb(error);
        return error;
    }
    const { err, data: identifier } = await controller.DialogStore.getGlobal(["modalIdentifier"]);
    if (err || !identifier) {
        // If null is passed back, this likely means the window is not ready yet.
        // We only need to proceed if the window exists and identifier is not null
        const error = `DialogManagerClient.showModal() error from store: ${err}`;
        Logger.system.debug(error);
        cb === null || cb === void 0 ? void 0 : cb(error);
        return error;
    }
    const { wrap: modal } = await FinsembleWindow.getInstance({
        uuid: identifier.uuid,
        name: identifier.windowName,
    });
    modal.updateOptions({
        options: {
            opacity: 0.4,
        },
    }, () => {
        modal.show(() => {
            modal.bringToFront(() => {
                var _a;
                (_a = controller.finsembleWindow) === null || _a === void 0 ? void 0 : _a.focus(() => {
                    var _a;
                    (_a = controller.finsembleWindow) === null || _a === void 0 ? void 0 : _a.bringToFront();
                    if (cb && typeof cb === "function") {
                        cb();
                    }
                });
            });
        });
    });
    return null;
};
/**
 * @language javascript, java
 * @deprecated
 * Hides the dialog's modal.
 */
export const hideModal = async () => {
    Logger.system.deprecated("DialogManagerClient.hideModal() is deprecated.");
    const controller = await getControllerInstance();
    if (!controller.DialogStore) {
        const error = `DialogMangerClient.hideModal() store not yet initialized}`;
        Logger.system.error(error);
        return error;
    }
    const { err, data: identifier } = await controller.DialogStore.getGlobal(["modalIdentifier"]);
    if (err || !identifier) {
        // If null is passed back, this likely means the window is not ready yet.
        // We only need to proceed if the window exists and identifier is not null
        const error = `DialogManagerClient.hideModal() error from store: ${err}`;
        Logger.system.debug(error);
        return error;
    }
    const { wrap: modal } = await FinsembleWindow.getInstance({
        uuid: identifier.uuid,
        name: identifier.windowName,
    });
    modal.hide();
    return null;
};
// ///////////////////////////////////////////
// Client Side Functions (Calling a Dialog Window)
// ///////////////////////////////////////////
/**
 * Function to initialize and open a dialog. Internally used.
 *
 * @param {object} identifier The window identifier of the dialog.
 * @param {object} options Any data to send to the dialog for its initialization.
 * @param {function} onUserInput Callback to be invoked after the user interacts with the dialog.
 */
const sendQueryToDialog = async (identifier, options, onUserInput) => {
    const warn = (timeout) => {
        console.warn(`No response from dialog ${identifier.windowName} after ${timeout / 1000} seconds. Check to make sure your dialog is sending back data.`);
    };
    const controller = await getControllerInstance();
    controller.moveDialogFromAvailableToOpened(identifier);
    const queryChannel = `Dialog.${identifier.windowName}.Show`;
    const listenerChannel = generateDialogReadyChannel(identifier);
    let warningTimeout;
    const unsubscribe = RouterClient.addListener(listenerChannel, async (err, response) => {
        warningTimeout = setTimeout(warn.bind(null, response.data.userInputTimeout), response.data.userInputTimeout);
        const { err: launchError, data } = await LauncherClient.showWindow(identifier, {
            monitor: options.monitor || "mine",
            left: "center",
            top: "center",
        });
        if (launchError || !data) {
            const error = `DialogManagerClient.sendQueryToDialog error showing window ${identifier.windowName} ${launchError}`;
            Logger.system.error(error);
            warn(error);
            return;
        }
        const { name } = data.finWindow;
        FinsembleWindow.getInstance({ name }, (getFinsembleWindowInstanceErr, wrap) => {
            wrap.bringToFront();
            wrap.focus();
        });
        unsubscribe();
    });
    const { err, response } = await RouterClient.query(queryChannel, options);
    clearTimeout(warningTimeout);
    controller.moveDialogFromOpenedToAvailable(identifier);
    onUserInput(err, response.data);
};
/**
 * @language javascript, java
 * Spawns an app or url in "dialog mode". The app's window will not be dockable.
 * The app does not need to be explicitly coded as a dialog.
 *
 * Parameters passed as `params.inputParams` can be retrieved in the dialog window by
 * calling `FSBL.Clients.DialogManager.getParametersFromInDialog()`.
 *
 * ```javascript
 * FSBL.Clients.DialogManager.spawnDialog(
 * {
 * 	name: "dialogTemplate",
 * 	height:300,
 * 	width:400,
 * 	url:"http://localhost/components/system/dialogs/dialog1.html"
 * },
 * {
 * 	someData: 12345
 * },
 * (error, responseParameters) => {
 *	if (!error) {
 * 		console.log(">>>> spawnDialog response: " + JSON.stringify(responseParameters));
 *	}
 * });
 * ```
 * @param {object} params Parameters. Same as <a href="/docs/reference/typedocs/modules/LauncherClient#spawn">LauncherClient.spawn()</a> with the following exceptions.
 * @param {string} params.url URL of dialog to launch
 * @param {string} [params.name] - The name of the dialog
 * @param {number|string} params.x - Same as <a href="/docs/reference/typedocs/modules/LauncherClient#spawn">LauncherClient.spawn()</a> except defaults to "center".
 * @param {number | string} [params.y="center"] - Same as <a href="/docs/reference/typedocs/modules/LauncherClient#spawn">LauncherClient.spawn()</a> except defaults to "center".
 * @param {object} inputParams Object or any data type needed by your dialog.
 * @param {function} dialogResponseCallback called when response received back from dialog window (typically on dialog completion). `responseParameters` is defined by the dialog.
 * @param {function} cb Returns response from <a href="/docs/reference/typedocs/modules/LauncherClient#spawn">LauncherClient.spawn()</a>
 */
export const spawnDialog = async (params, inputParams, dialogResponseCallback, cb) => {
    const responseChannel = `DialogChannel-${uuidv4()}`;
    params.data = { inputParams, responseChannel };
    // Dialogs default to center
    if (!params.left && params.left !== 0 && !params.right && params.right !== 0) {
        params.left = "center";
    }
    if (!params.top && params.top !== 0 && !params.bottom && params.bottom !== 0) {
        params.top = "center";
    }
    const unsubscribe = RouterClient.addListener(responseChannel, (err, response) => {
        dialogResponseCallback(err, response.data);
        unsubscribe();
    });
    const { error: err, response: data } = await LauncherClient.spawn("dialogTemplate", params);
    if (err) {
        Logger.system.error("DialogMangerClient.spawnDialog error spawning", err);
        dialogResponseCallback(err, data);
    }
    cb === null || cb === void 0 ? void 0 : cb(err, data);
    return { err, data };
};
/**
 * @language javascript, java
 * Opens an existing dialog that is registered under the requested type.
 * If no dialog is found then one will be spawned (with the options passed as the second parameter).
 *
 * Resolves to an error string if the dialog could not be opened, or null if it was successfully opened.
 *
 * @param {string} type Component type to open. The <code>type</code> must be a key in the finsemble.components configuration object.
 * If "yesNo" or "singleInput" are provided (legacy component names) then these will be converted to "YesNoDialog" and "SingleInputDialog"
 * (current component names).
 * @param {object} options Options to pass into the opened window. See the [Dialogs tutorial](/docs/customize/customize-with-SDK/user-experience/component-catalog/dialogs) for more info about the options that you can pass to the "YesNoDialog" and the "SingleInputDialog".
 * @param {function} onUserInput Callback to be invoked when the user interacts with the dialog.
 */
export const open = async (type, options, onUserInput) => {
    var _a;
    /**
     * Replaces legacy UI Component names with their updated name. Existing customer code may reference prior names
     * such as "yesNo" or "singleInput". This code will replace those calls with "YesNoDialog" and "SingleInputDialog".
     * See comment in ../../deprecated/legacyUIComponentNames.ts for more information.
     */
    type = (_a = legacyUINameMap[type]) !== null && _a !== void 0 ? _a : type;
    /** Attempts to open the dialog. If no dialog is yet registered then it resolves to false */
    const openDialog = () => new Promise(async (resolve) => {
        const controller = await getControllerInstance();
        controller.getAvailableDialog(type, (dialogIdentifier) => {
            if (dialogIdentifier) {
                sendQueryToDialog(dialogIdentifier, options, onUserInput);
                resolve(true);
            }
            else {
                resolve(false);
            }
        });
    });
    // Attempt to open the dialog. This will work if the dialog is open and properly registered.
    const opened = await openDialog();
    if (opened)
        return null;
    const spawnOptionsForDialogs = {
        options: {
            customData: {
                foreign: {
                    clients: {
                        dialogManager: {
                            isDialog: true,
                        },
                    },
                },
            },
        },
    };
    const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
    // Otherwise, launch the dialog and then wait for it to register. Since registration is an asynchronous activity
    // we are forced to poll.
    const { error } = await LauncherClient.spawn(type, spawnOptionsForDialogs);
    if (error) {
        const errorMessage = `DialogManagerClient.open launching dialog "${type}" error ${error}`;
        Logger.system.error(errorMessage);
        return errorMessage;
    }
    else {
        // The dialog was successfully launched, now we poll for up to 30 seconds waiting for it to register
        const timeout = 30000;
        for (let i = 0; i < 15; i++) {
            if (await openDialog())
                return null;
            else
                await sleep(timeout / 15);
        }
        const errorMessage = `DialogManagerClient.open error opening dialog "${type}. The dialog launched but didn't register within ${timeout} ms`;
        Logger.system.error(errorMessage);
        return errorMessage;
    }
};
// ///////////////////////////////////////////
// Server Side Functions (Becoming a Dialog Window)
// ///////////////////////////////////////////
// Currently unused internally
/**
 * @language javascript
 * Called from within a dialog window to get the `inputParams` passed by the originating window when
 * it called `spawnDialog()`.
 *
 * ```javascript
 * var dialogData = await FSBL.Clients.DialogManager.getParametersFromInDialog();
 * ```
 */
export const getParametersFromInDialog = async () => {
    // For legacy reasons, this function wasn't async and so there's a possibility that spawnData might
    // not be set yet, but unlikely given how the API is used
    const controller = await getControllerInstance();
    const { inputParams } = controller.spawnData;
    Logger.system.debug(`DialogManagerClient:getParametersFromInDialog: ${JSON.stringify(inputParams)}`);
    return inputParams;
};
// Currently unused internally
/**
 * @language javascript
 * Called within a dialog to pass a response back to the originating window.
 * This will invoke the originating window's callback function that it passed to `spawnDialog()`.
 *
 * ```javascript
 * FSBL.Clients.DialogManager.respondAndExitFromInDialog({ choice: response });
 * ```
 * @param {object} responseParameters Data to be returned to the originating window's callback
 */
export const respondAndExitFromInDialog = async (responseParameters) => {
    // For legacy reasons, this function wasn't async and so there's a possibility that spawnData might
    // not be set yet, but unlikely given how the API is used
    const controller = await getControllerInstance();
    Logger.system.debug(`DialogManagerClient:respondAndExitFromInDialog: ${JSON.stringify(responseParameters)}`);
    const { responseChannel } = controller.spawnData;
    RouterClient.transmit(responseChannel, responseParameters);
};
/**
 * @language javascript
 * Must be called by the dialog window when it is ready to be displayed.
 */
export const showDialog = async () => {
    var _a;
    const controller = await getControllerInstance();
    const myWindowIdentifier = await getMyWindowIdentifier();
    const listenerChannel = generateDialogReadyChannel(myWindowIdentifier);
    // tells the dialog manager in the opening window that the dialog is ready to be shown.
    RouterClient.transmit(listenerChannel, {
        userInputTimeout: (_a = controller.userInputTimeout) !== null && _a !== void 0 ? _a : 10000,
    });
};
/**
 * @language javascript
 * Dialog windows *must* run this method so that results can be sent back to
 * originating windows. This method must *only* be run once in any given dialog window.
 *
 * ```javascript
 * FSBL.Clients.DialogManager.registerDialogCallback((err, request) => {
 *    if(err) return;
 *    console.log("Originating window sent", request.data);
 *    request.sendQueryResponse("your response");
 *    FSBL.System.Window.getCurrent().hide();
 * });
 *
 * ```
 *
 * @param {function} callback Callback that is invoked when the originating window
 * connects. Data passed by the originating window can be found in the `data` member of the
 * second parameter to the callback.
 */
export const registerDialogCallback = async (cb) => {
    const controller = await getControllerInstance();
    if (controller.RESPONDER_CHANNEL) {
        RouterClient.addResponder(controller.RESPONDER_CHANNEL, (err, message) => {
            // Keep a local copy of the inquiry so that `respondToOpener()` can be
            // called asynchronously to respond
            if (message)
                controller.openerMessage = message;
            cb === null || cb === void 0 ? void 0 : cb(err, message);
        });
    }
};
/**
 * @language javascript
 * Dialogs may call this to respond to the originating window (instead of responding
 * directly to callbacks from `registerDialogCallback()`). The main advantage of using
 * this method is that it automatically hides the dialog.
 *
 * @param {any} data Data to be passed back to the originating window.
 */
export const respondToOpener = async (data) => {
    var _a, _b;
    const controller = await getControllerInstance();
    Logger.system.info("DialogManagerClient:RespondToOpener:", data);
    (_a = controller.openerMessage) === null || _a === void 0 ? void 0 : _a.sendQueryResponse(null, data);
    (_b = controller.finsembleWindow) === null || _b === void 0 ? void 0 : _b.hide();
    controller.openerMessage = null;
};
/**
 * @ignore
 */
export const DialogManagerClient = {
    /**
     * Used to set a timeout. This will be refactored.
     */
    set userInputTimeout(timeout) {
        const controller = singleton("DialogManagerClient", DialogManagerClientController);
        controller.userInputTimeout = timeout;
    },
    /**
     * Used to check response from dialog. This will be refactored.
     */
    get openerMessage() {
        const controller = singleton("DialogManagerClient", DialogManagerClientController);
        return controller.openerMessage;
    },
    /**
     * For backward compatibility
     * @private
     * @deprecated
     */
    initialize: () => {
        Logger.system.deprecated("It is no longer necessary to call DialogManager.initialize()");
    },
    /**
     * For backward compatibility
     * @private
     * @deprecated
     */
    onReady: (cb) => {
        Logger.system.deprecated("It is no longer necessary to call DialogManager.onReady()");
        cb === null || cb === void 0 ? void 0 : cb();
    },
    createStore: (cb) => {
        Logger.system.deprecated("DialogManager.createStore() is an internal function. It has been deprecated and will be removed in a future release.");
        const controller = singleton("DialogManagerClient", DialogManagerClientController);
        return controller.createStore(cb);
    },
    getAvailableDialog: (type, cb) => {
        Logger.system.deprecated("DialogManager.getAvailableDialog() is an internal function. It has been deprecated and will be removed in a future release.");
        const controller = singleton("DialogManagerClient", DialogManagerClientController);
        return controller.getAvailableDialog(type, cb);
    },
    sendQueryToDialog: (identifier, options, onUserInput) => {
        Logger.system.deprecated("DialogManager.sendQueryToDialog() is an internal function. It has been deprecated and will be removed in a future release.");
        return sendQueryToDialog(identifier, options, onUserInput);
    },
    open,
    spawnDialog,
    registerDialogCallback,
    getParametersFromInDialog,
    respondAndExitFromInDialog,
    showDialog,
    registerModal,
    showModal,
    hideModal,
    respondToOpener,
};
/**
 * @ignore
 */
export default DialogManagerClient;
//# sourceMappingURL=dialogManagerClient.js.map