import { Arr, Limiter, Obj, Http } from "@intelligentgraphics/ig.utilities";
import { IGXR } from "@intelligentgraphics/igd.core";
import Logger from "../Logger";
import { addIGXR, parseIGXR } from "./Util";
/** Loader for IGXR data. */
export class IGXRLoader {
    /** Constructor. */
    constructor(igxrData) {
        this.intializePromises = {};
        if (!igxrData || !igxrData.length) {
            throw new Error("Expected an array of at least one igxr object");
        }
        parseIGXR(igxrData[0]);
        this._igxr = igxrData[0];
        for (const raw of igxrData.slice(1)) {
            try {
                addIGXR(this._igxr, raw);
            }
            catch (err) {
                console.error("Encountered errors when parsing igxr. Skipping", raw);
            }
        }
        if (!this._igxr) {
            const tLogMsg = "no IGXR data given";
            Logger.fatal("IGXRLoader:", tLogMsg);
            throw new Error(tLogMsg);
        }
        this.registerCatalogs = this.registerCatalogs.bind(this);
    }
    addIGXR(igxrData) {
        addIGXR(this._igxr, igxrData);
    }
    loadCatalog(sourceId, registry, connectionManager, localizationManager) {
        return this.registerCatalogs([sourceId], registry, connectionManager, localizationManager).then(registeredIds => registeredIds[0]);
    }
    loadCatalogs(sourceIds, registry, connectionManager, localizationManager) {
        return this.registerCatalogs(sourceIds, registry, connectionManager, localizationManager);
    }
    addAllCatalogsToSession(session) {
        const catalogs = this.getCatalogList();
        return this.registerCatalogs(catalogs.map(cat => `${cat.ManufacturerId}.${cat.CatalogId}`), session, session, session);
    }
    /**
     * Registers services for the specified catalogs inside the session
     *
     * @param {Session} session
     * @param {CatalogIdentifier[]} catalogs
     * @returns
     */
    addCatalogsToSession(catalogs, session) {
        return this.registerCatalogs(catalogs, session, session, session);
    }
    addCatalogToSession(catalog, session) {
        return this.addCatalogsToSession([catalog], session).then(([id]) => id);
    }
    /**
     * Register selected catalogs in the Dispatcher.
     * @param catalogs A list of catalogs Ids.
     */
    async registerCatalogs(catalogs, registry, connectionManager, localizationManager) {
        const ids = catalogs.map(cat => {
            const { manufacturerId, catalogId, } = this.extractCatalogAndManufacturerId(cat);
            return `${manufacturerId}.${catalogId}`;
        });
        const uniqueIds = Arr.unique(ids);
        const idMap = {};
        const { queue } = new Limiter(5);
        await Promise.all(uniqueIds.map(async (id) => {
            const [man, cat] = id.split(".");
            const dispatcherId = await queue(() => this.registerCatalog(man, cat, registry, connectionManager, localizationManager).catch(err => {
                Logger.error(`Could not register catalog ${man}.${cat}.`, err);
                return undefined;
            }));
            if (dispatcherId !== undefined) {
                idMap[id] = dispatcherId;
            }
        }));
        return ids.map(id => idMap[id]);
    }
    registerCatalog(manufacturerId, catalogId, registry, connectionManager, localizationManager) {
        const sourceId = manufacturerId + "." + catalogId;
        if (registry.hasSource(sourceId)) {
            return Promise.resolve(sourceId);
        }
        // if the same catalog is already being instantiated, skip this funtion and return the other promise.
        if (this.intializePromises[sourceId]) {
            return this.intializePromises[sourceId];
        }
        // use async iife to create a new promise for this catalog and add it to the list of currently loading catalogs
        return (this.intializePromises[sourceId] = (async () => {
            const { catalog } = this.findCatalog(manufacturerId, catalogId);
            const serverType = this._igxr.Servers[catalog.Server].Type;
            const backend = await registry.loadBackend(serverType);
            if (!backend) {
                throw new Error(`No backend has been registered for type: ${serverType}`);
            }
            const { catalogManager, productManager: productDataManager, multiArticles, configurationService, } = await backend.initializeManagers({
                catalog,
                fetchData: (url) => this.loadExternalData(sourceId, url),
                dispatcherId: sourceId,
            }, connectionManager, localizationManager);
            registry.registerSource({
                sourceId,
                serverType,
                catalogManager,
                configurationService,
                productDataManager,
                multiArticles,
            });
            delete this.intializePromises[sourceId];
            return sourceId;
        })());
    }
    /**
     * returns the merged IGXR data
     */
    get IGXR() {
        return this._igxr;
    }
    getServerType(serverId) {
        const server = this._igxr.Servers[serverId];
        if (server) {
            return server.Type;
        }
        Logger.error("IGXRLoader.getServerType(): unknown server", serverId);
        return undefined;
    }
    getCatalog(identifier) {
        const { catalogId, manufacturerId, } = this.extractCatalogAndManufacturerId(identifier);
        const manufacturer = this._igxr.Manufacturers[manufacturerId];
        if (manufacturer) {
            const catalog = manufacturer.Catalogs[catalogId];
            if (catalog) {
                return catalog;
            }
        }
        Logger.error("IGXRLoader.getCatalog(): unknown catalog", manufacturerId, catalogId);
        return undefined;
    }
    findOne(haystack, arr) {
        return arr.some(v => {
            return haystack.indexOf(v) >= 0;
        });
    }
    getAllManufacturers(language = "any", filterTags) {
        const arr = [];
        Obj.iter(this._igxr.Manufacturers, (manufacturerName, manufacturer) => {
            const manuKeys = manufacturer.Keywords
                ? manufacturer.Keywords[language] || manufacturer.Keywords.any
                : undefined;
            if (!filterTags ||
                (manuKeys && this.findOne(manuKeys, filterTags))) {
                arr.push(manufacturer.Name[language] || manufacturer.Name.any);
            }
        });
        return arr;
    }
    getAllCatalogTags(language = "any") {
        let arr = [];
        Obj.iter(this._igxr.Manufacturers, (index, manufacturer) => {
            Obj.iter(manufacturer.Catalogs, (indexCat, catalog) => {
                if (catalog.Keywords) {
                    arr = Arr.unique(arr.concat(catalog.Keywords[language] || catalog.Keywords.any));
                }
            });
        });
        return arr;
    }
    /**
     * Returns the entries of the IGXR as a straightened list, optionally filtered.
     * @param language Language to use
     * @param filterManufacturers manufacturer names (in /language/)
     * @param filterTags category names (in /language/)
     */
    getCatalogList(language = "any", filterManufacturers = [], filterTags = [], sorted = false) {
        const results = [];
        for (const manufacturerId in this._igxr.Manufacturers) {
            const manufacturer = this._igxr.Manufacturers[manufacturerId];
            const manufacturerName = IGXR.getLocalizedText(manufacturer.Name, language);
            if (filterManufacturers.length > 0 &&
                filterManufacturers.indexOf(manufacturerName) === -1) {
                continue;
            }
            for (const catalogId in manufacturer.Catalogs) {
                const catalog = manufacturer.Catalogs[catalogId];
                let keywords;
                if (catalog.Keywords) {
                    keywords =
                        IGXR.getLocalizedText(catalog.Keywords, language) || [];
                }
                else {
                    keywords = [];
                }
                if (filterTags.length > 0 &&
                    !this.findOne(keywords, filterTags)) {
                    continue;
                }
                const catalogEntry = {
                    CatalogId: catalogId,
                    ManufacturerId: manufacturerId,
                    CatalogName: catalog.Name[language] || catalog.Name["any"],
                    ManufacturerName: manufacturerName,
                    Keywords: keywords,
                    Image: catalog.Image,
                    SourceId: `${manufacturerId}.${catalogId}`,
                    Hidden: catalog.Hidden === true || manufacturer.Hidden === true,
                };
                results.push(catalogEntry);
            }
        }
        if (sorted) {
            results.sort((a, b) => {
                return (a.ManufacturerName +
                    "." +
                    a.CatalogName).toUpperCase() <
                    (b.ManufacturerName + "." + b.CatalogName).toUpperCase()
                    ? -1
                    : 1;
            });
        }
        return results;
    }
    /**
     * Retrieves informations for the specified catalog identifier
     *
     * @param {CatalogIdentifier} catalogIdentifier
     * @param {string} [language="any"]
     * @returns {IGXRCatalogListEntry}
     */
    getCatalogInfo(catalogIdentifier, language = "any") {
        const { catalogId, manufacturerId, } = this.extractCatalogAndManufacturerId(catalogIdentifier);
        const { manufacturer, catalog } = this.findCatalog(manufacturerId, catalogId);
        let keywords;
        if (catalog.Keywords !== undefined) {
            keywords = IGXR.getLocalizedText(catalog.Keywords, language) || [];
        }
        else {
            keywords = [];
        }
        return {
            CatalogId: catalogId,
            CatalogName: IGXR.getLocalizedText(catalog.Name, language),
            ManufacturerId: manufacturerId,
            ManufacturerName: IGXR.getLocalizedText(manufacturer.Name, language),
            SourceId: `${manufacturerId}.${catalogId}`,
            Keywords: keywords,
            Image: catalog.Image,
            Hidden: catalog.Hidden === true || manufacturer.Hidden === true,
        };
    }
    getWizard(catalogIdentifier, language = "any") {
        const { manufacturerId, catalogId, } = this.extractCatalogAndManufacturerId(catalogIdentifier);
        const { catalog } = this.findCatalog(manufacturerId, catalogId);
        if (catalog.Settings === undefined ||
            catalog.Settings.Wizard === undefined) {
            return undefined;
        }
        return {
            Name: catalog.Settings.Wizard.Name === undefined
                ? undefined
                : IGXR.getLocalizedText(catalog.Settings.Wizard.Name, language),
            Image: catalog.Settings.Wizard.Image,
            LoadPath: catalog.Settings.Wizard.LoadPath,
            Options: catalog.Settings.Wizard.Options,
        };
    }
    findCatalog(manufacturerId, catalogId) {
        const manufacturer = this._igxr.Manufacturers[manufacturerId];
        if (manufacturer === undefined) {
            throw new Error(`Could not find a manufacturer for id "${manufacturerId}"`);
        }
        const catalog = manufacturer.Catalogs[catalogId];
        if (catalog === undefined) {
            throw new Error(`Could not find a catalog "${catalogId}" inside manufacturer "${manufacturerId}"`);
        }
        return {
            manufacturer,
            catalog,
        };
    }
    loadExternalData(source, url) {
        return Http.getJSON(url);
    }
    extractCatalogAndManufacturerId(catalogIdentifier) {
        if (typeof catalogIdentifier === "object") {
            if ("CatalogId" in catalogIdentifier) {
                return {
                    catalogId: catalogIdentifier.CatalogId,
                    manufacturerId: catalogIdentifier.ManufacturerId,
                };
            }
            return catalogIdentifier;
        }
        const [manufacturerId, catalogId] = catalogIdentifier.split(".");
        if (catalogId === undefined) {
            throw new Error("Could not parse catalog id and manufacturer id from registry id");
        }
        return {
            manufacturerId,
            catalogId,
        };
    }
}
