/*!
 * Copyright 2017 - 2020 by ChartIQ, Inc.
 * All rights reserved.
 */
/**
 * Used to send, receive and manipulate notifications
 * @module NotificationClient
 */
/**
This is how we want our API to work going foward, the way the world does it: import { processAndSet } from "configClient"
```javascript
export const processAndSet = () => {
    // ...
};
```

This module's single exported object gives us backward compatibility when our core code does this: import { ConfigClient } from "configClient"
and provides the object that is attached to FSBL.Clients. This export should be kept as clean and simple as possible.
```javascript
export const ConfigClient = {
   processAndSet
}
```

The default export gives us backward compatibility where our core code does this: import ConfigClient from "configClient"
This can be eliminated when our core core is converted to normal imports as in the first example.
```javascript
export default ConfigClient;
```
*/
import { Action, ActionTypes, Filter, Notification, Subscription, } from "../platform/services/notification/types";
import { Logger } from "./logger";
import { RouterClient } from "./routerClient";
import { singleton } from "../common/Singleton";
import SystemManagerClient from "../common/systemManagerClient";
export { Notification, Subscription, Action, Filter, ActionTypes, };
export { Meta } from "../platform/services/notification/types";
/**
 * Internal (private) router channels
 */
const ROUTER_ENDPOINTS = {
    ACTION_PREFIX: "action.",
    CHANNEL_PREFIX: "notification.",
    DELETE: "delete",
    FETCH_HISTORY: "fetch_history",
    LAST_ISSUED: "last_issued",
    MARK_READ: "mark_read",
    MARK_UNREAD: "mark_unread",
    MUTE: "mute",
    NOTIFY: "notify",
    PERFORM_ACTION: "perform_action",
    SUBSCRIBE: "subscribe",
    UNMUTE: "unmute",
    UNSUBSCRIBE: "unsubscribe",
    SOURCES: "sources",
    MUTE_FILTERS: "mute_filters",
    PREFERENCES: "preferences",
};
class NotificationClientController {
    constructor() {
        this.subscriptions = [];
    }
}
const getControllerInstance = () => singleton("NotificationClient", NotificationClientController);
/**
 * @param channel
 * @param channelPrefix Will use the default prefix if null is passed
 * @param data
 * @param callback
 */
const query = (channel, data, channelPrefix, callback) => new Promise(async (resolve, reject) => {
    try {
        if (channelPrefix == null) {
            channelPrefix = ROUTER_ENDPOINTS.CHANNEL_PREFIX;
        }
        Logger.system.info(`sending message on ${channelPrefix + channel} channel`, data);
        const response = await RouterClient.query(channelPrefix + channel, data);
        if (response.err) {
            Logger.system.error(`Error: `, response.err);
            reject(response.err);
        }
        else {
            Logger.system.log(`${channel} raw response: `, response);
            if (callback) {
                callback(null, response.response.data.data);
            }
            resolve(response.response.data.data);
        }
    }
    catch (e) {
        Logger.system.error(`Error: `, e);
        if (callback) {
            callback(e);
        }
        reject(e);
    }
});
/**
 * @language javascript, java, dotnet
 * Fetches all the notifications sent to Finsemble after a specific date.
 *
 * ```javascript
 * let lastMonth = new Date();
 * lastMonth.setMonth(lastMonth.getMonth()-1);
 *
 * const notifications = await FSBL.Clients.NotificationClient.fetchHistory({
 * 	sentAfter: lastMonth.toISOString()
 * });
 * ```
 *
 * Get notifications sent between now and one month ago that matches a filter.
 * ```javascript
 *
 * const lastMonth = new Date();
 * lastMonth.setMonth(lastMonth.getMonth()-1);
 *
 * const filter = new FSBL.Clients.NotificationClient.Filter();
 *
 * // include all notifications from the OMS
 * filter.include.push({"source":"order-management-system"})
 *
 * // But exclude informational notifications from the OMS
 * filter.exclude.push({"source":"order-management-system", type:"informational"})
 * const notifications = await FSBL.Clients.NotificationClient.fetchHistory({
 * 	sentAfter: lastMonth.toISOString()},
 * 	filter
 * );
 * ```
 * @param options An options object or an ISO8601 formatted date string.
 * @param filter to match to notifications.
 * @returns array of notifications.
 * @throws Error throws an error on par with the Promise standard, containing detail why the request did not complete
 *
 * Get notifications sent between now and one month ago
 */
