import { useEffect, useReducer, useState } from "react";
import { usePubSub } from "./usePubSub";
import { NOTIFICATIONS_PUBSUB_TOPIC } from "../components/notifications/constants";
import { FEAGlobals } from "../../common/Globals";
import { throttle } from "lodash";
const initialState = { notifications: [] };
/*
Action Types
*/
const UPDATE_MULTIPLE = "UPDATE_MULTIPLE";
const UPDATE = "UPDATE";
const REMOVE = "REMOVE";
/*
    Reducer
    */
function reducer(state, action) {
    // check to see if the notification exists if so update the values
    const notificationExistsInArray = (id) => state.notifications.find((notification) => notification.id === id);
    const newNotifications = [];
    switch (action.type) {
        case UPDATE_MULTIPLE:
            const existingNotifications = [...state.notifications];
            action.payload.map((notification) => {
                const notificationIndex = existingNotifications.findIndex((toCheck) => notification.id === toCheck.id);
                //if the notification exists then update it else add the new notification
                if (notificationIndex !== -1) {
                    existingNotifications[notificationIndex] = notification;
                }
                else {
                    newNotifications.push(notification);
                }
            });
            return {
                notifications: [...newNotifications, ...existingNotifications],
            };
        case UPDATE: {
            //if the notification exists then do nothing and return the current state else add the new notification to the state
            const notifications = notificationExistsInArray(action.payload.id)
                ? state.notifications.map((notification) => notification.id === action.payload.id ? action.payload : notification)
                : [action.payload, ...state.notifications];
            return { notifications };
        }
        case REMOVE:
            return {
                notifications: state.notifications.filter((notification) => notification.id !== action.payload.id),
            };
        default:
            throw new Error();
    }
}
/**
 * Thin wrapper around usePubSub to give us typesafety for the notification service's UI state.
 * Automatically appends this client's windowName which is used by the notification service to
 * display the notification center on an appropriate monitor.
 */
export const useNotificationUI = () => {
    const [state, setState] = usePubSub(NOTIFICATIONS_PUBSUB_TOPIC);
    const setStateAndAddWindowName = (newState) => {
        setState({
            ...newState,
            mostRecentWindowName: FEAGlobals.finsembleWindow.name,
        });
    };
    return [state, setStateAndAddWindowName];
};
const getActiveDescriptorsByType = (componentType) => new Promise(async (resolve, reject) => {
    FEAGlobals.FSBL.Clients.LauncherClient.getActiveDescriptors((err, data) => {
        var _a;
        const activeDescriptors = [];
        if (err) {
            reject(err);
        }
        else {
            for (const key in data) {
                if (data.hasOwnProperty(key) && ((_a = data[key]) === null || _a === void 0 ? void 0 : _a.componentType) === componentType) {
                    activeDescriptors.push(data[key]);
                }
            }
        }
        resolve(activeDescriptors);
    });
});
let toolbarWindowName;
export const getToolbarMonitor = async () => new Promise(async (resolve, reject) => {
    var _a;
    if (!toolbarWindowName) {
        const activeDescriptors = await getActiveDescriptorsByType("Toolbar");
        toolbarWindowName = (_a = activeDescriptors[0]) === null || _a === void 0 ? void 0 : _a.name;
    }
    const { err, data } = await FEAGlobals.FSBL.Clients.LauncherClient.getMonitorInfo({
        windowIdentifier: { windowName: toolbarWindowName },
    });
    if (err) {
        FEAGlobals.FSBL.Clients.Logger.system.error("Could not get Monitor info", err);
        reject(err);
        return;
    }
    resolve(data);
});
export const moveToToolbarMonitor = async () => {
    var _a, _b;
    const data = await getToolbarMonitor();
    const { data: windowMonitor } = await FEAGlobals.FSBL.Clients.LauncherClient.getMonitorInfo({
        windowIdentifier: { windowName: FEAGlobals.finsembleWindow.name },
    });
    const { data: bounds } = await FEAGlobals.finsembleWindow.getBounds();
    const width = ((_a = bounds === null || bounds === void 0 ? void 0 : bounds.right) !== null && _a !== void 0 ? _a : 0) - ((_b = bounds === null || bounds === void 0 ? void 0 : bounds.left) !== null && _b !== void 0 ? _b : 0);
    /**
     * It's been reported that on docked laptops and some curved monitors that getMonitorInfo returns undefined.
     * The root cause of this has not been determined.
     *
     * The second condition prevents execution if toolbar and window on the same monitor
     */
    if (!data || data.deviceId === (windowMonitor === null || windowMonitor === void 0 ? void 0 : windowMonitor.deviceId))
        return;
    // notifications-toasts window shouldn't have a full monitor height to prevent flickering
    const height = FEAGlobals.finsembleWindow.name === "notifications-toasts" ? 0 : data["availableRect"]["height"];
    FEAGlobals.finsembleWindow.setBounds({
        bounds: {
            top: data["availableRect"]["top"],
            left: data["availableRect"]["right"] - width,
            height: height,
            width: width,
        },
    }, (err) => {
        if (err) {
            FEAGlobals.FSBL.Clients.Logger.system.error("Could not update position", err);
        }
    });
};
export const getOS = () => {
    if (navigator.platform.indexOf("Win") != -1) {
        return "Windows";
    }
    else if (navigator.platform.indexOf("Linux") != -1) {
        return "Linux";
    }
    else if (navigator.platform.indexOf("Mac") != -1) {
        return "Mac";
    }
};
/**
 * Example for setting up button clicks
 *
 * @param notification
 * @param action
 */
