/*
Copyright The Infusion copyright holders
See the AUTHORS.md file at the top-level directory of this distribution and at
https://github.com/fluid-project/infusion/raw/main/AUTHORS.md.

Licensed under the Educational Community License (ECL), Version 2.0 or the New
BSD license. You may not use this file except in compliance with one these
Licenses.

You may obtain a copy of the ECL 2.0 License and BSD License at
https://github.com/fluid-project/infusion/raw/main/Infusion-LICENSE.txt
*/

"use strict";

fluid.registerNamespace("fluid.dom");

fluid.dom.getDocumentHeight = function (dokkument) {
    var body = $("body", dokkument)[0];
    return body.offsetHeight;
};

/*******************************************************
 * Separated Panel Preferences Editor Top Level Driver *
 *******************************************************/

fluid.defaults("fluid.prefs.separatedPanel", {
    gradeNames: ["fluid.prefs.prefsEditorLoader", "fluid.contextAware"],
    events: {
        onReady: null,
        onCreateSlidingPanelReady: {
            events: {
                onPrefsEditorMessagesLoaded: "onPrefsEditorMessagesLoaded"
            }
        },
        templatesAndMessagesReady: {
            events: {
                templatesLoaded: "onPrefsEditorTemplatesLoaded",
                messagesLoaded: "onPrefsEditorMessagesLoaded"
            }
        }
    },
    lazyLoad: false,
    contextAwareness: {
        lazyLoad: {
            checks: {
                lazyLoad: {
                    contextValue: "{fluid.prefs.separatedPanel}.options.lazyLoad",
                    gradeNames: "fluid.prefs.separatedPanel.lazyLoad"
                }
            }
        }
    },
    selectors: {
        reset: ".flc-prefsEditor-reset",
        prefsEditor: ".flc-slidingPanel-panel",
        containerMarker: ".flc-prefsEditor-main"
    },
    styles: {
        blockingClasses: ["fl-blocking-theme", "fl-blocking-font", "fl-blocking-input-enhanced"]
    },
    listeners: {
        "onReady.bindEvents": {
            listener: "fluid.prefs.separatedPanel.bindEvents",
            args: ["{separatedPanel}.prefsEditor", "{separatedPanel}.innerEnhancer", "{separatedPanel}"]
        },
        "onCreate.hideReset": {
            listener: "fluid.prefs.separatedPanel.hideReset",
            args: ["{separatedPanel}"]
        },
        "onCreate.applyInitialClasses": {
            listener: "fluid.prefs.separatedPanel.applyInitialClasses",
            args: ["{separatedPanel}"]
        }
    },
    components: {
        slidingPanel: {
            type: "fluid.slidingPanel",
            container: "{separatedPanel}.container",
            createOnEvent: "onCreateSlidingPanelReady",
            options: {
                gradeNames: ["fluid.prefs.msgLookup"],
                strings: {
                    showText: "{that}.msgLookup.slidingPanelShowText",
                    hideText: "{that}.msgLookup.slidingPanelHideText",
                    showTextAriaLabel: "{that}.msgLookup.showTextAriaLabel",
                    hideTextAriaLabel: "{that}.msgLookup.hideTextAriaLabel",
                    panelLabel: "{that}.msgLookup.slidingPanelPanelLabel"
                },
                components: {
                    msgResolver: {
                        type: "fluid.messageResolver",
                        options: {
                            messageBase: "{messageLoader}.resources.prefsEditor.parsed"
                        }
                    }
                }
            }
        },
        // innerEnhancer handles preferences applied to the UIO container
        innerEnhancer: {
            type: "fluid.uiEnhancer",
            container: "{separatedPanel}.dom.prefsEditor",
            options: {
                gradeNames: ["{pageEnhancer}.uiEnhancer.options.userGrades"],
                tocTemplate: "{pageEnhancer}.uiEnhancer.options.tocTemplate",
                distributeOptions: {
                    applyInitValue: {
                        record: true,
                        target: "{that enactor}.options.applyInitValue"
                    },
                    syllabifyHiddenContents: {
                        record: true,
                        target: "{that fluid.prefs.enactor.syllabification > parser}.options.parseHidden"
                    },
                    // Disable TOC and self voicing enactors from the innerEnhancer
                    removeTocEnactor: {
                        record: "fluid.emptySubcomponent",
                        target: "{that fluid.prefs.enactor.tableOfContents}.type"
                    },
                    removeOrator: {
                        record: "fluid.emptySubcomponent",
                        target: "{that fluid.orator}.type"
                    },
                    removeSelfVoicingEnactor: {
                        record: "fluid.emptySubcomponent",
                        target: "{that fluid.prefs.enactor.selfVoicing}.type"
                    }
                }
            }
        },
        prefsEditor: {
            createOnEvent: "templatesAndMessagesReady",
            container: "{separatedPanel}.dom.prefsEditor",
            options: {
                gradeNames: ["fluid.prefs.uiEnhancerRelay", "fluid.prefs.arrowScrolling"],
                // ensure that model and applier are available to users at top level
                model: {
                    preferences: "{separatedPanel}.model.preferences"
                    // The `local` model path is used by the `fluid.remoteModelComponent` grade
                    // for persisting and synchronizing model values with remotely stored data.
                    // local: {}
                },
                autoSave: true,
                events: {
                    updateEnhancerModel: "{that}.events.modelChanged"
                },
                listeners: {
                    "onCreate.bindReset": {
                        "this": "{separatedPanel}.dom.reset",
                        method: "on",
                        args: ["click", "{that}.reset"]
                    },
                    // Refresh the prefs editor with initial preferences
                    "{separatedPanel}.events.onReady": "{that}.events.onPrefsEditorRefresh.fire",
                    "afterReset.applyChanges": "{that}.applyChanges"
                }
            }
        }
    },
    outerEnhancerOptions: "{originalEnhancerOptions}.options.originalUserOptions",
    distributeOptions: {
        "separatedPanel.slidingPanel": {
            source: "{that}.options.slidingPanel",
            removeSource: true,
            target: "{that > slidingPanel}.options"
        },
        "separatedPanel.innerEnhancer.outerEnhancerOptions": {
            source: "{that}.options.outerEnhancerOptions",
            removeSource: true,
            target: "{that > innerEnhancer}.options"
        },
        "separatedPanel.ignoreSelectorForToc": {
            source: "{that}.options.selectors.containerMarker",
            target: "{fluid.prefs.builder > enhancer fluid.prefs.enactor.tableOfContents}.options.ignoreSelectorForEnactor.forEnactor"
        },
        "separatedPanel.ignoreSelectorForSelfVoicing": {
            source: "{that}.options.selectors.containerMarker",
            target: "{fluid.prefs.builder > enhancer fluid.prefs.enactor.selfVoicing}.options.ignoreSelectorForEnactor.forEnactor"
        },
        "separatedPanel.ignoreSelectorForSyllabification": {
            source: "{that}.options.selectors.prefsEditor",
            target: "{fluid.prefs.builder > enhancer fluid.prefs.enactor.syllabification}.options.ignoreSelectorForEnactor.forEnactor"
        }
    }
});

