/* eslint-disable @typescript-eslint/no-use-before-define */
import { findIndex, toPath } from "lodash";
import { getStore } from "./LauncherStore";
import AppDirectory from "../modules/AppDirectory";
import FDC3 from "../modules/FDC3";
import { iconFromConfig } from "../../../../icon/Icon";
import { FEAGlobals } from "../../../../../../common/Globals";
import async from "async";
let FDC3Client;
let appd;
let appDEndpoint;
export default {
    initialize,
    addApp,
    addNewFolder,
    addAppToFolder,
    removeAppFromFolder,
    renameFolder,
    deleteFolder,
    deleteApp,
    deleteTag,
    reorderFolders,
    getFolders,
    getFoldersList,
    getActiveFolderName,
    getActiveFolder,
    getSingleFolder,
    getAllAppsTags,
    getAllApps,
    getAllAppsFromFolder,
    getFormStatus,
    getSearchText,
    getSortBy,
    addTag,
    getTags,
    getApp,
    getDragDisabled,
    getConstants,
    findAppByField,
    getApps,
    refreshAppDefinitions,
};
const data = {};
const ADVANCED_APP_LAUNCHER = "Advanced App Launcher";
const NEW_FOLDER_NAME = "New folder";
// returns names of default folders.
function getConstants() {
    const DASHBOARDS = "Dashboards";
    const FAVORITES = "Favorites";
    return { ADVANCED_APP_LAUNCHER, DASHBOARDS, FAVORITES };
}
// Add to here if you want to disable dragging on a folder.
function getDragDisabled() {
    const { DASHBOARDS, FAVORITES } = getConstants();
    return [ADVANCED_APP_LAUNCHER, DASHBOARDS, FAVORITES];
}
function initialize(callback = Function.prototype) {
    FEAGlobals.FSBL.Clients.ConfigClient.get(["finsemble", "appDirectoryEndpoint"], (_, appDirectoryEndpoint) => {
        // cache value globally to be used when we need to fetch data for a given component.
        appDEndpoint = appDirectoryEndpoint;
        const store = getStore();
        data.folders = store === null || store === void 0 ? void 0 : store.values.appFolders.folders;
        data.foldersList = store === null || store === void 0 ? void 0 : store.values.appFolders.list;
        validateFolderDataStructure();
        data.apps = store === null || store === void 0 ? void 0 : store.values.appDefinitions;
        data.tags = store === null || store === void 0 ? void 0 : store.values.activeLauncherTags;
        data.activeFolder = store === null || store === void 0 ? void 0 : store.values.activeFolder;
        data.filterText = store === null || store === void 0 ? void 0 : store.values.filterText;
        data.sortBy = store === null || store === void 0 ? void 0 : store.values.sortBy;
        data.isFormVisible = store === null || store === void 0 ? void 0 : store.values.isFormVisible;
        data.configComponents = {};
        // Add listeners to keep our copy up to date
        store === null || store === void 0 ? void 0 : store.addListener({ field: "appFolders.folders" }, (foldersErr, dt) => {
            data.folders = dt.value;
        });
        store === null || store === void 0 ? void 0 : store.addListener({ field: "appFolders.list" }, (folderListErr, dt) => (data.foldersList = dt.value));
        store === null || store === void 0 ? void 0 : store.addListener({ field: "appDefinitions" }, (appDefinitionsErr, dt) => (data.apps = dt.value));
        store === null || store === void 0 ? void 0 : store.addListener({ field: "activeFolder" }, (activeFolderErr, dt) => {
            data.activeFolder = dt.value;
        });
        store === null || store === void 0 ? void 0 : store.addListener({ field: "isFormVisible" }, (visibilityErr, dt) => (data.isFormVisible = dt.value));
        store === null || store === void 0 ? void 0 : store.addListener({ field: "sortBy" }, (sortErr, dt) => (data.sortBy = dt.value));
        store === null || store === void 0 ? void 0 : store.addListener({ field: "activeLauncherTags" }, (launcherTagsErr, dt) => (data.tags = dt.value));
        store === null || store === void 0 ? void 0 : store.addListener({ field: "filterText" }, (filterTextErr, dt) => (data.filterText = dt.value));
        FEAGlobals.FSBL.Clients.RouterClient.subscribe("Launcher.update", (err, response) => {
            FEAGlobals.FSBL.Clients.Logger.debug("list updated", err, response);
            if (err) {
                return console.error(err);
            }
            refreshAppDefinitions(response.data.componentList);
        });
        // @deprecate in favor of SystemManagerClient.waitForStartup()
        FEAGlobals.FSBL.Clients.RouterClient.subscribe("Finsemble.Service.State.launcherService", (unsubscribeErr) => {
            if (unsubscribeErr) {
                FEAGlobals.FSBL.Clients.Logger.error("App Launcher encountered an error unsubscribing from the launcher service", unsubscribeErr);
            }
            loadInstalledComponentsFromStore(() => {
                // We load our stored components(config driven) here
                loadInstalledConfigComponents(() => {
                    updateAppsInFolders(callback);
                });
            });
        });
    });
}
// This gets a specific app in FDC3 and returns the results
function getApp(appID, cb = Function.prototype) {
    appd
        .get(appID)
        .then((app) => cb(null, app))
        .catch((err) => cb(err));
}
/**
 * Ensures all 'apps' properties on folders conform
 * to the new structure (Array vs object)
 */