export const doAction = (notification, action) => {
    try {
        FEAGlobals.FSBL.Clients.NotificationClient.performAction([notification], action).then(() => {
            // NOTE: The request to perform the action has be sent to the notifications service successfully
            // The action itself has not necessarily been performed successfully
            // 1) alert user notification has been sent (action may not have completed)
        });
    }
    catch (e) {
        // NOTE: The request to perform the action has failed
        console.error("could not create a notification client", e);
        FEAGlobals.FSBL.Clients.Logger.system.error("could not create a notification client", e);
    }
};
/**
 * Example for setting up button clicks
 *
 * @param filter
 */
export const mute = async (filter) => {
    try {
        await FEAGlobals.FSBL.Clients.NotificationClient.mute(filter);
    }
    catch (e) {
        // NOTE: The request to perform the action has failed
        console.error("Could not save mute preferences", e);
        FEAGlobals.FSBL.Clients.Logger.system.error("Could not save mute preferences", e);
    }
};
/**
 * Example for setting up button clicks
 *
 * @param filter
 */
export const unmute = async (filter) => {
    try {
        await FEAGlobals.FSBL.Clients.NotificationClient.unmute(filter);
    }
    catch (e) {
        // NOTE: The request to perform the action has failed
        console.error("Could not save mute preferences", e);
        FEAGlobals.FSBL.Clients.Logger.system.error("Could not save mute preferences", e);
    }
};
/**
 * Snooze a notification
 *
 * @param {INotification[]} notifications
 */
export const snoozeNotifications = async (notifications) => {
    const preferences = await FEAGlobals.FSBL.Clients.NotificationClient.getPreferences();
    const snoozeAction = {
        buttonText: "Snooze",
        type: "SNOOZE",
    };
    if (preferences && preferences.defaultSnoozePeriodSeconds) {
        snoozeAction.milliseconds = preferences.defaultSnoozePeriodSeconds * 1000;
    }
    for (let i = 0; i < notifications.length; i++) {
        doAction(notifications[i], snoozeAction);
    }
};
/**
 * Marks a notification as read
 *
 * @param {INotification[]} notifications
 */
export const markNotificationsUnread = (notifications) => {
    try {
        FEAGlobals.FSBL.Clients.NotificationClient.markUnread(notifications).then(() => {
            // NOTE: The request to perform the action has be sent to the notifications service successfully
            // The action itself has not necessarily been perform successfully
        });
    }
    catch (e) {
        console.error("Error marking messages as unread", e);
        FEAGlobals.FSBL.Clients.Logger.system.error("Could not mark notification as unread", e);
    }
};
/**
 * Marks a notification as read
 *
 * @param {INotification[]} notifications
 */
export const markNotificationsRead = (notifications) => {
    try {
        FEAGlobals.FSBL.Clients.NotificationClient.markRead(notifications).then(() => {
            // NOTE: The request to perform the action has be sent to the notifications service successfully
            // The action itself has not necessarily been perform successfully
        });
    }
    catch (e) {
        console.error("Error marking messages as read", e);
        FEAGlobals.FSBL.Clients.Logger.system.error("Could not mark notification as read", e);
    }
};
/**
 * Dismisses a notification
 *
 * @param {INotification[]} notifications
 */
