import async from "async";
import { FEAGlobals } from "../../../../common/Globals";
let ProcessMonitorStore;
//Regex code taken from https://gist.github.com/mattheyan/46a230da86a0894a5ff654b9139ec5f2
const DEFAULT_SORT_DIRECTION = "ascending";
const DEFAULT_STORE_DATA = {
    sort: {
        field: "name",
        direction: "ascending",
    },
};
const MAGIC_NUMBER_TERMINATION_TIMEOUT = 4000;
export let storeWrapper;
function createLocalStore(done) {
    FEAGlobals.FSBL.Clients.DistributedStoreClient.createStore({
        store: "Finsemble-ProcessMonitor-Local-Store",
        values: DEFAULT_STORE_DATA,
    }, (err, store) => {
        ProcessMonitorStore = store;
        storeWrapper = store;
        done();
    });
}
const Actions = {
    /**
     * Creates the local store, then gets the first cut at stats for the process list.
     */
    initialize: (cb) => {
        createLocalStore(() => {
            Actions.getInitialData();
            cb();
        });
    },
    /**
     * Gets the process list, pulls the statistics out, and renders the app. Also sets up an interval where we'll retrieve more stats from the system.
     */
    getInitialData: () => {
        FEAGlobals.FSBL.System.getProcessList(Actions.extractData);
        setInterval(() => {
            FEAGlobals.FSBL.System.getProcessList(Actions.extractData);
        }, 1000);
    },
    /**
     * Sets the sort field; if it's the same field as we had previously, we flip the sort direction.
     */
    setSort: async (field) => {
        var _a;
        const currentSort = (_a = (await (ProcessMonitorStore === null || ProcessMonitorStore === void 0 ? void 0 : ProcessMonitorStore.get(["sort"])))) === null || _a === void 0 ? void 0 : _a.data;
        let processLists = await (ProcessMonitorStore === null || ProcessMonitorStore === void 0 ? void 0 : ProcessMonitorStore.get(["processList"]));
        let processes;
        if (processLists === null || processLists === void 0 ? void 0 : processLists.data) {
            processes = processLists === null || processLists === void 0 ? void 0 : processLists.data;
        }
        let newSort;
        //If it's the same field as before, flip the direction
        if ((currentSort === null || currentSort === void 0 ? void 0 : currentSort.field) === field) {
            newSort = currentSort;
            if (currentSort.direction == "ascending") {
                newSort.direction = "descending";
            }
            else {
                newSort.direction = "ascending";
            }
        }
        else {
            newSort = {
                field,
                direction: DEFAULT_SORT_DIRECTION,
            };
        }
        await (ProcessMonitorStore === null || ProcessMonitorStore === void 0 ? void 0 : ProcessMonitorStore.set(["sort"], newSort));
        processes = await Actions.sortProcesses(processes);
        await (ProcessMonitorStore === null || ProcessMonitorStore === void 0 ? void 0 : ProcessMonitorStore.set(["processList"], processes));
    },
    /**
     * Extracts data from the process list (via the createDataModel function), and then sets the processList value on the store. This causes the UI to re-render.
     */
    extractData: (processes) => {
        Actions.createDataModel(processes, async (procs) => {
            //set store.
            await (ProcessMonitorStore === null || ProcessMonitorStore === void 0 ? void 0 : ProcessMonitorStore.set(["processList"], procs));
        });
    },
    /**
     * Converts the system.getProcessList response into something that's easily render-able.
     */
    createDataModel: async (processes, cb) => {
        const sortWindow = (a, b) => {
            if (a.webContentsName < b.webContentsName)
                return -1;
            if (a.webContentsName > b.webContentsName)
                return 1;
            return 0;
        };
        const viewOrWindowToProc = (item) => {
            return {
                webContentsName: item.webContentsName,
                windowName: item.windowName,
                type: item.type,
            };
        };
        const procs = processes.map((proc) => {
            const childWindows = [...Object.values(proc.webContents)]
                .map(viewOrWindowToProc)
                .sort(sortWindow);
            return {
                statistics: proc,
                visible: true,
                type: proc.type,
                childWindows,
            };
        });
        cb(await Actions.sortProcesses(procs));
    },
    /**
     * Sorting function. Handles ascending and descending.
     */
    sortProcesses: async (procs) => {
        const resp = await (ProcessMonitorStore === null || ProcessMonitorStore === void 0 ? void 0 : ProcessMonitorStore.get(["sort"]));
        const currentSort = resp && resp.data;
        let sortFN = (a, b) => {
            let aValue = a.statistics[currentSort.field];
            let bValue = b.statistics[currentSort.field];
            if (typeof aValue === "undefined" || typeof bValue === "undefined") {
                return 0;
            }
            //Comparing upper and lowercase names makes the sort look wrong. we equalize all strings here.
            if (typeof aValue === "string") {
                aValue = aValue.toLowerCase();
            }
            if (typeof bValue === "string") {
                bValue = bValue.toLowerCase();
            }
            if (currentSort.direction === "ascending") {
                if (aValue < bValue)
                    return -1;
                if (aValue > bValue)
                    return 1;
                return 0;
            }
            if (aValue > bValue)
                return -1;
            if (aValue < bValue)
                return 1;
            return 0;
        };
        return procs.sort(sortFN);
    },
    /**
     * Gets the current "flashing" state - whether the process monitor is currently flashing a window
     */
    getFlashingState: async (cb) => {
        await (ProcessMonitorStore === null || ProcessMonitorStore === void 0 ? void 0 : ProcessMonitorStore.get(["flashing"], cb));
    },
    /**
     * Gets the current "flashing" state for the process monitor
     */
    setFlashingState: async (value, cb) => {
        await (ProcessMonitorStore === null || ProcessMonitorStore === void 0 ? void 0 : ProcessMonitorStore.set(["flashing"], value, cb));
    },
    /**
     * Make the window flash a couple of times so that the user can identify it.
     */
    identifyWindow: function (winID) {
        if (winID.windowName.includes("Service"))
            return;
        this.setFlashingState(true);
        const OPACITY_ANIMATION_DURATION = 200;
        FEAGlobals.FSBL.FinsembleWindow.getInstance({ name: winID.windowName }, (err, win) => {
            let windowState = "hidden";
            function flash(n, done) {
                setTimeout(() => {
                    win.hide(() => {
                        setTimeout(() => {
                            win.show(() => {
                                done(null);
                            });
                        }, OPACITY_ANIMATION_DURATION / 1.5);
                    });
                }, OPACITY_ANIMATION_DURATION / 1.5);
            }
            win.isShowing({}, (error, isVisible) => {
                //cache the visible state of the window prior to making it flash. If it was hidden before, hide it when the flashing is done.
                windowState = isVisible ? "visible" : "hidden";
                win.show(() => {
                    win.bringToFront(() => {
                        //Flash 5 times, in series.
                        async.timesSeries(5, flash, () => {
                            if (windowState === "hidden") {
                                win.hide();
                            }
                            this.setFlashingState(false);
                        });
                    });
                });
            });
        });
    },
    /**
     * Terminates a process group. Containing windows for browser views will be closed.
     */
    terminateProcess: (webContents) => {
        const forceCloseProcess = () => {
            if (!webContents)
                return;
            // De-duped array of window names from both windows and browser views
            const windowNames = new Set(Object.values(webContents).map((item) => item.windowName));
            windowNames.forEach((windowName) => {
                Actions.closeWindow(windowName, true);
            });
        };
        //Ask user if they're sure. If they says "yes", close it.
        FEAGlobals.FSBL.Clients.DialogManager.open("YesNoDialog", {
            title: "Terminate Process?",
            question: "Terminating the process may close other apps. Are you sure you want to continue?",
            affirmativeResponseLabel: "Terminate",
            showNegativeButton: false,
        }, (err, response) => {
            if (err || response.choice === "affirmative") {
                forceCloseProcess();
            }
        });
    },
    /**
     * This function exists to make the UI feel snappy. May take a second or so for the window to close properly and for the change to flow to system.getProcessList. To make that lag go away, we immediately render the change.
     */
    removeWindowLocally: async function (windowName) {
        let result = await (ProcessMonitorStore === null || ProcessMonitorStore === void 0 ? void 0 : ProcessMonitorStore.get(["processList"]));
        let procs = result === null || result === void 0 ? void 0 : result.data;
        procs = procs.map((proc) => {
            if (proc.childWindows) {
                const cwIndex = proc.childWindows.findIndex((cw) => cw.windowName === windowName);
                proc.childWindows.splice(cwIndex, 1);
            }
            return proc;
        });
        await (ProcessMonitorStore === null || ProcessMonitorStore === void 0 ? void 0 : ProcessMonitorStore.set(["processList"], procs));
    },
    /**
     * Try to close the window. If that fails, try to force close the window. If that fails, ask if they'd like to terminate the parent process.
     */
    closeWindow: function (windowName, force = false) {
        const win = FEAGlobals.FSBL.System.Window.wrap(windowName);
        const onCloseFailure = () => {
            //If Actions.closeWindow was called with force === true, and the close failed.
            if (force) {
                alert("Cannot close window");
            }
            else {
                Actions.closeWindow(windowName, true);
            }
        };
        //If we can't close within 4 seconds, something's wrong - try to force close it.
        const closeTimeout = setTimeout(() => {
            onCloseFailure();
        }, MAGIC_NUMBER_TERMINATION_TIMEOUT);
        const onCloseSuccess = () => {
            Actions.removeWindowLocally(windowName);
            clearTimeout(closeTimeout);
        };
        win.close(force, onCloseSuccess, onCloseFailure);
    },
};
export { ProcessMonitorStore as Store, Actions };
//# sourceMappingURL=ProcessMonitorStore.js.map