function validateFolderDataStructure() {
    Object.keys(data.folders).forEach((folderName) => {
        const folder = data.folders[folderName];
        if (!Array.isArray(folder.apps)) {
            const warning = "Application Launcher Persistent Store has data stored in deprecated format. Please check distributedStore configs";
            // If the structure is wrong, notify the user in hopes that the foundation will be fixed
            FEAGlobals.FSBL.Clients.Logger.warn(warning);
            FEAGlobals.FSBL.UserNotification.alert("system", "ONCE-SINCE-STARTUP", "Distributed Store Type Mismatch", warning);
            let newApps = [];
            Object.values(folder.apps).forEach((app) => {
                newApps.push(app);
            });
            folder.apps = newApps;
        }
    });
    _setFolders();
}
// Update apps in folders with updated config information
function updateAppsInFolders(cb = Function.prototype) {
    // Loop through folders and update apps with new info
    const { ADVANCED_APP_LAUNCHER: advancedAppLauncherFolderName } = getConstants();
    Object.keys(data.folders).forEach((folderName) => {
        if (folderName === advancedAppLauncherFolderName)
            return;
        else {
            const folder = data.folders[folderName];
            Object.values(data.configComponents).forEach((configComp) => {
                let index = -1;
                folder.apps.forEach((folderApp, i) => {
                    if (folderApp.appID.toString().trim() === configComp.appID.toString().trim()) {
                        index = i;
                    }
                });
                if (index > -1) {
                    data.folders[folderName].apps.splice(index, 1, configComp);
                }
            });
        }
    });
    _setFolders(cb);
}
/**
 * Given a component config, will return tags, or an empty array.
 *
 * @param {*} componentConfig
 * @returns
 */
function extractTagsFromFinsembleComponentConfig(componentConfig) {
    if (!componentConfig.foreign)
        return [];
    if (!componentConfig.foreign.components)
        return [];
    if (!componentConfig.foreign.components["App Launcher"])
        return [];
    const { tags } = componentConfig.foreign.components["App Launcher"];
    if (tags) {
        if (typeof tags === "string") {
            return [tags];
        }
        return tags;
    }
    return [];
}
/**
 * Instantiates classes needed to interact with the appD server.
 * Only done when needed. If there are no components with source 'FDC3', this code will not execute.
 */
function lazyLoadAppD() {
    if (!FDC3Client)
        FDC3Client = new FDC3({ url: appDEndpoint });
    if (!appd)
        appd = new AppDirectory(FDC3Client);
}
/**
 * Here we load apps from FDC3
 * @param {*} cb
 */