export const fetchHistory = async (options, filter) => {
    Logger.system.info("FetchHistory() called with params: ", options, filter);
    return await query(ROUTER_ENDPOINTS.FETCH_HISTORY, {
        options,
        filter,
    });
};
/**
 * @language javascript, java, dotnet
 * Return an ISO8601 date a notification matching the specified source was issued.
 * If no source is provided it will get the latest issue date for all notifications
 * (i.e the last time any notification was issue to the service)
 *
 * If you're sending notifications to Finsemble from an external source, you might want to know when the last
 * Notification of that type was received by Finsemble, so you're able to send any pending ones to the
 * user's desktop
 *
 * ```javascript
 * // Get the last date Finsemble received a notification sent with the source field set to "product-update-service"
 * const lastIssuedDate = await FSBL.Clients.NotificationClient.getLastIssuedAt("product-update-service")
 * ```
 * @param source to identify which notification to save lastUpdated time for.
 * @returns last issued at date string in the ISO8601 date format.
 * @throws Error throws an error on par with the Promise standard, containing detail why the request did not complete
 */
export const getLastIssuedAt = async (source) => {
    Logger.system.info("getLastIssued called with params: ", source);
    return await query(ROUTER_ENDPOINTS.LAST_ISSUED, source);
};
/**
 * @language javascript, java, dotnet
 * Tells the service to perform the supplied action on the notification(s)
 *
 *
 * ```javascript
 *  await FSBL.Clients.NotificationClient.performAction([notification], action).catch((e) => {
 *   console.log("Request to perform action failed");
 * });
 * // Request to perform action succeeded. The updated notification state will be broadcast to the subscribed clients
 * ```
 * @param notifications Notifications to apply action to.
 * @param action which has been triggered by user.
 * @throws If no error is thrown, the service has received the request to perform the action successfully. Note a successful resolution of the promise does not mean successful completion of the action.
 */
export const performAction = async (notifications, action) => {
    Logger.system.info("performAction() called with params: ", notifications, action);
    // I think this is a clumsy interface. The default case will likely be a single notification.
    // No need to punish the developer
    if (!Array.isArray(notifications)) {
        notifications = [notifications];
    }
    return await query(ROUTER_ENDPOINTS.PERFORM_ACTION, {
        notifications: notifications,
        action: action,
    });
};
/**
 * @language javascript, dotnet
 * Sets the states of the isRead field to true.
 *
 * All clients subscribed to received the updated notifications will be sent the new state.
 *
 *
 * ```javascript
 * await FSBL.Clients.NotificationClient.markRead([previouslyReceivedNotification]);
 * ```
 * @param notifications
 */
export const markRead = async (notifications) => {
    Logger.system.info("markRead() called with params: ", notifications);
    if (!Array.isArray(notifications)) {
        notifications = [notifications];
    }
    await query(ROUTER_ENDPOINTS.MARK_READ, { notifications, isRead: true });
};
/**
 * @language javascript, dotnet
 * Sets the states of the isRead field to false.
 *
 * All clients subscribed to received the updated notifications will be sent the new state.
 *
 *
 * ```javascript
 * await FSBL.Clients.NotificationClient.markUnread([previouslyReceivedNotification]);
 * ```
 * @param notifications
 */
export const markUnread = async (notifications) => {
    Logger.system.info("markUnread() called with params: ", notifications);
    if (!Array.isArray(notifications)) {
        notifications = [notifications];
    }
    await query(ROUTER_ENDPOINTS.MARK_UNREAD, { notifications, isRead: false });
};
/**
 * @language javascript, dotnet
 * Deletes a notification
 *
 * All clients subscribed to receive the updated notifications will be sent the new state.
 *
 * @param notifications The notifications to delete
 */
export const deleteNotifications = (notifications) => {
    Logger.system.info("deleteNotifications() called with params: ", notifications);
    return new Promise(async (resolve, reject) => {
        try {
            const data = query(ROUTER_ENDPOINTS.DELETE, notifications);
            resolve(data);
        }
        catch (e) {
            reject(e);
        }
    });
};
/**
 * @language javascript, java, dotnet
 * Send a new notification to Finsemble or update existing notifications already in Finsemble.
 *
 * Send a new notification
 * ```javascript
 * const notification = new FSBL.Clients.NotificationClient.Notification();
 * // Set your notification fields accordingly
 * 	notification.title = "Regarding Order...";
 * 	notification.details = "Creates a new notification in UI";
 * 	notification.type = "email";
 *
 * await FSBL.Clients.NotificationClient.notify([notification]);
 * ```
 * @param notifications Array of INotification
 * @throws Error If no error is thrown the service has received the notifications successfully
 */
