// Copyright (c) 2009-2026 SAP SE, All Rights Reserved
sap.ui.define([
    "sap/base/i18n/Formatting",
    "sap/base/i18n/Localization",
    "sap/base/Log",
    "sap/base/util/ObjectPath",
    "sap/m/library",
    "sap/ui/core/BusyIndicator",
    "sap/ui/core/ComponentContainer",
    "sap/ui/core/Lib",
    "sap/ui/core/routing/History",
    "sap/ui/thirdparty/hasher",
    "sap/ui/thirdparty/jquery",
    "sap/ui/thirdparty/URI",
    "sap/ushell/appRuntime/ui5/AppCommunicationMgr",
    "sap/ushell/appRuntime/ui5/performance/FesrEnhancer",
    "sap/ushell/appRuntime/ui5/renderers/fiori2/AccessKeysAgent",
    "sap/ushell/appRuntime/ui5/services/AppConfiguration", // must be included, do not remove
    "sap/ushell/appRuntime/ui5/services/AppLifeCycleAgent",
    "sap/ushell/appRuntime/ui5/services/ShellUIService",
    "sap/ushell/appRuntime/ui5/SessionHandlerAgent",
    "sap/ushell/EventHub",
    "sap/ushell/iconfonts",
    "sap/ushell/UI5ComponentType",
    "sap/ushell/ui5service/UserStatus",
    "sap/ushell/utils",
    "sap/ushell/utils/UrlParsing",
    "sap/ushell/utils/WindowUtils",
    "sap/ushell/appRuntime/ui5/AppRuntimeContext",
    "sap/ushell/renderer/utils",
    "sap/ushell/appRuntime/ui5/renderers/fiori2/Renderer"
], (
    Formatting,
    Localization,
    Log,
    ObjectPath,
    mobileLibrary,
    BusyIndicator,
    ComponentContainer,
    Library,
    History,
    hasher,
    jQuery,
    URI,
    AppCommunicationMgr,
    FesrEnhancer,
    AccessKeysAgent,
    AppConfiguration,
    AppLifeCycleAgent,
    ShellUIService,
    SessionHandlerAgent,
    EventHub,
    Iconfonts,
    UI5ComponentType,
    UserStatus,
    ushellUtils,
    UrlParsing,
    WindowUtils,
    AppRuntimeContext,
    RendererUtils,
    Renderer
) => {
    "use strict";
    /* eslint-disable valid-jsdoc */

    // track performance marks and enhance UI5's Frontend Sub Records with FLP specific information

    // shortcut for sap.m.URLHelper
    const URLHelper = mobileLibrary.URLHelper;

    FesrEnhancer.init();

    let Container;
    let DirtyState;
    let fnOrigURLHelperRedirect;
    let fnOrigURLHelperTriggerEmail;
    let bURLHelperReplaced = false;
    let bPluginsLoaded = false;
    let bHashChangeRegistered = false;
    let bPopupCallbackRegistered = false;
    let sManifestWidthFromUrl;

    /**
     * Application runtime for UI5 applications running in iframe
     *
     * @private
     */
    function AppRuntime () {
        /**
         * @private
         */
        this.main = async function () {
            // init postmessage handlers first as they might be needed for
            // expanding the URL immediately
            AppCommunicationMgr.init(true);

            const oInitialInformation = await this._collectInitialInformation(
                window["sap-ushell-config"],
                this._getURIParams()
            );
            await this._init(oInitialInformation);
            await this._executeInitialNavigation(oInitialInformation);
        };

        /**
         * Initializes the AppRuntime framework: sets up communication, the app runtime context,
         * the UI5 environment, services, post-message handlers, and the app lifecycle agent.
         *
         * @param {object} oInitialInformation result of {@link #_collectInitialInformation}
         * @returns {Promise<void>}
         * @private
         */
        this._init = async function (oInitialInformation) {
            const {
                navigationInfo: {
                    appId: sAppId,
                    isScube: bIsScube,
                    scubeNavigationData: oScubeNavigationData
                },
                ui5Configuration: {
                    modulePaths: oModulePaths,
                    additionalLibs: oAdditionalLibs,
                    manifestWidthFromUrl: sManifestWidthFromUrlParam
                }
            } = oInitialInformation;

            // Phase 1: init AppRuntimeContext
            this._initAppRuntimeContext(bIsScube, oScubeNavigationData);

            // Phase 2: Infrastructure setup
            this._setModulePaths(oModulePaths);
            const [oLocaleData] = await Promise.all([
                this._fetchLocaleData(bIsScube),
                this._loadAdditionalLibs(oAdditionalLibs),
                // must be included, do not remove
                // included separately due to synchronization issues with initializing page configuration
                ushellUtils.requireAsync(["sap/ushell/appRuntime/ui5/services/UserInfo"]),
                this._initServicesContainer()
            ]);

            // Phase 3: UI5 environment
            this._prepareUi5EnvironmentForApp(sManifestWidthFromUrlParam, oLocaleData);

            // Phase 4: Shell services and agents
            this._initPostMessages();
            this._initAppLifeCycleAgent(sAppId);
            SessionHandlerAgent.init();
            AccessKeysAgent.init();

            // Phase 5: Scube-specific async pre-warming
            if (AppRuntimeContext.getIsScube() === true) {
                Container.getServiceAsync("CommonDataModel");
            }

            // Phase 6: Notify outer shell of current inner app route
            this._informOuterShellAboutInnerAppRoute();
        };

        /**
         * @private
         */
        this._initAppRuntimeContext = function (bIsScube, oScubeNavigationData) {
            if (bIsScube) {
                AppRuntimeContext.setIsScube(true);
                AppRuntimeContext.setRemoteSystemId(oScubeNavigationData?.remoteSystemId);
                if (oScubeNavigationData?.appIntent === undefined) {
                    // should be handled in appLifecycleAgent who should throw
                    Log.error("Application cannot be opened. 'sAppIntent' is undefined");
                }
            }
            AppRuntimeContext.setAppLifeCycleAgent(AppLifeCycleAgent);
        };

        /**
         * @private
         */
        this._fetchLocaleData = function (bIsScube) {
            if (bIsScube) {
                return AppCommunicationMgr.postMessageToFLP("sap.ushell.services.UserInfo.getLocaleData", undefined, undefined, 3000, {});
            }
            return Promise.resolve();
        };

        /**
         * Creates and renders the application using the resolved app data produced by {@link #_executeInitialNavigation}.
         * This is the designated site for app-level error handling: failures here can be caught and
         * surfaced to the user without affecting the already-initialized shell infrastructure.
         *
         * @param {object} oAppData the app data returned by {@link #_executeInitialNavigation}
         * @param {string} oAppData.sAppId the application id
         * @param {object} oAppData.oUrlParams the URL parameters
         * @param {object} oAppData.oResolvedHashFragment the resolved hash fragment
         * @param {string} oAppData.sAppIntent the application intent (S/CUBE only)
         * @returns {Promise<void>}
         * @private
         */
        this._startApp = async function ({ sAppId, oUrlParams, oResolvedHashFragment, sAppIntent, sSapTouch }) {
            const oCreateApplicationResult = await this._createApplication(
                sAppId,
                oUrlParams,
                oResolvedHashFragment,
                sAppIntent,
                sSapTouch
            );
            this._renderApplication(oCreateApplicationResult);
        };

        /**
         * @private
         */
        this._executeInitialNavigation = async function (oInitialInformation) {
            const {
                navigationInfo: {
                    appId: sAppId,
                    urlParams: oUrlParams,
                    scubeNavigationData: oScubeNavigationData
                },
                ui5Configuration: {
                    sapTouch: sSapTouch
                }
            } = oInitialInformation;

            const oResolvedHashFragment = await this._resolveHashFragment(sAppId, oScubeNavigationData?.appIntent);

            await this._startApp({
                sAppId,
                oUrlParams,
                oResolvedHashFragment,
                sAppIntent: oScubeNavigationData?.appIntent,
                sSapTouch
            });
        };

        /**
         *
         * @param {object} oUshellConfig
         * @param {*} oUri
         * @returns {Promise<{navigationInfo: object, ui5Configuration: object}>}
         */
        this._collectInitialInformation = async function (oUshellConfig, oUri) {
            const oUrlParams = await AppLifeCycleAgent.expandSapIntentParams(oUri);

            // initial nav / app
            const oNavigationInfo = {
                urlParams: oUrlParams,
                appId: oUrlParams["sap-ui-app-id"],
                isScube: false
            };
            if (oUrlParams.hasOwnProperty("sap-remote-intent") === true) {
                oNavigationInfo.isScube = true;
                oNavigationInfo.scubeNavigationData = {
                    appIntent: oUrlParams["sap-remote-intent"],
                    remoteSystemId: oUrlParams["sap-remote-system"]
                };
            }

            // ui5 config
            const oUi5Configuration = {
                additionalLibs: ObjectPath.get("ui5.libs", oUshellConfig) || {},
                manifestWidthFromUrl: oUri["sap-manifest-width"],
                sapTouch: oUri["sap-touch"]
            };
            if (oUshellConfig.modulePaths) {
                oUi5Configuration.modulePaths = {};
                Object.keys(oUshellConfig.modulePaths).forEach((sModule) => {
                    oUi5Configuration.modulePaths[sModule.replace(/\./g, "/")] = oUshellConfig.modulePaths[sModule];
                });
            }

            return { navigationInfo: oNavigationInfo, ui5Configuration: oUi5Configuration };
        };

        this._setUI5LocaleAttributes = function (oLocaleData) {
            if (Object.keys(oLocaleData).length <= 0) {
                return;
            }

            if (oLocaleData.calendarType) {
                Formatting.setCalendarType(oLocaleData.calendarType);
            }
            if (oLocaleData.dateFormatShort) {
                Formatting.setDatePattern("short", oLocaleData.dateFormatShort);
            }
            if (oLocaleData.dateFormatMedium) {
                Formatting.setDatePattern("medium", oLocaleData.dateFormatMedium);
            }
            if (oLocaleData.numberFormatGroup) {
                Formatting.setNumberSymbol("group", oLocaleData.numberFormatGroup);
            }
            if (oLocaleData.numberFormatDecimal) {
                Formatting.setNumberSymbol("decimal", oLocaleData.numberFormatDecimal);
            }
            if (oLocaleData.timeFormatShort) {
                Formatting.setTimePattern("short", oLocaleData.timeFormatShort);
            }
            if (oLocaleData.timeFormatMedium) {
                Formatting.setTimePattern("medium", oLocaleData.timeFormatMedium);
            }
            if (oLocaleData.calendarMapping) {
                Formatting.setCustomIslamicCalendarData(oLocaleData.calendarMapping);
            }
            if (oLocaleData.timeZone) {
                Localization.setTimezone(oLocaleData.timeZone);
            }
            if (oLocaleData.currencyFormats) {
                Formatting.setCustomCurrencies(oLocaleData.currencyFormats);
            }
        };

        this._informOuterShellAboutInnerAppRoute = function () {
            const oParsedHash = UrlParsing.parseShellHash(hasher.getHash());
            if (oParsedHash && oParsedHash.appSpecificRoute && oParsedHash.appSpecificRoute.length > 0) {
                AppCommunicationMgr.postMessageToFLP("sap.ushell.services.CrossApplicationNavigation.setInnerAppRoute", {
                    appSpecificRoute: oParsedHash.appSpecificRoute
                });
            }
        };

        /**
         * @returns {string} the document URL
         * @private
         */
        this._getDocumentURL = function () {
            return document.URL;
        };

        /**
         * @returns {object} the parsed URI query parameters
         * @private
         */
        this._getURIParams = function () {
            return new URI(this._getDocumentURL()).search(true);
        };

        /**
         * @private
         */
        this._prepareUi5EnvironmentForApp = function (sManifestWidthParam, oLocaleData) {
            Iconfonts.registerFiori2IconFont();
            sManifestWidthFromUrl = sManifestWidthParam;
            AppConfiguration.setFullWidthFromManifest(sManifestWidthFromUrl);
            this._setUI5LocaleAttributes(oLocaleData || {});
        };

        /**
         * @private
         */
        this._initPostMessages = function () {
            const oRequestHandlers = {
                "sap.ushell.appRuntime.hashChange": {
                    async handler (oMessageBody, oMessageEvent) {
                        const sHash = oMessageBody.sHash;
                        if (typeof sHash === "string") {
                            const oNewHash = UrlParsing.parseShellHash(sHash);
                            const oOldHash = UrlParsing.parseShellHash(hasher.getHash());
                            if (oNewHash && oOldHash && oNewHash.semanticObject === oOldHash.semanticObject && oNewHash.action === oOldHash.action) {
                                hasher.replaceHash(sHash);
                            }
                        }
                    }
                },
                "sap.ushell.appRuntime.innerAppRouteChange": {
                    async handler (oMessageBody, oMessageEvent) {
                        const sHash = oMessageBody.oHash?.fullHash;
                        if (typeof sHash === "string") {
                            hasher.replaceHash(sHash);
                        }
                    }
                },
                "sap.ushell.appRuntime.setDirtyFlag": {
                    async handler (oMessageBody, oMessageEvent) {
                        await DirtyState.setDirtyFlag(oMessageBody.bIsDirty);
                    }
                },
                "sap.ushell.appRuntime.getDirtyFlag": {
                    async handler (oMessageBody, oMessageEvent) {
                        return await DirtyState.getDirtyFlag();
                    }
                },
                "sap.ushell.appRuntime.themeChange": {
                    async handler (oMessageBody, oMessageEvent) {
                        const currentThemeId = oMessageBody.currentThemeId;
                        Container.getUser().setTheme(currentThemeId);
                    }
                },
                "sap.ushell.appRuntime.buttonClick": {
                    async handler (oMessageBody, oMessageEvent) {
                        Renderer.handleHeaderButtonClick(
                            oMessageBody.buttonId
                        );
                    }
                },
                "sap.ushell.appRuntime.executeLogoutFunctions": {
                    async handler (oMessageBody, oMessageEvent) {
                        await Container.executeAsyncAndSyncLogoutFunctions();
                    }
                },
                "sap.ushell.appRuntime.uiDensityChange": {
                    async handler (oMessageBody, oMessageEvent) {
                        const isTouch = oMessageBody.isTouch;
                        jQuery("body")
                            .toggleClass("sapUiSizeCompact", (isTouch === "0"))
                            .toggleClass("sapUiSizeCozy", (isTouch === "1"));
                    }
                },
                "sap.ushell.appRuntime.handleDirtyStateProvider": {
                    async handler (oMessageBody, oMessageEvent) {
                        return Container.handleDirtyStateProvider(oMessageBody.oNavigationContext);
                    }
                },
                "sap.ushell.services.MessageBroker._execute": {
                    async handler (oMessageBody, oMessageEvent) {
                        const MessageBroker = await Container.getServiceAsync("MessageBroker");
                        await MessageBroker.handleMessage(oMessageBody);
                    }
                }
            };

            Object.keys(oRequestHandlers).forEach((sServiceRequest) => {
                AppCommunicationMgr.setRequestHandler(sServiceRequest, oRequestHandlers[sServiceRequest].handler);
            });
        };

        /**
         * @private
         */
        this.handleLinkElementOpen = function (sFlpUrl, event) {
            try {
                if (event.isDefaultPrevented && event.isDefaultPrevented() === true) {
                    return;
                }
                const oTarget = event.target;

                if (oTarget && oTarget.tagName === "A" && oTarget.href && oTarget.href.indexOf("#") > 0) {
                    if (oTarget.target === "_blank") {
                        const sTargetUrlSplit = oTarget.href.split("#");
                        if (sTargetUrlSplit[0].length === 0 || sTargetUrlSplit[0] === this._getDocumentURL().split("#")[0]) {
                            this.rebuildNewAppUrl(oTarget.href, sFlpUrl).then((sNewURL) => {
                                WindowUtils.openURL(sNewURL);
                            });
                            // We're returning false to determine that the default browser behaviour should NOT take place
                            // and we will be the one to handle the opening of the link (with 'window.open').
                            event.preventDefault();
                        }
                    } else if (oTarget.target === undefined || oTarget.target === "" || oTarget.target === "_top" || oTarget.target === "_self") {
                        // if the new hash is different than the current one, this is a cross application navigation
                        // and we will use the ushell service for that in order to properly create a new browser history
                        // entry
                        const oTargetUrlParts = oTarget.href.split("#");
                        const oCurrUrlParts = this._getDocumentURL().split("#");
                        if (oTargetUrlParts[0] === oCurrUrlParts[0]) {
                            const oTargetHashParts = oTargetUrlParts[1] && oTargetUrlParts[1].split("&/");
                            const oCurrHashParts = oCurrUrlParts[1] && oCurrUrlParts[1].split("&/");
                            if (typeof oTargetHashParts[0] === "string" && typeof oCurrHashParts[0] === "string" && oTargetHashParts[0] !== oCurrHashParts[0]) {
                                Container.getServiceAsync("Navigation").then((Navigation) => {
                                    Navigation.navigate({
                                        target: {
                                            shellHash: oTargetUrlParts[1]
                                        }
                                    });
                                });
                                event.preventDefault();
                            }
                        }
                    }
                }
            } catch (oError) {
                // do nothing means that the browser will take care of the click
            }
        };

        /**
         * @private
         */
        this.rebuildNewAppUrl = async function (sTargetUrl, sFlpUrl) {
            const sTargetUrlSplit = sTargetUrl.split("#");
            // Check if the destination URL equals to the IFrame URL
            if (sTargetUrlSplit[0].length === 0 || sTargetUrlSplit[0] === this._getDocumentURL().split("#")[0]) {
                // Replace the Iframe URL with the FLP URL or Add it before the #
                if (AppRuntimeContext.getIsScube()) {
                    const aResult = await AppRuntimeContext.checkIntentsConversionForScube([{
                        intent: `#${sTargetUrlSplit[1]}`
                    }]);
                    return sFlpUrl + aResult[0].intent;
                }
                return `${sFlpUrl}#${sTargetUrlSplit[1]}`;
            }
            return sTargetUrl;
        };

        /**
         * @private
         */
        this._setModulePaths = function (oModulePaths) {
            sap.ui.loader.config({ paths: oModulePaths });
        };

        /**
         * @private
         */
        this._loadAdditionalLibs = function (oAdditionalLibs) {
            const aLibPromise = [];
            Object.keys(oAdditionalLibs).forEach((sLib) => {
                if (oAdditionalLibs[sLib] === true) {
                    aLibPromise.push(Library.load(sLib));
                }
            });
            return Promise.all(aLibPromise);
        };

        /**
         * @private
         */
        this._initServicesContainer = async function () {
            [Container, DirtyState] = await ushellUtils.requireAsync([
                "sap/ushell/appRuntime/ui5/services/Container",
                "sap/ushell/navigation/DirtyState"
            ]);

            // Container.init also initializes the DirtyState
            await Container.init("apprt", { apprt: "sap.ushell.appRuntime.ui5.services.adapters" });

            // This section refers for the usecase where clicking on an Iframe link in order to open another Iframe
            // application in a new tab. If the destination URL equals to the IFrame URL, it means that the destination
            // Iframe URL is wrong and should be replaced with the FLP URL.
            Container.getFLPUrlAsync().then((sFlpUrl) => {
                jQuery(document).on("click.appruntime", this.handleLinkElementOpen.bind(this, sFlpUrl));
                jQuery(document).on("keydown.appruntime", (event) => {
                    if (event.code === "Enter") {
                        return this.handleLinkElementOpen(sFlpUrl, event);
                    }
                });
            });

            const ShellNavigationInternal = await Container.getServiceAsync("ShellNavigationInternal");
            ShellNavigationInternal.init(() => { });
            this._enableHistoryEntryReplacedDetection();
        };

        this._enableHistoryEntryReplacedDetection = function () {
            this._fnOriginalSetHash = hasher.setHash;
            this._fnOriginalReplaceHash = hasher.replaceHash;

            hasher.setHash = function () {
                if (hasher.disableCFLPUpdate === true) {
                    return this._fnOriginalSetHash.apply(hasher, arguments);
                }
                if (AppRuntimeContext.checkDataLossAndContinue()) {
                    return this._fnOriginalSetHash.apply(hasher, arguments);
                }
            }.bind(this);

            hasher.replaceHash = function () {
                if (hasher.disableCFLPUpdate === true) {
                    return this._fnOriginalReplaceHash.apply(hasher, arguments);
                }
                if (AppRuntimeContext.checkDataLossAndContinue()) {
                    return this._fnOriginalReplaceHash.apply(hasher, arguments);
                }
            }.bind(this);
        };

        /**
         * Initializes the AppLifeCycleAgent with the resolution module configuration and registers
         * the app lifecycle PostMessage handlers (create, destroy, store, restore). Also sends the
         * AppRuntime setup notification to the outer FLP shell and sets up the dirty-state unload handler.
         *
         * Must be called during init, before {@link #_resolveHashFragment}.
         *
         * @param {string} sAppId the application id (non-S/CUBE).
         * @private
         */
        this._initAppLifeCycleAgent = function (sAppId) {
            const sResolutionModule = window["sap-ushell-config"]?.ui5appruntime?.config?.appIndex?.module;
            const oAppIndexData = window["sap-ushell-config"]?.ui5appruntime?.config?.appIndex?.data;
            const bInEnableAppResolutionCache = window["sap-ushell-config"]?.ui5appruntime?.config?.appIndex?.enableCache;

            AppLifeCycleAgent.init(
                sResolutionModule,
                this._createApplication.bind(this),
                this._renderApplication.bind(this),
                bInEnableAppResolutionCache,
                sAppId,
                oAppIndexData
            );
        };

        /**
         * Resolves the initial hash fragment to an app component descriptor.
         * Requires {@link #_initAppLifeCycleAgent} to have been called first.
         *
         * @param {string} sAppId the application id.
         * @param {string} sAppIntent the application intent (S/CUBE only).
         * @returns {Promise<sap.ushell.services.Ui5ComponentLoader.AppProperties>} the resolved hash fragment.
         * @private
         */
        this._resolveHashFragment = async function (sAppId, sAppIntent) {
            return AppLifeCycleAgent.resolveHashFragment(sAppId, this._getDocumentURL(), sAppIntent);
        };

        /**
         * @private
         */
        this._setHashChangedCallback = function () {
            if (bHashChangeRegistered === true) {
                return;
            }

            hasher.changed.add(this._treatHashChanged.bind(this), this);
            bHashChangeRegistered = true;
        };

        this._treatHashChanged = async function (newHash, oldHash) {
            if (hasher.disableCFLPUpdate === true) {
                return;
            }

            if (newHash && typeof newHash === "string" && newHash.length > 0) {
                if (oldHash && typeof oldHash === "string" && oldHash.length > 0) {
                    // navigate via Navigation service for new apps
                    const oCompareHashResult = UrlParsing.compareHashes(newHash, oldHash);
                    if (!oCompareHashResult.sameIntent || !oCompareHashResult.sameParameters) {
                        Container.getServiceAsync("Navigation").then((Navigation) => {
                            Navigation.navigate({
                                target: {
                                    shellHash: newHash
                                }
                            });
                        });
                        return;
                    }
                }
                AppCommunicationMgr.postMessageToFLP("sap.ushell.appRuntime.hashChange", {
                    newHash: newHash,
                    direction: History.getInstance().getDirection()
                });
            }
        };

        this._createApplication = async function (sAppId, oURLParameters, oResolvedHashFragment, sAppIntent, sSapTouch) {
            // Getting the history direction, taken from the history object of UI5 (sent by the FLP).
            // The direction value is used to update the direction property of the UI5 history object
            // that is running in the FLP.
            if (oURLParameters.hasOwnProperty("sap-history-dir")) {
                const sDirection = oURLParameters["sap-history-dir"];

                const ShellNavigationInternal = await Container.getServiceAsync("ShellNavigationInternal");
                ShellNavigationInternal.hashChanger.fireEvent("hashReplaced", {
                    hash: ShellNavigationInternal.hashChanger.getHash(),
                    direction: sDirection
                });

                Log.debug(`AppRuntime.createApplication :: Informed by the FLP, to change the History direction property in the Iframe to: ${sDirection}`);
            }

            const oComponentContainer = new ComponentContainer({
                id: `${AppRuntimeContext.getIsScube() === true ? sAppIntent : sAppId}-content`,
                width: "100%",
                height: "100%"
            });

            oComponentContainer.addStyleClass("sapAppRuntimeBaseStyle");

            let isTouch = "0";
            if (sSapTouch !== undefined) {
                isTouch = sSapTouch;
                if (isTouch !== "0" && isTouch !== "1") {
                    isTouch = "0";
                }
            }
            jQuery("body")
                .toggleClass("sapUiSizeCompact", (isTouch === "0"))
                .toggleClass("sapUiSizeCozy", (isTouch === "1"));

            if (bPopupCallbackRegistered === false) {
                const oShellUIService = new ShellUIService({ scopeObject: oComponentContainer, scopeType: "component" });
                oShellUIService.setBackNavigation();

                /* eslint-disable no-new */
                /**
                 * @deprecated since 1.120. UserStatus service is deprecated without successor.
                 */
                new UserStatus({ scopeObject: oComponentContainer, scopeType: "component" });
                /* eslint-enable no-new */

                AppLifeCycleAgent.setShellUIService(oShellUIService);

                RendererUtils.init();
                bPopupCallbackRegistered = true;
            }

            this._setHashChangedCallback();

            const [
                Ui5ComponentLoader,
                AppLifeCycle
            ] = await Promise.all([
                Container.getServiceAsync("Ui5ComponentLoader"),
                Container.getServiceAsync("AppLifeCycle")
            ]);

            // remove the "sap.ushell" from libs to be loaded as it is not needed (its already
            // loaded as part of the appruntime bundle)
            if (oResolvedHashFragment.asyncHints && Array.isArray(oResolvedHashFragment.asyncHints.libs)) {
                oResolvedHashFragment.asyncHints.libs = oResolvedHashFragment.asyncHints.libs.filter((hint) => {
                    return hint.name !== "sap.ushell";
                });
            }

            const oResolvedHashFragmentWithComponentHandle = await Ui5ComponentLoader.createComponent(
                oResolvedHashFragment,
                [],
                UI5ComponentType.Application
            );
            const oComponentHandle = oResolvedHashFragmentWithComponentHandle.componentHandle;
            const oComponentInstance = oComponentHandle.getInstance();

            let sAppTitle;
            // Overwrite the title for the generic intent scenario in scube,
            // as the outer shell only has generic information about the app.
            if (AppRuntimeContext.getIsScube() && AppRuntimeContext.getIsGenericStartIntent()) {
                try {
                    sAppTitle = oComponentInstance.getManifestEntry("/sap.app/title");
                    if (typeof sAppTitle === "string") {
                        setTimeout(() => {
                            AppCommunicationMgr.postMessageToFLP("sap.ushell.services.ShellUIService.setTitle", { sTitle: sAppTitle });
                        }, 2000);
                    }
                } catch (oError) {
                    // do nothing
                }
            }

            oComponentContainer.setComponent(oComponentInstance);
            FesrEnhancer.setAppShortId(oComponentHandle);
            AppLifeCycle.prepareCurrentAppObject(
                "UI5",
                oComponentInstance,
                false,
                undefined
            );

            this.getCurrentAppInfo(AppLifeCycle, oURLParameters, oResolvedHashFragment, sAppTitle);
            this.considerChangingTheDefaultFullWidthVal(oResolvedHashFragmentWithComponentHandle);
            this.overrideUrlHelperFuncs();

            return {
                oComponentContainer: oComponentContainer,
                oResolvedHashFragmentWithComponentHandle: oResolvedHashFragmentWithComponentHandle,
                sAppIntent: sAppIntent || "",
                ui5ComponentName: oResolvedHashFragment.ui5ComponentName
            };
        };

        /**
         * @private
         */
        this.getCurrentAppInfo = function (AppLifeCycleService, oURLParameters, oResolvedHashFragment, sAppTitle) {
            const aAppInfoParams = [
                "appFrameworkVersion",
                "appVersion"
            ];
            if (!oURLParameters["sap-startup-params"] || oURLParameters["sap-startup-params"].indexOf("sap-fiori-id") === -1) {
                aAppInfoParams.push("appId");
            }
            if (!oURLParameters["sap-startup-params"] || oURLParameters["sap-startup-params"].indexOf("sap-ach") === -1) {
                aAppInfoParams.push("appSupportInfo");
            }

            const oCurrentApp = AppLifeCycleService.getCurrentApplication();
            oCurrentApp.getInfo(aAppInfoParams).then((oAppInfo) => {
                for (const sParameter in oAppInfo) {
                    if (oAppInfo[sParameter] === undefined || oAppInfo[sParameter] === "") {
                        delete (oAppInfo[sParameter]);
                    }
                }
                oAppInfo.appFrameworkId = "UI5";
                oAppInfo.technicalAppComponentId = oResolvedHashFragment.ui5ComponentName;
                if (typeof sAppTitle === "string") {
                    oAppInfo.title = sAppTitle;
                }
                AppLifeCycleAgent.setCurrentAppAttributes(oAppInfo);
                setTimeout(() => {
                    AppCommunicationMgr.postMessageToFLP("sap.ushell.services.AppLifeCycle.updateCurrentAppInfo", { info: oAppInfo });
                }, 500);
            });
        };

        /**
         * @private
         */
        this.considerChangingTheDefaultFullWidthVal = function (oResolvedHashFragmentWithComponentHandle) {
            // letter box width for users who use this parameter
            if (sManifestWidthFromUrl === true || sManifestWidthFromUrl === "true") {
                // Making sure that if the previous app was opened in letter box, it will not affect the default behavior
                // of the next app to be opened in full width state
                EventHub.emit("appWidthChange", false);
            }

            const oComponentInstance = oResolvedHashFragmentWithComponentHandle.componentHandle.getInstance();
            const oMetadata = oComponentInstance.getMetadata();
            if (oMetadata) {
                const vFullWidthUi = oComponentInstance.getManifestEntry("/sap.ui/fullWidth");
                const vFullWidthUi5Config = oComponentInstance.getManifestEntry("/sap.ui5/config/fullWidth");
                if (vFullWidthUi === true || vFullWidthUi === "true" || vFullWidthUi5Config === true || vFullWidthUi5Config === "true") {
                    EventHub.emit("appWidthChange", true);
                } else if (vFullWidthUi === false || vFullWidthUi === "false" || vFullWidthUi5Config === false || vFullWidthUi5Config === "false") {
                    EventHub.emit("appWidthChange", false);
                }
            }
        };

        /**
         * Overrides {@link sap.m.URLHelper#triggerEmail} and {@link sap.m.URLHelper#redirect}
         * so that they work correctly inside the AppRuntime iframe.
         *
         * <ul>
         *   <li><code>triggerEmail</code> is replaced with a postMessage call that delegates
         *       the actual mailto handling to the outer FLP shell.</li>
         *   <li><code>redirect</code> is wrapped so that navigation intents whose base URL
         *       matches the iframe document URL are rewritten to the FLP URL before the
         *       browser redirect happens.</li>
         * </ul>
         *
         * The method is idempotent – calling it more than once has no additional effect.
         * Use {@link #_resetUrlHelperFuncs} to restore the original functions (test support).
         *
         * @private
         */
        this.overrideUrlHelperFuncs = function () {
            if (bURLHelperReplaced === true) {
                return;
            }
            bURLHelperReplaced = true;

            if (URLHelper) {
                fnOrigURLHelperTriggerEmail = URLHelper.triggerEmail;
                URLHelper.triggerEmail = function (sTo, sSubject, sBody, sCc, sBcc) {
                    AppCommunicationMgr.postMessageToFLP("sap.ushell.services.ShellUIService.sendEmail", {
                        sTo: sTo,
                        sSubject: sSubject,
                        sBody: sBody,
                        sCc: sCc,
                        sBcc: sBcc,
                        sIFrameURL: this._getDocumentURL(),
                        bSetAppStateToPublic: true
                    });
                }.bind(this);

                fnOrigURLHelperRedirect = URLHelper.redirect;
                URLHelper.redirect = function (sURL, bNewWindow) {
                    if (sURL && bNewWindow === true && sURL.indexOf("#") >= 0) {
                        Container.getFLPUrlAsync().then((sFlpUrl) => {
                            this.rebuildNewAppUrl(sURL, sFlpUrl).then((sNewURL) => {
                                fnOrigURLHelperRedirect.call(URLHelper, sNewURL, bNewWindow);
                            });
                        });
                        return;
                    }
                    fnOrigURLHelperRedirect.call(URLHelper, sURL, bNewWindow);
                }.bind(this);
            }
        };

        /**
         * Resets the URLHelper overrides installed by overrideUrlHelperFuncs.
         * Restores the original URLHelper.redirect and URLHelper.triggerEmail functions.
         * @private
         */
        this._resetUrlHelperFuncs = function () {
            if (fnOrigURLHelperRedirect) {
                URLHelper.redirect = fnOrigURLHelperRedirect;
                fnOrigURLHelperRedirect = undefined;
            }
            if (fnOrigURLHelperTriggerEmail) {
                URLHelper.triggerEmail = fnOrigURLHelperTriggerEmail;
                fnOrigURLHelperTriggerEmail = undefined;
            }
            bURLHelperReplaced = false;
        };

        /**
         * This method registers the RTA agent plugin in the AppRuntime.
         * This agent plugin will be loaded only if the FLP will loads the RTA plugin
         * @private
         */
        function registerRTAPluginAgent (PluginManagerService) {
            PluginManagerService.registerPlugins({
                RTAPluginAgent: {
                    component: "sap.ushell.appRuntime.ui5.plugins.rtaAgent",
                    url: sap.ui.require.toUrl("sap/ushell/appRuntime/ui5/plugins/rtaAgent"),
                    config: {
                        "sap-plugin-agent": true
                    }
                }
            });
        }

        /**
         * This method registers the WA agent plugin in the AppRuntime.
         * This agent plugin will be loaded only if the FLP loads the WA plugin
         * @private
         */
        function registerWAPluginAgent (PluginManagerService) {
            PluginManagerService.registerPlugins({
                WAPluginAgent: {
                    component: "sap.ushell.appRuntime.ui5.plugins.scriptAgent",
                    url: sap.ui.require.toUrl("sap/ushell/appRuntime/ui5/plugins/scriptAgent"),
                    config: {
                        "sap-plugin-agent": true,
                        scube: false
                    }
                }
            });
        }

        /**
         * @private
         */
        this.loadPlugins = async function () {
            if (bPluginsLoaded === true) {
                return;
            }
            bPluginsLoaded = true;

            const PluginManager = await Container.getServiceAsync("PluginManager");
            registerRTAPluginAgent(PluginManager);
            if (AppRuntimeContext.getIsScube() === false) {
                registerWAPluginAgent(PluginManager);
            }

            await ushellUtils.promisify(PluginManager.loadPlugins("RendererExtensions"));
        };

        /**
         * @private
         */
        this._renderApplication = function (oCreateApplicationResult) {
            oCreateApplicationResult.oComponentContainer.placeAt("content");
            BusyIndicator.hide();

            setTimeout(() => {
                if (oCreateApplicationResult.oResolvedHashFragmentWithComponentHandle.componentHandle.getInstance().active) {
                    oCreateApplicationResult.oResolvedHashFragmentWithComponentHandle.componentHandle.getInstance().active();
                }

                this.loadPlugins();
            }, 0);

            AppLifeCycleAgent.setCurrentApp(
                oCreateApplicationResult.oComponentContainer,
                oCreateApplicationResult.sAppIntent,
                oCreateApplicationResult.oResolvedHashFragmentWithComponentHandle
            );
        };
    }

    return new AppRuntime();
});