export const deleteNotifications = (notifications) => {
    try {
        FEAGlobals.FSBL.Clients.NotificationClient.deleteNotifications(notifications).then(() => {
            // NOTE: The request to perform the action has be sent to the notifications service successfully
            // The action itself has not necessarily been perform successfully
        });
    }
    catch (e) {
        console.error("Error marking messages as read", e);
        FEAGlobals.FSBL.Clients.Logger.system.error("Could not mark notification as read", e);
    }
};
export const initializeCenterToggle = () => {
    const [uiState, setUIState] = useNotificationUI();
    const toggleCenter = () => {
        setUIState({
            ...uiState,
            showCenter: !uiState.showCenter,
        });
    };
    return {
        toggleCenter,
    };
};
/**
 * Get Notification's config from
 */
export const getNotificationConfig = () => {
    var _a, _b, _c, _d, _e;
    const config = (_a = FEAGlobals.FSBL.Clients.WindowClient.options) === null || _a === void 0 ? void 0 : _a.customData;
    return Object.assign(((_c = (_b = config === null || config === void 0 ? void 0 : config.window) === null || _b === void 0 ? void 0 : _b.data) === null || _c === void 0 ? void 0 : _c.notifications) || {}, {
        isTransparent: ((_e = (_d = config === null || config === void 0 ? void 0 : config.window) === null || _d === void 0 ? void 0 : _d.options) === null || _e === void 0 ? void 0 : _e.transparent) || false,
    });
};
export const getFetchHistoryParams = (config, params) => {
    var _a, _b;
    const notificationsHistory = (config === null || config === void 0 ? void 0 : config.notificationsHistory) || ((_a = params === null || params === void 0 ? void 0 : params.config) === null || _a === void 0 ? void 0 : _a.notificationsHistory);
    // Backwards compatibility - `since` replaced with `options` object
    let options = notificationsHistory.since ? notificationsHistory.since : null;
    // Use options object if it's been set in the component config
    if (!options) {
        options = notificationsHistory === null || notificationsHistory === void 0 ? void 0 : notificationsHistory.options;
    }
    // If no options object set and notificationsHistory is true set in config, use the params
    if (!options && typeof notificationsHistory === "boolean" && notificationsHistory) {
        options = (_b = params === null || params === void 0 ? void 0 : params.config) === null || _b === void 0 ? void 0 : _b.notificationsHistory.options;
    }
    const filter = notificationsHistory.filter ? notificationsHistory.filter : null;
    return [options, filter];
};
export default function useNotifications(params = {}) {
    const [state, dispatch] = useReducer(reducer, initialState);
    /**
     * It's possible for async code to try update the state of an unmounted component.
     * When this happens, React throws an error indicating there might be a memory leak.
     * This value is used to allow async code to update the state only when the component is mounted.
     */
    const [componentUnmounted, setComponentUnmounted] = useState(false);
    /*
        Action Creators
    */
    const removeNotification = (notification) => {
        dispatch({ type: REMOVE, payload: notification });
    };
    /**
     * Filters the incoming notifications array to leave the latest notifications with unique ids
     */
    const getUniqueNotificationsById = (notifications) => {
        const map = new Map();
        notifications.forEach((notification) => {
            if (!map.has(notification.id) || // filter by id
                (notification.receivedAt && new Date(notification.receivedAt) > new Date(map.get(notification.id).receivedAt))) {
                // time filter
                map.set(notification.id, notification);
            }
        });
        return Array.from(map.values());
    };
    /**
     * The next 2 functions wrap `addNotification` to reduce the number of calls and the number of UI redraws.
     * If 1 notification per second arrives, it is processed as before.
     * Otherwise, all incoming notifications in 1 second are grouped and added at the same time.
     */
    // temporary array to store notifications to add
    let notificationsToAdd = [];
    const addNotificationInner = () => {
        if (notificationsToAdd.length === 1) {
            dispatch({ type: UPDATE, payload: notificationsToAdd[0] });
        }
        else {
            // reverse the array to show notifications in the right order: the last incoming should be on the top
            const reversedArray = notificationsToAdd.reverse();
            // While throttling a few notifications with same id could come in. The function unifies them and lefts only latest one.
            const uniqueNotifications = getUniqueNotificationsById(reversedArray);
            dispatch({ type: UPDATE_MULTIPLE, payload: uniqueNotifications });
        }
        // don't use `notificationsToAdd.length = 0` to clean array below because it removes links to objects
        notificationsToAdd = [];
    };
    const addNotificationThrottle = throttle(addNotificationInner, 300, {
        leading: false,
    });
    const addNotification = (notification) => {
        // saves all notifications in an array so as not to lose during the `throttle` function
        notificationsToAdd.push(notification);
        addNotificationThrottle();
    };
    const addMultipleNotifications = (notifications) => {
        dispatch({ type: UPDATE_MULTIPLE, payload: notifications });
    };
    /**
     * Group Notifications by Type
     * @param notifications
     */
    const groupNotificationsByType = (notifications) => {
        const groupBy = (arr) => arr
            .map((notification) => { var _a; return (_a = notification.type) !== null && _a !== void 0 ? _a : "unknown"; })
            .reduce((acc, notificationType, index) => {
            acc[notificationType] = [...(acc[notificationType] || []), arr[index]];
            return acc;
        }, {});
        return groupBy(notifications);
    };
    /**
     * 	 * get the past notifications
     * WARNING - The default gets all notifications all the way back from 1969!!!
     * @param since
     * @param filter
     */
    const getNotificationHistory = (since = { sentAfter: "1969-12-31T23:59:59.999Z" }, filter = null) => FEAGlobals.FSBL.Clients.NotificationClient.fetchHistory(since, filter);
    const notificationIsActive = (notification, applyMuteFilterOverride) => {
        let applyMuteFilters = applyMuteFilterOverride;
        if (typeof applyMuteFilterOverride === "undefined") {
            const config = getNotificationConfig();
            applyMuteFilters = (config === null || config === void 0 ? void 0 : config.applyMuteFilters) ? config.applyMuteFilters : false;
        }
        return (!notification.isSnoozed &&
            !notification.isRead &&
            !notification.isDeleted &&
            (applyMuteFilters ? !notification.isMuted : true));
    };
    const activeNotifications = (notifications, applyMuteFilterOverride) => notifications.filter((notification) => notificationIsActive(notification, applyMuteFilterOverride));
    /**
     * Main init function to start the subscription
     */
    async function init() {
        var _a, _b, _c, _d, _e;
        try {
            const subscription = { id: null };
            const notificationConfig = getNotificationConfig();
            const filter = {
                exclude: [],
                include: [],
            };
            // make filters from the config
            if (notificationConfig) {
                ((_a = notificationConfig.filter) === null || _a === void 0 ? void 0 : _a.include) && ((_b = filter.include) === null || _b === void 0 ? void 0 : _b.push(...notificationConfig.filter.include));
                ((_c = notificationConfig.filter) === null || _c === void 0 ? void 0 : _c.exclude) && ((_d = filter.exclude) === null || _d === void 0 ? void 0 : _d.push(...notificationConfig.filter.exclude));
            }
            subscription.filter = filter;
            const notificationsHistory = (notificationConfig === null || notificationConfig === void 0 ? void 0 : notificationConfig.notificationsHistory) || ((_e = params === null || params === void 0 ? void 0 : params.config) === null || _e === void 0 ? void 0 : _e.notificationsHistory);
            if (notificationsHistory) {
                const [options, historyFilter] = getFetchHistoryParams(notificationConfig, params);
                const pastNotifications = await getNotificationHistory(options, historyFilter);
                if (!componentUnmounted) {
                    addMultipleNotifications(pastNotifications);
                }
            }
            const onNotification = function (notification) {
                // This function will be called when a notification arrives
                if (!componentUnmounted) {
                    if (notification.isDeleted) {
                        removeNotification(notification);
                    }
                    else {
                        addNotification(notification);
                    }
                }
            };
            return FEAGlobals.FSBL.Clients.NotificationClient.subscribe(subscription, onNotification);
        }
        catch (error) {
            console.error(error);
        }
    }
    // start receiving Notifications and putting them in state
    useEffect(() => {
        const subscribe = init();
        return () => {
            setComponentUnmounted(true);
            // Unsubscribe using the subscription ID
            (async () => {
                const subscribeObject = await subscribe;
                if (subscribeObject) {
                    await FEAGlobals.FSBL.Clients.NotificationClient.unsubscribe(subscribeObject.id);
                }
            })();
        };
    }, []);
    return {
        activeNotifications,
        addNotification,
        notificationIsActive,
        getNotificationHistory,
        groupNotificationsByType,
        notifications: state.notifications,
        removeNotification,
    };
}
//# sourceMappingURL=useNotifications.js.map