function loadInstalledComponentsFromStore(cb = Function.prototype) {
    async.map(Object.values(data.apps), (component, componentDone) => {
        // Load FDC3 components here
        if (component.source && component.source === "FDC3") {
            lazyLoadAppD();
            // get the app info so we can load it into the launcher
            return getApp(component.appID, (err) => {
                if (err) {
                    // don't want to kill this;
                    deleteApp(component.appID);
                    console.error("there was an error loading from FDC3", component, err);
                }
                return componentDone();
            });
        }
        // Assumption - component is always a user defined component
        // when testing I was never able to get any other component types in this method
        FEAGlobals.FSBL.Clients.LauncherClient.addUserDefinedComponent(component, (compAddErr) => {
            if (compAddErr) {
                console.warn("Failed to add new app:", compAddErr);
            }
            componentDone(compAddErr);
        });
    }, (err) => {
        cb(err);
    });
}
// We load our apps that were loaded from the config.
function loadInstalledConfigComponents(cb = Function.prototype) {
    // Get the list of components from the launcher service
    FEAGlobals.FSBL.Clients.LauncherClient.getComponentList((err, componentList) => {
        /*
         * Update the folders under the "App" menu and delete any apps in the folder
         * that are no longer in the config and are not user defined components.
         */
        refreshAppDefinitions(componentList || {});
        cb();
    });
}
function getApps() {
    var _a;
    return (_a = data === null || data === void 0 ? void 0 : data.apps) !== null && _a !== void 0 ? _a : {};
}
function saveCustomComponents(componentList) {
    const userDefinedComponents = {};
    Object.values(componentList).forEach((config) => {
        var _a;
        if ((_a = config === null || config === void 0 ? void 0 : config.component) === null || _a === void 0 ? void 0 : _a.isUserDefined) {
            userDefinedComponents[config.component.type] = config;
        }
    });
    FEAGlobals.FSBL.Clients.StorageClient.save({
        topic: "finsemble",
        key: "userDefinedComponents",
        value: userDefinedComponents,
    });
}
function refreshAppDefinitions(componentList) {
    var _a;
    const componentTypeList = Object.keys(componentList);
    const { folders } = data;
    // Get the user defined apps
    const apps = Object.keys((_a = data === null || data === void 0 ? void 0 : data.apps) !== null && _a !== void 0 ? _a : {});
    Object.keys(folders).forEach((folderName) => {
        folders[folderName].apps.forEach((configDefinedApp, i) => {
            const { name, appID } = configDefinedApp;
            // If the component is not in the config component list and is not a user defined component
            if (!componentTypeList.includes(name) && !apps.includes(appID)) {
                // Delete app from the folder
                folders[folderName].apps.splice(i, 1);
            }
        });
    });
    // removes component from the configComponents list if it has been removed from the config
    Object.values(data.configComponents).forEach((component) => {
        if (!componentTypeList.includes(component.name) && !apps.includes(component.appID)) {
            delete data.configComponents[component.appID];
        }
    });
    componentTypeList.forEach((componentType) => {
        var _a, _b, _c, _d, _e, _f;
        const component = componentList === null || componentList === void 0 ? void 0 : componentList[componentType];
        if (component && component.component) {
            const launchableByUser = (_c = (_b = (_a = component === null || component === void 0 ? void 0 : component.foreign) === null || _a === void 0 ? void 0 : _a.components) === null || _b === void 0 ? void 0 : _b["App Launcher"]) === null || _c === void 0 ? void 0 : _c.launchableByUser;
            const displayName = (_d = component.component.displayName) !== null && _d !== void 0 ? _d : componentType;
            const iconObject = iconFromConfig((_f = (_e = component === null || component === void 0 ? void 0 : component.foreign) === null || _e === void 0 ? void 0 : _e.components) === null || _f === void 0 ? void 0 : _f.Toolbar, displayName, component === null || component === void 0 ? void 0 : component.appConfig);
            // Make sure the app is launchable by user
            if (launchableByUser && !component.component.isUserDefined) {
                data.configComponents[componentType] = {
                    appID: componentType,
                    icon: iconObject,
                    name: componentType,
                    displayName,
                    source: "config",
                    tags: extractTagsFromFinsembleComponentConfig(component),
                };
            }
            else if (!launchableByUser && data.configComponents[componentType]) {
                delete data.configComponents[componentType];
            }
        }
    });
    saveCustomComponents(componentList);
}
async function _setValue(field, value, cb) {
    var _a;
    const path = toPath(field);
    const result = await ((_a = getStore()) === null || _a === void 0 ? void 0 : _a.set(path, value));
    if (result === null || result === void 0 ? void 0 : result.err) {
        FEAGlobals.FSBL.Clients.Logger.error(`Advanced App Launcher: Failed to save: ${field}:${value}`);
    }
    if (cb)
        cb();
}
function _setFolders(cb = Function.prototype) {
    _setValue("appFolders.folders", data.folders, (err) => {
        if (err) {
            FEAGlobals.FSBL.Clients.Logger.error(`Advanced App Launcher: Failed to set folders: ${err}`);
            return;
        }
        cb();
    });
}
function _findAppIndexInFolders(appID, folderName) {
    return findIndex(data.folders[folderName].apps, (app) => app.appID === appID);
}
function getFolders() {
    return data.folders;
}
function getFoldersList() {
    var _a;
    return (_a = data.foldersList) !== null && _a !== void 0 ? _a : [];
}
function getAllApps() {
    return Object.assign({}, data.apps, data.configComponents);
}
function getFormStatus() {
    return data.isFormVisible;
}
function getSingleFolder(folderName) {
    return data.folders[folderName];
}
function reorderFolders(destIndex, srcIndex) {
    // There are two types of folders: Those that can be arranged, and those that cannot. We don't want to reorder the folders relative to the unorderable folders. Split them out, and then combine them after doing the filtering/swapping.
    const dragDisabled = getDragDisabled();
    const unorderableFolders = data.foldersList.filter((folderName) => dragDisabled.includes(folderName));
    const orderableFolders = data.foldersList.filter((folderName) => !dragDisabled.includes(folderName));
    const movedFolder = orderableFolders[destIndex];
    const remainingItems = orderableFolders.filter((item, index) => index !== destIndex);
    data.foldersList = [
        ...unorderableFolders,
        ...remainingItems.slice(0, srcIndex),
        movedFolder,
        ...remainingItems.slice(srcIndex),
    ];
    _setValue("appFolders.list", data.foldersList, () => { });
    return data.foldersList;
}
function addApp(app = {}, cb) {
    const appID = app.name;
    const folder = data.activeFolder;
    const newAppData = {
        appID,
        tags: app.tags,
        name: app.name,
        url: app.url,
        type: "component",
        canDelete: true,
    };
    FEAGlobals.FSBL.Clients.LauncherClient.addUserDefinedComponent(newAppData, (compAddErr) => {
        if (compAddErr) {
            cb({ code: "failed_to_add_app", message: compAddErr });
            console.warn("Failed to add new app:", compAddErr);
            return;
        }
        data.apps[appID] = newAppData;
        // Add to ADVANCED_APP_LAUNCHER folder
        data.folders[ADVANCED_APP_LAUNCHER].apps.push(newAppData);
        // Check if we need to add to another folder as well
        if (folder !== ADVANCED_APP_LAUNCHER) {
            data.folders[folder].apps.push(newAppData);
        }
        // Save appDefinitions and then folders
        _setValue("appDefinitions", data.apps, () => {
            _setFolders();
            cb && cb();
        });
    });
}
function deleteApp(appID) {
    // Delete app from any folder that has it
    for (const key in data.folders) {
        const appIndex = _findAppIndexInFolders(appID, key);
        data.folders[key].apps.splice(appIndex, 1);
    }
    // Delete app from the apps list
    FEAGlobals.FSBL.Clients.LauncherClient.removeUserDefinedComponent(data.apps[appID], () => {
        delete data.apps[appID];
        // Save appDefinitions and then folders
        _setValue("appDefinitions", data.apps, () => {
            _setFolders();
        });
    });
}
function addNewFolder(name) {
    // Each new folder is given a number, lets store them here
    // to get the highest one and then increment
    // result of this will be [0, 1, 2] etc
    const newFoldersNums = [
        0,
        // Find folders that have a name of "New folder" or "New folder #"
        ...data.foldersList
            .filter((folder) => folder.startsWith(NEW_FOLDER_NAME))
            .map((folder) => {
            const numbers = folder.match(/\d+/g) || [];
            return Math.max(...numbers);
        }),
    ];
    const highestFolderNumber = Math.max(...newFoldersNums);
    const folderName = name !== null && name !== void 0 ? name : `${NEW_FOLDER_NAME} ${highestFolderNumber + 1}`;
    const newFolder = {
        disableUserRemove: true,
        icon: "ff-adp-hamburger",
        canEdit: true,
        canDelete: true,
        apps: [],
    };
    data.folders[folderName] = newFolder;
    _setFolders(() => {
        // Update folders order if adding was successful
        data.foldersList.push(folderName);
        _setValue("appFolders.list", data.foldersList, () => { });
    });
}
function deleteFolder(folderName) {
    // Check if user is trying to delete the active folder
    if (folderName === data.activeFolder) {
        data.activeFolder = ADVANCED_APP_LAUNCHER;
        _setValue("activeFolder", data.activeFolder, () => { });
    }
    delete data.folders[folderName] &&
        _setFolders(() => {
            // Update the order of folders
            const index = data.foldersList.indexOf(folderName);
            data.foldersList.splice(index, 1);
            _setValue("appFolders.list", data.foldersList, () => { });
        });
}
function renameFolder(oldName, newName) {
    let indexOfOld = data.foldersList.findIndex((folderName) => folderName === oldName);
    if (indexOfOld > -1) {
        let oldFolder = data.folders[oldName];
        data.folders[newName] = oldFolder;
        data.foldersList.splice(indexOfOld, 1, newName);
        _setFolders(() => {
            _setValue("appFolders.list", data.foldersList, () => { });
            if (data.activeFolder === oldName) {
                _setValue("activeFolder", newName, () => { });
            }
            delete data.folders[oldName];
        });
    }
}
function addAppToFolder(folderName, app) {
    const appIndex = _findAppIndexInFolders(app.appID, folderName);
    if (appIndex < 0) {
        data.folders[folderName].apps.push({
            name: app.name,
            displayName: app.displayName,
            appID: app.appID,
        });
        _setFolders();
    }
}
function removeAppFromFolder(folderName, app) {
    const appIndex = _findAppIndexInFolders(app.appID, folderName);
    data.folders[folderName].apps.splice(appIndex, 1);
    _setFolders();
}
function getAllAppsFromFolder(folderName) {
    return data.folders[folderName].apps;
}
/**
 * Given a field, search through FDC3 apps and apps pulled in via config and return that app.
 * */