fluid.prefs.separatedPanel.hideReset = function (separatedPanel) {
    separatedPanel.locate("reset").hide();
};

fluid.prefs.separatedPanel.applyInitialClasses = function (separatedPanel) {
    separatedPanel.container[0].classList.add(separatedPanel.options.selectors.containerMarker.substring(1));
    separatedPanel.locate("prefsEditor")[0].classList.add(...separatedPanel.options.styles.blockingClasses);
};

fluid.prefs.separatedPanel.bindEvents = function (prefsEditor, innerEnhancer, separatedPanel) {
    // FLUID-5740: This binding should be done declaratively - needs ginger world in order to bind onto slidingPanel
    // which is a child of this component

    var separatedPanelId = separatedPanel.slidingPanel.panelId;
    separatedPanel.locate("reset").attr({
        "aria-controls": separatedPanelId,
        "role": "button"
    });

    separatedPanel.slidingPanel.events.afterPanelHide.addListener(function () {
        prefsEditor.events.onPrefsEditorRefresh.fire();
    }, "updateView");
    prefsEditor.events.afterReset.addListener(function (prefsEditor) {
        prefsEditor.events.onPrefsEditorRefresh.fire();
    }, "updateView");
    prefsEditor.events.onPrefsEditorRefresh.addListener(function () {
        innerEnhancer.updateModel(prefsEditor.model.preferences);
    }, "updateModel");
    separatedPanel.slidingPanel.events.afterPanelShow.addListener(function () {
        separatedPanel.prefsEditor.container.slideDown(separatedPanel.slidingPanel.options.animationDurations.show);
        separatedPanel.locate("reset").show();
    }, "openPanel");
    separatedPanel.slidingPanel.events.onPanelHide.addListener(function () {
        separatedPanel.locate("reset").hide();
    }, "hideReset");
};