export const notify = async (notifications) => {
    Logger.system.info("notify() called with params: ", notifications);
    // NotificationService is initialised at "system-preuser" stage.
    await SystemManagerClient.waitForBootStage("system-preuser", "stageCompleted");
    await query(ROUTER_ENDPOINTS.NOTIFY, Array.isArray(notifications) ? notifications : [notifications]);
};
/**
 *
 * @param channel
 * @param dataProcessor
 * @param channelPrefix
 */
const addResponder = (channel, dataProcessor, channelPrefix) => {
    if (channelPrefix == null) {
        channelPrefix = ROUTER_ENDPOINTS.CHANNEL_PREFIX;
    }
    Logger.system.info(`Adding responder for endpoint: ${channelPrefix + channel}`);
    RouterClient.addResponder(channelPrefix + channel, (err, message) => {
        Logger.system.info(`Message received on ${channel}: `, message);
        if (err || !message) {
            Logger.system.error(`Failed to setup ${channel} responder`, err);
            return;
        }
        try {
            Logger.system.info(`Processing message on channel ${channel}: `, message);
            const returnVal = dataProcessor(message.data);
            Logger.system.info(`Message response on ${channel}: `, returnVal);
            message.sendQueryResponse(null, {
                status: "success",
                data: returnVal,
            });
        }
        catch (e) {
            Logger.system.error(`Failed to process query`, e);
            message.sendQueryResponse(e);
        }
    });
};
/**
 * Listens on a channel to execute the onNotification callback and sends receipt
 *
 * @param channel the channel to listen to.
 * @param onNotification the action to take when a notification comes though
 */
const monitorChannel = (channel, onNotification) => new Promise((resolve) => {
    Logger.system.info("Listening for messages on channel", channel);
    addResponder(channel, (queryMessage) => {
        Logger.system.info("Notification received: ", queryMessage.id);
        // Catching user-code errors to allow for successful sending of receipt.
        try {
            onNotification(queryMessage);
        }
        catch (e) {
            // Error thrown in the onNotification
            Logger.system.error(`Error thrown in the subscription.onNotification()`, e);
        }
        // Return value used in addResponder as notification received response.
        return { message: "success" };
    });
    resolve();
});
/**
 * @language javascript, java, dotnet
 * Subscribe to a notification stream given filters. Returns subscriptionId
 *
 * ```javascript
 * const { NotificationClient } = FSBL.Clients;
 * const subscription = new NotificationClient.Subscription();
 *
 * // Set the filter to match INotification fields
 * subscription.filter = new NotificationClient.Filter();
 * // subscription.filter.include.push({"type": "chat"});
 *
 * const onNotification = (notification: INotification) => {
 *  // This function will be called when a notification arrives
 *  addToList(notification);
 * };
 *
 * // Use the subscription Id to unsubscribe from a specific subscription
 * const subscriptionId = await NotificationClient.subscribe(subscription, onNotification);
 * ```
 * @param subscription with name value pair used to match on.
 * @param onNotification callback for when a subscribing UI component receives a notification.
 * @throws Throws an error on par with the Promise standard, containing detail why the request did not complete
 * @returns A subscription Id that can be used to unsubscribe from the notification stream.
 */
export const subscribe = async (subscription, onNotification) => {
    const controller = getControllerInstance();
    Logger.system.info("subscribe() called with params: ", subscription, onNotification);
    const returnValue = await query(ROUTER_ENDPOINTS.SUBSCRIBE, JSON.parse(JSON.stringify(subscription)));
    // Monitor the channel and execute subscription.onNotification() for each one that arrives.
    Logger.system.info("Got a return value containing a channel", returnValue);
    await monitorChannel(returnValue.channel, onNotification);
    controller.subscriptions.push(returnValue);
    return returnValue;
};
/**
 *
 * @param channel
 * @param channelPrefix
 */
const removeResponder = (channel, channelPrefix) => {
    if (channelPrefix == null) {
        channelPrefix = ROUTER_ENDPOINTS.CHANNEL_PREFIX;
    }
    RouterClient.removeResponder(channelPrefix + channel);
};
/**
 * @param subscriptionId
 */
const cleanupSubscription = (subscriptionId) => {
    const controller = getControllerInstance();
    const index = controller.subscriptions.findIndex((element) => element.id === subscriptionId);
    if (index > -1) {
        removeResponder(controller.subscriptions[index].channel);
        controller.subscriptions.splice(index, 1);
    }
};
/**
 * @language javascript, java, dotnet
 * Used to unsubscribe from a notification stream.
 *
 * ```javascript
 * // the subscriptionId is returned from the subscribe function
 * await FSBL.Clients.NotificationClient.unsubscribe(subscriptionId);
 * ```
 * @param subscriptionId which was returned when subscription was created.
 * @throws Throws an error on par with the Promise standard, containing detail why the request did not complete
 */