function findAppByField(field, value) {
    return (Object.values(data.apps).find((app) => (app ? app[field] === value : false)) ||
        Object.values(data.configComponents).find((app) => (app ? app[field] === value : false)));
}
function getActiveFolder() {
    const folder = (data === null || data === void 0 ? void 0 : data.folders) && (data === null || data === void 0 ? void 0 : data.activeFolder) ? data.folders[data.activeFolder] : null;
    if (!folder) {
        FEAGlobals.FSBL.Clients.Logger.warn(`activeFolder:"${data === null || data === void 0 ? void 0 : data.activeFolder}" not found!  Defaulting to empty folder`);
        return {
            name: (data === null || data === void 0 ? void 0 : data.activeFolder) || "",
            apps: [],
        };
    }
    Object.values(folder.apps).forEach((app) => {
        const appData = findAppByField("appID", app.appID);
        if (!appData) {
            app.tags = [];
        }
        else {
            app.tags = appData.tags;
        }
    });
    // Need a name for the AppDefinition/AppActionsMenu rendering
    folder.name = data.activeFolder;
    return folder;
}
function getActiveFolderName() {
    var _a;
    return (_a = data === null || data === void 0 ? void 0 : data.activeFolder) !== null && _a !== void 0 ? _a : "";
}
function getSearchText() {
    var _a;
    return (_a = data === null || data === void 0 ? void 0 : data.filterText) !== null && _a !== void 0 ? _a : "";
}
function getSortBy() {
    var _a;
    return (_a = data === null || data === void 0 ? void 0 : data.sortBy) !== null && _a !== void 0 ? _a : "Alphabetical";
}
function getTags() {
    var _a;
    return (_a = data === null || data === void 0 ? void 0 : data.tags) !== null && _a !== void 0 ? _a : [];
}
function getAllAppsTags() {
    var _a, _b;
    let tags = [];
    try {
        // Pull tags from applications installed via FDC3 and the component config.
        const apps = Object.values((_a = data.apps) !== null && _a !== void 0 ? _a : {}).concat(Object.values((_b = data.configComponents) !== null && _b !== void 0 ? _b : {}));
        apps.forEach((app) => {
            tags = tags.concat(app.tags);
        });
    }
    catch (e) {
        FEAGlobals.FSBL.Clients.Logger.error("Error getting all apps tags: ", e);
    }
    // return unique ones only
    return tags.filter((tag, index) => tags.indexOf(tag) === index);
}
function addTag(tag) {
    // Push new tag to list
    data.tags.indexOf(tag) < 0 && data.tags.push(tag);
    // Update tags in store
    _setValue("activeLauncherTags", data.tags, () => { });
}
function deleteTag(tag) {
    // Push new tag to list
    data.tags.splice(data.tags.indexOf(tag), 1);
    // Update tags in store
    _setValue("activeLauncherTags", data.tags, () => { });
}
//# sourceMappingURL=StoreActions.js.map