/**
 * FLUID-5926: Some of our users have asked for ways to improve the initial page load
 * performance when using the separated panel prefs editor / UI Options. One option,
 * provided here, is to implement a scheme for lazy loading the instantiation of the
 * prefs editor, only instantiating enough of the workflow to allow display the
 * sliding panel tab.
 *
 * fluid.prefs.separatedPanel.lazyLoad modifies the typical separatedPanel workflow
 * by delaying the instantiation and loading of resources for the prefs editor until
 * the first time it is opened.
 *
 * Lazy Load Workflow:
 *
 * - On instantiation of the prefsEditorLoader only the messageLoader and slidingPanel are instantiated
 * - On instantiation, the messageLoader only loads preLoadResources, these are the messages required by
 *   the slidingPanel. The remaining message bundles will not be loaded until the "onLazyLoad" event is fired.
 * - After the preLoadResources have been loaded, the onPrefsEditorMessagesPreloaded event is fired, and triggers the
 *   sliding panel to instantiate.
 * - When a user opens the separated panel prefs editor / UI Options, it checks to see if the prefs editor has been
 *   instantiated. If it hasn't, a listener is temporarily bound to the onReady event, which gets fired
 *   after the prefs editor is ready. This is used to continue the process of opening the sliding panel for the first time.
 *   Additionally the onLazyLoad event is fired, which kicks off the remainder of the instantiation process.
 * - onLazyLoad triggers the templateLoader to fetch all of the templates and the messageLoader to fetch the remaining
 *   message bundles. From here the standard instantiation workflow takes place.
 */
fluid.defaults("fluid.prefs.separatedPanel.lazyLoad", {
    events: {
        onLazyLoad: null,
        onPrefsEditorMessagesPreloaded: null,
        onCreateSlidingPanelReady: {
            events: {
                onPrefsEditorMessagesLoaded: "onPrefsEditorMessagesPreloaded"
            }
        },
        templatesAndMessagesReady: {
            events: {
                onLazyLoad: "onLazyLoad"
            }
        }
    },
    components: {
        templateLoader: {
            createOnEvent: "onLazyLoad"
        },
        messageLoader: {
            options: {
                events: {
                    onResourcesPreloaded: "{separatedPanel}.events.onPrefsEditorMessagesPreloaded"
                },
                preloadResources: "prefsEditor",
                listeners: {
                    "onCreate.loadResources": { // Override of framework's definition
                        listener: "fluid.prefs.separatedPanel.lazyLoad.preloadResources",
                        args: ["{that}", "{that}.options.preloadResources"]
                    },
                    "{separatedPanel}.events.onLazyLoad": {
                        listener: "{that}.resourceFetcher.fetchAll"
                    }
                }
            }
        },
        slidingPanel: {
            options: {
                invokers: {
                    operateShow: {
                        funcName: "fluid.prefs.separatedPanel.lazyLoad.showPanel",
                        args: ["{separatedPanel}", "{that}.events.afterPanelShow.fire"]
                    }
                }
            }
        }
    }
});

fluid.prefs.separatedPanel.lazyLoad.showPanel = function (separatedPanel, callback) {
    if (separatedPanel.prefsEditor) {
        fluid.invokeLater(callback);
    } else {
        separatedPanel.events.onReady.addListener(function (that) {
            that.events.onReady.removeListener("showPanelCallBack");
            fluid.invokeLater(callback);
        }, "showPanelCallBack");
        separatedPanel.events.onLazyLoad.fire();
    }

};

/**
 * Used to override the standard "onCreate.loadResources" listener for fluid.resourceLoader component,
 * allowing for pre-loading of a subset of resources. This is required for the lazyLoading workflow
 * for the "fluid.prefs.separatedPanel.lazyLoad".
 *
 * @param {fluid.resourceLoader} that - the resourceLoader, augmented with the preload workflow
 * @param {String|String[]} toPreload - a String or an String[]s corresponding to the names
 *                                   of the resources, supplied in the resource argument, that
 *                                   should be loaded. Only these resources will be loaded.
 */
fluid.prefs.separatedPanel.lazyLoad.preloadResources = function (that, toPreload) {
    toPreload = fluid.makeArray(toPreload);
    var resourceFetcher = that.resourceFetcher;
    var preloadPromises = toPreload.map(function (onePreload) {
        return fluid.fetchResources.fetchOneResource(resourceFetcher.resourceSpecs[onePreload], resourceFetcher);
    });
    var preloadAllPromise = fluid.promise.sequence(preloadPromises);
    preloadAllPromise.then(function () {
        that.events.onResourcesPreloaded.fire(that.resources);
    });
};
