Source: widget/help/catalogues/SEN2.js

(function() {
    /**
     * this backend connector is able to handle SEN catalogue content for a RW model configuration
     * @augments Help4.widget.help.catalogues.SEN
     */
    Help4.widget.help.catalogues.SEN2 = class extends Help4.widget.help.catalogues.SEN {
        /**
         * @override
         * @param {Help4.typedef.SystemConfiguration} config - the system configuration
         * @returns {Promise<Help4.widget.help.catalogues.SEN.CatalogueProjects|null>}
         */
        static async load(config) {
            const {wpb, sen, ext} = Help4.SERVICE_LAYER;
            const {serviceLayer, rwModel, serviceUrl2} = config.help;
            if (!serviceUrl2 || serviceLayer !== ext || rwModel !== wpb && rwModel !== sen) return null;

            const {editor} = config.core;
            const {serverBaseUrl, pubUrl, headUrl} = /** @type {Help4.widget.help.catalogues.SEN.CatalogueUrlConfig} */ this._getUrls(config, serviceUrl2);

            const {SEN} = Help4.widget.companionCore;
            if (editor) {
                // editor: load PUB and HEAD
                /** @type {Array<{url: string}>} */ const request = [pubUrl, headUrl].map(url => ({url}));
                const [
                    /** @type {Array<?Help4.widget.companionCore.SEN.CatalogueResponse>} */ pub,
                    /** @type {Array<?Help4.widget.companionCore.SEN.CatalogueResponse>} */ head
                ] = await SEN.doMultifileRequest(config, {serverBaseUrl, request}) || [];

                /** @type {Help4.widget.help.catalogues.SEN.CatalogueProject[]|null} */ const pubProjects = this._parse(pub, 'SEN2');
                /** @type {Help4.widget.help.catalogues.SEN.CatalogueProject[]|null} */ const headProjects = this._parse(head, 'SEN2');
                return {pub: pubProjects, head: headProjects};
            } else {
                // non-editor: load PUB only
                /** @type {?Help4.widget.companionCore.SEN.CatalogueResponse} */ const pub = await SEN.doGetRequest(serverBaseUrl + pubUrl);
                /** @type {Help4.widget.help.catalogues.SEN.CatalogueProject[]|null} */ const pubProjects = this._parse(pub, 'SEN2');
                return {pub: pubProjects};
            }
        }

        /**
         * this function is to find the best match for RO - even if this selects a different project than the RW one
         * for EXT in help mode two projects will be merged into one (UACP/SEN + SEN2)
         * @param {string} screenId - current screen ID
         * @param {Help4.widget.help.CatalogueSelection[]} selected - currently selected projects
         * @param {Help4.widget.help.CatalogueProject[]} projects - all projects
         * @param {Help4.typedef.SystemConfiguration} config - the system configuration
         * @returns {?Help4.widget.help.CatalogueSelection}
         */
        static getHelpSelection(screenId, selected, projects, config) {
            // take the selected project from RO and search all possible extensions
            // even in case they have not been selected due to language scores or other metrics
            /** @type {Help4.widget.help.CatalogueSelection} */
            const roSelected = selected.find(({catalogueType: ct}) => ct === 'UACP' || ct === 'SEN');  // RO source (UACP, SEN)
            /** @type {Help4.widget.help.CatalogueProject[]} */
            const extensions = projects.filter(({contextType: a, _catalogueType: b, screen: c}) => a === 'HELP' && b === 'SEN2' && c === screenId);  // RW extensions (SEN2)
            if (!roSelected || !extensions.length) return null;  // not applicable

            const {companionCore} = Help4.widget;
            /** @type {Help4.widget.help.CatalogueProject} */ const roHelp = projects.find(({id}) => id === roSelected.id);

            // roLangList && rwLangList should be of equal length, and it is fine if they have duplicate entries
            // to find the equivalent RW language for RO, we need to find them by index and not by language code
            // Ex 1: ro = [et-EE, fi-FI, en-US], rw = [et, fi, en-US]
            // Ex 2: ro = [de-DE, de-DE, en-US], rw = [de-DE, de-CH, en-US]
            /** @type {string[]} */ const roLangList = companionCore[roHelp._dataType].getLanguages(config, true);
            /** @type {string[]} */ const rwLangList = companionCore.SEN.getLanguages(config, true);

            // get all RW languages that are better than RO; find a better RW than RO
            const roLangIndex = roLangList.indexOf(roHelp.language);
            const betterLangList = rwLangList.slice(0, roLangIndex);  // this will only have entries if RO not system language
            /** @type {?Help4.widget.help.CatalogueProject} */
            const betterRW = betterLangList.length &&
                extensions.find(({language: l}) => betterLangList.indexOf(l) >= 0);

            if (betterRW) {
                // a better RW project has been found; ignore RO
                const {_catalogueType: catalogueType, id} = betterRW;
                return {catalogueType, id};
            } else {
                // get RW language that is the same as RO; find a matching RW
                const matchingLangList = rwLangList.slice(0, roLangIndex + 1);
                /** @type {?Help4.widget.help.CatalogueProject} */
                let matchingRW = matchingLangList.length &&
                    extensions.find(({language: l}) => matchingLangList.indexOf(l) >= 0);
                const {languageFallbackMode} = config.core;

                if (!matchingRW && languageFallbackMode === 'mix') {
                    // RW content matching RO language not found
                    // select from fallback language
                    const rwFallbackLangList = rwLangList.slice(roLangIndex + 1, rwLangList.length);
                    for (const rwLang of rwFallbackLangList) {
                        matchingRW = extensions.find(({language}) => language === rwLang);
                        if (matchingRW) break;
                    }
                }

                if (matchingRW) {
                    // extension found; return joint result
                    const {_catalogueType: catalogueType, id} = roHelp;
                    const {_catalogueType: catalogueType2, id: id2} = matchingRW;
                    return {catalogueType, id, catalogueType2, id2};
                }
            }

            // no better RW and no extension has been found (this means that RW has only low priority languages); return RO
            return roSelected;
        }

        /**
         * delivers a map with EXT information, e.g. {srcId: destId, ...}
         * destId === <string>: this RW tour extends the one described by srcId
         * destId === null: this RW tour must not be displayed; due to low-priority language
         * @param {Help4.widget.help.CatalogueProject[]} projects
         * @param {Help4.typedef.SystemConfiguration} config - the system configuration
         * @returns {Object|null}
         */
        static getTourList(projects, config) {
            /** @type {Help4.widget.help.CatalogueProject[]} */ const roTours = projects.filter(({_catalogueType: ct}) => ct === 'UACP' || ct === 'SEN');
            /** @type {Help4.widget.help.CatalogueProject[]} */ const rwTours = projects.filter(({_catalogueType: ct}) => ct === 'SEN2');

            /** @type {Help4.widget.help.CatalogueProject} */ const roTour = roTours[0];
            if (!roTour) return null;  // not applicable

            const {companionCore} = Help4.widget;
            /** @type {string[]} */ const roLangList = companionCore[roTour._dataType].getLanguages(config, true);
            /** @type {string[]} */ const rwLangList = companionCore.SEN.getLanguages(config, true);

            // defines EXT targets for some tours
            // RO.id -> RW.id: an RO tour is extended by an RW one
            // RW.id -> null: an RW tour is extending an RO one but in the wrong language; to be ignored
            const map = {};
            /** @type {string[]} */ const accepted = [];

            // find possible RW extensions of RO tours
            for (const /** @type {Help4.widget.help.CatalogueProject} */ {language, loio, id} of roTours) {
                // get all RW language that are better or same than RO
                const matchingLangList = rwLangList.slice(0, roLangList.indexOf(language) + 1);

                // check whether we find a matching RW for RO
                /** @type {Help4.widget.help.CatalogueProject[]} */
                const matchingRW = matchingLangList.length &&
                    rwTours.filter(({language: l}) => matchingLangList.indexOf(l) >= 0) || [];

                // does RO have an extension in RW?
                const {id: rwId} = /** @type {Help4.widget.help.CatalogueProject} */ matchingRW.find(({loio: l}) => l === loio) || {};
                if (rwId) {
                    // roTour is extended by tour with rwId
                    map[id] = rwId;
                    accepted.push(rwId);
                }
            }

            // find and remove RW extensions that have the wrong language
            for (/** @type {Help4.widget.help.CatalogueProject} */ const rwTour of rwTours) {
                if (accepted.indexOf(rwTour.id) >= 0) continue;  // ignore RW tours that have been accepted for EXT already

                if (roTours.find(({loio}) => loio === rwTour.loio)) {
                    // RW tour is EXT for RO but obviously wrong language
                    // otherwise would have been selected for EXT in loop above
                    map[rwTour.id] = null;
                }
            }

            return accepted.length ? map : null;
        }
    }
})();