export const unsubscribe = async (subscriptionId) => {
    Logger.system.info("unsubscribe() called with params: ", subscriptionId);
    await query(ROUTER_ENDPOINTS.UNSUBSCRIBE, subscriptionId);
    cleanupSubscription(subscriptionId);
};
/**
 * @language javascript, dotnet
 * Unsubscribe from all notification streams registered with the window's client instance.
 *
 * ```javascript
 * FSBL.Clients.NotificationClient.unsubscribeAll();
 * ```
 */
export const unsubscribeAll = () => {
    const controller = getControllerInstance();
    Logger.system.info("unsubscribeAll() called");
    controller.subscriptions.forEach((subscription) => {
        query(ROUTER_ENDPOINTS.UNSUBSCRIBE, subscription.id).then();
        removeResponder(subscription.id);
    });
    controller.subscriptions = [];
};
/**
 * @language javascript, dotnet
 * Sets a user preference on which notifications to mute. All future notifications that match the filter will
 * have the mute flag set to true
 *
 * ```javascript
 * // Mute all notifications coming from the 'order-management-system' source with type 'informational'
 * await FSBL.Clients.NotificationClient.mute({
 *   source: "order-management-system",
 *   type: "informational"
 * });
 * ```
 * @param filter Notifications to apply action to.
 * @throws If no error is thrown the service has performed the mute successfully.
 */
export const mute = async (filter) => {
    Logger.system.info("mute() called with params: ", filter);
    return await query(ROUTER_ENDPOINTS.MUTE, {
        filter: filter,
    });
};
/**
 * @language javascript, java, dotnet
 * Sets the filter preference on which notifications to mute. All future notifications that match the filter will
 * have the mute flag set to false
 *
 * ```javascript
 * // Unmute all notifications coming from the 'order-management-system' source with type 'informational'
 * await FSBL.Clients.NotificationClient.unmute({
 *   source: "order-management-system",
 *   type: "informational"
 * });
 * ```
 * @param filter Notifications to apply action to.
 * @throws If no error is thrown the service has performed the unmute successfully.
 */
export const unmute = async (filter) => {
    Logger.system.info("unmute() called with params: ", filter);
    return await query(ROUTER_ENDPOINTS.UNMUTE, {
        filter: filter,
    });
};
/**
 * Gets the available notification sources in Finsemble.
 *
 * @returns A string array with all the configured sources in Finsemble.
 */
const getSources = async () => {
    Logger.system.info("getSources() called");
    return await query(ROUTER_ENDPOINTS.SOURCES, {});
};
/**
 * @language javascript, dotnet
 * Gets the current mute filters for Notification sources in Finsemble.
 *
 * @return an array with all the mute filters.
 */
export const getMuteFilters = async () => {
    Logger.system.info("getMuteFilters() called");
    return await query(ROUTER_ENDPOINTS.MUTE_FILTERS, {});
};
/**
 * @language javascript, dotnet
 * Gets the current notifications preferences object. It tries to load the configs saved in
 * the user preferences and will fallback to config/schema defaults if none are present.
 *
 * @returns The notification preferences object.
 */
export const getPreferences = async () => {
    Logger.system.info("getPreferences() called");
    return await query(ROUTER_ENDPOINTS.PREFERENCES, {});
};
/**
 * @ignore
 */
export const onClose = (cb) => {
    unsubscribeAll();
    cb === null || cb === void 0 ? void 0 : cb();
};
/**
 * @ignore
 */
export const NotificationClient = {
    /**
     * For backward compatibility
     * @private
     * @deprecated
     */
    // @ts-ignore This will be removed in a few releases
    initialize: () => {
        Logger.system.deprecated("It is no longer necessary to call NotificationClient.initialize()");
    },
    /**
     * For backward compatibility
     * @private
     * @deprecated
     */
    onReady: (cb) => {
        Logger.system.deprecated("It is no longer necessary to call NotificationClient.onReady()");
        cb === null || cb === void 0 ? void 0 : cb();
    },
    fetchHistory,
    getLastIssuedAt,
    performAction,
    markRead,
    markUnread,
    deleteNotifications,
    notify,
    subscribe,
    unsubscribe,
    unsubscribeAll,
    mute,
    unmute,
    getPreferences,
    getMuteFilters,
    /**
     * @deprecated
     */
    getSources,
    onClose,
    Notification,
    ActionTypes,
    Subscription,
    Action,
    Filter,
    actionTypes: ActionTypes,
};
/**
 * @ignore
 */
export default NotificationClient;
//# sourceMappingURL=notificationClient.js.map