// Copyright (c) 2009-2026 SAP SE, All Rights Reserved

sap.ui.define([
    "sap/base/Log",
    "sap/base/util/deepClone",
    "sap/base/util/extend",
    "sap/base/util/isEmptyObject",
    "sap/base/util/UriParameters",
    "sap/base/assert",
    "sap/ui/core/EventBus",
    "sap/ui/thirdparty/URI",
    "sap/ui/Device",
    "sap/ui/core/BusyIndicator",
    "sap/ushell/EventHub",
    "sap/ui/thirdparty/hasher",
    "sap/ushell/utils",
    "sap/ushell/utils/UrlParsing",
    "sap/ushell/resources",
    "sap/ushell/appRuntime/ui5/services/Container",
    "sap/ushell/appRuntime/ui5/AppRuntimeContext",
    "sap/ushell/appRuntime/ui5/AppCommunicationMgr",
    "sap/ushell/appRuntime/ui5/performance/FesrEnhancer",
    "sap/ushell/navigation/DirtyState"
], (
    Log,
    deepClone,
    extend,
    isEmptyObject,
    UriParameters,
    assert,
    EventBus,
    URI,
    Device,
    BusyIndicator,
    EventHub,
    hasher,
    ushellUtils,
    UrlParsing,
    resources,
    Container,
    AppRuntimeContext,
    AppCommunicationMgr,
    FesrEnhancer,
    DirtyState
) => {
    "use strict";

    /**
     * @typedef {object} sap.ushell.appRuntime.ui5.services.AppLifeCycleAgent.AppResolutionAppInfo
     * the resolution result as provided by module configured via #onInit. Only the most common
     * properties are documented here.
     * @property {string} url the application URL.
     * @property {object} [applicationDependencies] the application dependencies.
     * @property {string} [name] the UI5 component name.
     * @property {boolean} [manifest] whether the application has a manifest.
     *
     * @private
     */

    function AppLifeCycleAgent () {
        const that = this;
        let sAppResolutionModule;
        let oAppResolution;
        let bEnableAppResolutionCache = true;
        const oAppResolutionCache = {};
        let fnCreateApplication;
        const oCachedApplications = {};
        const oRouterDisableRetriggerApplications = {};
        const oAppBackNavigationFunc = {};
        let oRunningApp;
        let fnRenderApp;
        let oShellUIService;
        let sDataLossMessage;

        /**
         * @param {string} sResolutionModule the application resolution module.
         * @param {function} fnInCreateApplication the create application function.
         * @param {function} fnInRenderApp the render application function.
         * @param {boolean} bInEnableAppResolutionCache whether the application resolution cache is enabled.
         * @param {string} sAppId the application id.
         * @param {object} oAppIndexData the application info.
         * @private
         */
        this.init = function (sResolutionModule, fnInCreateApplication, fnInRenderApp, bInEnableAppResolutionCache, sAppId, oAppIndexData) {
            this.resetCurrentApp();
            sAppResolutionModule = sResolutionModule;
            fnCreateApplication = fnInCreateApplication;
            fnRenderApp = fnInRenderApp;
            if (bInEnableAppResolutionCache !== undefined) {
                bEnableAppResolutionCache = bInEnableAppResolutionCache;
            }
            this.addAppIndexDataToCache(sAppId, oAppIndexData, true);

            // register this create & destroy as a appLifeCycleCommunication handler
            const oRequestHandlers = {
                "sap.ushell.services.appLifeCycle.create": {
                    async handler (oMessageBody, oMessageEvent) {
                        return that.create(oMessageBody);
                    }
                },
                "sap.ushell.services.appLifeCycle.destroy": {
                    async handler (oMessageBody, oMessageEvent) {
                        return that.destroy(oMessageBody);
                    }
                },
                "sap.ushell.services.appLifeCycle.store": {
                    async handler (oMessageBody, oMessageEvent) {
                        return that.store(oMessageBody);
                    }
                },
                "sap.ushell.services.appLifeCycle.restore": {
                    async handler (oMessageBody, oMessageEvent) {
                        return that.restore(oMessageBody);
                    }
                }
            };

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

            this._sendAppRuntimeSetup();
            // handle dirty state confirmation dialog within the iframe and not
            // in the outer shell
            sDataLossMessage = resources.i18n.getText("dataLossExternalMessage");
            window.onbeforeunload = function () {
                if (DirtyState.getDirtyFlagInternal()) {
                    return sDataLossMessage;
                }
                return undefined;
            };
        };

        /**
         * @private
         */
        this._sendAppRuntimeSetup = () => {
            let oSetup;
            if (window["sap-ushell-config"]?.ui5appruntime?.config?.stateful === false) {
                oSetup = {
                    session: {
                        bLogoutSupport: true
                    }
                };
            } else {
                oSetup = {
                    isStateful: true,
                    isKeepAlive: true,
                    isIframeValid: true,
                    session: {
                        bLogoutSupport: true
                    }
                };
            }

            const oUrl = new URL(location.href);
            if (oUrl.searchParams.has("sap-ushell-appruntime-stateful")) {
                oSetup.isStateful = oUrl.searchParams.get("sap-ushell-appruntime-stateful") === "true";
            }
            if (oUrl.searchParams.has("sap-ushell-appruntime-keepalive")) {
                if (oSetup.isStateful) {
                    oSetup.isKeepAlive = oUrl.searchParams.get("sap-ushell-appruntime-keepalive") === "true";
                } else {
                    Log.error("KeepAlive cannot be enabled when stateful is disabled");
                }
            }

            AppCommunicationMgr.postMessageToFLP("sap.ushell.services.appLifeCycle.setup", oSetup);
        };

        /**
         * @param {object} oMessageBody the message containing the URL and the hash.
         * @returns {Promise} after the application was created.
         * @private
         */
        this.create = async function (oMessageBody) {
            FesrEnhancer.startInteraction();

            const sIframeUrl = oMessageBody.sUrl;

            let sAppId;
            let sAppIntent;
            if (AppRuntimeContext.getIsScube()) {
                sAppIntent = UriParameters.fromURL(sIframeUrl).get("sap-remote-intent");
                assert(typeof sAppIntent === "string", "AppLifeCycleAgent::create - sAppIntent must be string");
            } else {
                sAppId = UriParameters.fromURL(sIframeUrl).get("sap-ui-app-id");
                assert(typeof sAppId === "string", "AppLifeCycleAgent::create - sAppId must be string");
            }

            hasher.disableCFLPUpdate = true;
            hasher.replaceHash("");
            hasher.replaceHash(oMessageBody.sHash);
            hasher.disableCFLPUpdate = false;

            // BusyIndicator work in hidden iframe only in chrome
            if (Device.browser.chrome) {
                BusyIndicator.show(0);
            }
            if (oShellUIService) {
                oShellUIService._resetBackNavigationCallback();
            }
            EventBus.getInstance().publish("launchpad", "appOpening", {});
            const oResolvedHashFragment = await that.resolveHashFragment(sAppId, sIframeUrl, sAppIntent);

            // Destroy the current running app before creating a new one
            this._destroyApp(oRunningApp.oApp);

            const sTargetUi5ComponentInstanceId = oResolvedHashFragment.ui5ComponentId;
            const sTargetUi5ComponentContainerId = `${AppRuntimeContext.getIsScube() === true ? sAppIntent : sAppId}-content`;
            const sTargetTechnicalComponentId = oResolvedHashFragment.ui5ComponentName;
            const sTargetComponentName = oResolvedHashFragment.applicationDependencies?.name;

            const sKeepAliveAppKey = Object.keys(oCachedApplications).find((sCachedKey) => {
                const oOldResolvedHashFragment = oCachedApplications[sCachedKey].resolvedHashFragment;
                const sOldUi5ComponentInstanceId = oOldResolvedHashFragment.ui5ComponentId;
                const sOldUi5ComponentContainerId = oCachedApplications[sCachedKey].oApp.getId();
                const sOldTechnicalComponentId = oOldResolvedHashFragment.ui5ComponentName;
                const sOldComponentName = oOldResolvedHashFragment.applicationDependencies?.name;

                if (sTargetUi5ComponentInstanceId === sOldUi5ComponentInstanceId) {
                    return true;
                }

                if (sTargetUi5ComponentContainerId === sOldUi5ComponentContainerId) {
                    return true;
                }

                if (sTargetTechnicalComponentId && sTargetTechnicalComponentId === sOldTechnicalComponentId) {
                    return true;
                }

                if (sTargetComponentName && sTargetComponentName === sOldComponentName) {
                    return true;
                }

                return false;
            });

            if (sKeepAliveAppKey) {
                that.destroy({
                    sCacheId: sKeepAliveAppKey
                }, true);
            }

            const oURLParameters = await that.expandSapIntentParams(new URI(sIframeUrl).query(true));

            const oCreateApplicationResult = await fnCreateApplication(sAppId, oURLParameters, oResolvedHashFragment, sAppIntent);
            fnRenderApp(oCreateApplicationResult);

            EventBus.getInstance().publish("sap.ushell", "appOpened", {});

            return {
                deletedKeepAliveId: sKeepAliveAppKey
            };
        };

        /**
         * @param {object} oMessageBody the message containing the cache id.
         * @returns {Promise} after the application was destroyed.
         * @private
         */
        this.destroy = async function (oMessageBody) {
            const sStorageKey = oMessageBody.sCacheId;

            if (oRunningApp.oApp === undefined && sStorageKey === undefined) {
                AppCommunicationMgr.postMessageToFLP("sap.ushell.appRuntime.isInvalidIframe", { bValue: true });
                return;
            }

            if (sStorageKey && oCachedApplications[sStorageKey]) {
                if (oCachedApplications[sStorageKey].oApp === oRunningApp.oApp) {
                    this.resetCurrentApp();
                }
                delete oRouterDisableRetriggerApplications[oCachedApplications[sStorageKey].oApp.getId()];
                this._destroyApp(oCachedApplications[sStorageKey].oApp);
                delete oCachedApplications[sStorageKey];
            } else if (oRunningApp.oApp) {
                delete oRouterDisableRetriggerApplications[oRunningApp.oApp.getId()];
                this._destroyApp(oRunningApp.oApp);
                this.resetCurrentApp();
            }
            await DirtyState.setDirtyFlag(false);

            if (oShellUIService) {
                oShellUIService._resetBackNavigationCallback();
            }
            FesrEnhancer.setAppShortId();

            EventBus.getInstance().publish("sap.ushell", "appClosed", {});
        };

        /**
         * @param {object} oMessageBody the message containing the cache id.
         * @returns {Promise} after the application was stored.
         * @private
         */
        this.store = async function (oMessageBody) {
            const sStorageKey = oMessageBody.sCacheId;
            const oCachedEntry = this.cloneCurrentAppEntry(oRunningApp);

            if (oRunningApp.oApp === undefined) {
                AppCommunicationMgr.postMessageToFLP("sap.ushell.appRuntime.isInvalidIframe", { bValue: true });
                return;
            }

            oCachedApplications[sStorageKey] = oCachedEntry;
            if (oShellUIService) {
                oAppBackNavigationFunc[sStorageKey] = oShellUIService._getBackNavigationCallback();
            }

            const oApp = oCachedEntry.oApp.getComponentInstance();
            oCachedEntry.oApp.setVisible(false);

            await DirtyState.setDirtyFlag(false);

            if (oApp) {
                if (oApp.isKeepAliveSupported && oApp.isKeepAliveSupported() === true) {
                    oApp.deactivate();
                } else {
                    if (oApp.suspend) {
                        oApp.suspend();
                    }
                    if (oApp.getRouter && oApp.getRouter()) {
                        oApp.getRouter().stop();
                    }
                }
            }
            this.resetCurrentApp();

            EventBus.getInstance().publish("sap.ushell", "appClosed", {});
        };

        /**
         * @param {object} oMessageBody the message containing the cache id and the hash.
         * @returns {Promise} after the application was restored.
         * @private
         */
        this.restore = async function (oMessageBody) {
            const sStorageKey = oMessageBody.sCacheId;
            const oCachedEntry = oCachedApplications[sStorageKey];
            const oApp = oCachedEntry.oApp.getComponentInstance();
            const bRouterDisableRetrigger = oRouterDisableRetriggerApplications[oCachedEntry.oApp.getId()];

            hasher.disableCFLPUpdate = true;
            hasher.replaceHash("");
            hasher.replaceHash(oMessageBody.sHash);
            hasher.disableCFLPUpdate = false;

            EventBus.getInstance().publish("launchpad", "appOpening", {});
            oCachedEntry.oApp.setVisible(true);

            if (oShellUIService) {
                oShellUIService.setBackNavigation(oAppBackNavigationFunc[sStorageKey]);
            }

            if (oApp) {
                if (oApp.isKeepAliveSupported && oApp.isKeepAliveSupported() === true) {
                    oApp.activate();
                } else {
                    if (oApp.restore) {
                        oApp.restore();
                    }
                    if (oApp.getRouter && oApp.getRouter() && oApp.getRouter().initialize) {
                        if (bRouterDisableRetrigger === false) {
                            oApp.getRouter().initialize();
                        } else {
                            oApp.getRouter().initialize(true);
                        }
                    }
                }

                oRunningApp = this.cloneCurrentAppEntry(oCachedEntry);
            }
            EventBus.getInstance().publish("sap.ushell", "appOpened", {});

            setTimeout(() => {
                AppCommunicationMgr.postMessageToFLP("sap.ushell.services.AppLifeCycle.setNewAppInfo", { info: oCachedEntry.oAppAttributes });
            }, 500);
        };

        /**
         * @param {object} oUrlParameters the URL parameters.
         * @returns {Promise} with the updated URL parameters.
         * @private
         */
        this.expandSapIntentParams = async function (oUrlParameters) {
            if (oUrlParameters.hasOwnProperty("sap-intent-param")) {
                const sAppStateKey = oUrlParameters["sap-intent-param"];
                try {
                    const sParameters = await AppCommunicationMgr.postMessageToFLP("sap.ushell.services.CrossApplicationNavigation.getAppStateData", { sAppStateKey: sAppStateKey });
                    delete oUrlParameters["sap-intent-param"];
                    const oUrlParametersExpanded = extend({}, oUrlParameters, (new URI(`?${sParameters}`)).query(true), true);
                    return oUrlParametersExpanded;
                } catch {
                    // fail silently
                }
            }

            return oUrlParameters;
        };

        /**
         * @param {string} sUrl the URL.
         * @param {string} sAppIntent the app intent.
         * @returns {Promise<string>} a promise with the application parameters string.
         * @private
         */
        this.addAppParamsToIntent = async function (sUrl, sAppIntent) {
            const oURLParameters = await that.expandSapIntentParams(new URI(sUrl).query(true));
            return that.getApplicationParameters(oURLParameters);
        };

        /**
         * @param {object} oURLParameters the URL parameters.
         * @returns {Promise<string>} a promise with the application parameters.
         * @private
         */
        this.getApplicationParameters = async function (oURLParameters) {
            let oStartupParameters;
            let sSapIntentParam;
            let sStartupParametersWithoutSapIntentParam;

            function buildFinalParamsString (sSimpleParams, sIntentParams) {
                let sParams = "";
                if (sSimpleParams && sSimpleParams.length > 0) {
                    sParams = (sSimpleParams.startsWith("?") ? "" : "?") + sSimpleParams;
                }
                if (sIntentParams && sIntentParams.length > 0) {
                    sParams += (sParams.length > 0 ? "&" : "?") + sIntentParams;
                }
                return sParams;
            }

            if (oURLParameters.hasOwnProperty("sap-startup-params")) {
                oStartupParameters = (new URI(`?${oURLParameters["sap-startup-params"]}`)).query(true);
                if (oStartupParameters.hasOwnProperty("sap-intent-param")) {
                    sSapIntentParam = oStartupParameters["sap-intent-param"];
                    delete oStartupParameters["sap-intent-param"];
                }
                sStartupParametersWithoutSapIntentParam = (new URI("?")).query(oStartupParameters).toString();

                // Handle the case when the parameters that were sent to the application were more than 1000 characters and in
                // the URL we see a shorten value of the parameters
                if (sSapIntentParam) {
                    try {
                        const sMoreParams = await AppCommunicationMgr.postMessageToFLP("sap.ushell.services.CrossApplicationNavigation.getAppStateData", { sAppStateKey: sSapIntentParam });
                        return buildFinalParamsString(sStartupParametersWithoutSapIntentParam, sMoreParams);
                    } catch {
                        // fail silently
                    }
                }
                return buildFinalParamsString(sStartupParametersWithoutSapIntentParam);
            }

            return "";
        };

        /**
         * @param {string} sAppId the application id.
         * @param {string} sIframeUrl the document URL used within the iframe.
         * @param {string} sAppIntent the application intent.
         * @returns {Promise<sap.ushell.services.Ui5ComponentLoader.AppProperties>} Resolved the the resolved hash fragment.
         *
         * @private
         */
        this.resolveHashFragment = async function (sAppId, sIframeUrl, sAppIntent) {
            let oParsedHash;
            let oResolvedHashFragment;
            let sEffectiveIframeUrl = sIframeUrl;

            if (sAppIntent) { // SCube
                let sParams = await that.addAppParamsToIntent(sIframeUrl, sAppIntent);
                if (sParams.length > 0) {
                    // remove un-needed parameters from the parameters list before the target resolution
                    sParams = new URI(sParams)
                        .removeSearch("sap-remote-system")
                        .removeSearch("sap-ushell-defaultedParameterNames")
                        .toString();
                }
                const sEffectiveAppIntent = `#${sAppIntent}${sParams}`;
                oParsedHash = UrlParsing.parseShellHash(sEffectiveAppIntent);

                const NavTargetResolutionInternal = await Container.getServiceAsync("NavTargetResolutionInternal");
                const oDeferred = NavTargetResolutionInternal.resolveHashFragmentLocal(sEffectiveAppIntent);
                oResolvedHashFragment = await ushellUtils.promisify(oDeferred);

                if (!oResolvedHashFragment.ui5ComponentName && oResolvedHashFragment.url) {
                    sEffectiveIframeUrl = oResolvedHashFragment.url;

                    // this is a WZ LEP URL which comes from a URL template, but should be embededd inplace as a UI5 app (no double iframes!)
                    const sAppIdFromUrl = UriParameters.fromURL(oResolvedHashFragment.url).get("sap-ui-app-id");
                    oResolvedHashFragment.ui5ComponentName = sAppIdFromUrl;

                    if (!sAppIdFromUrl) {
                        throw new Error("No ui5ComponentName found");
                    }
                    oResolvedHashFragment.applicationDependencies = await this.resolveAppIndexData(
                        sAppIdFromUrl,
                        oResolvedHashFragment.url, // sap.cf's tcprovider is going to read the app id from sap-ui-app-id as well
                        sAppIntent
                    );
                    // the applicationDependencies.url contains the URL of the UI5 Component to be loaded
                    oResolvedHashFragment.url = oResolvedHashFragment.applicationDependencies.url;
                }
            } else { // non S/Cube
                const sHash = UrlParsing.getHash(sEffectiveIframeUrl);
                oParsedHash = UrlParsing.parseShellHash(sHash);

                const oAppIndexData = await this.resolveAppIndexData(
                    sAppId,
                    sIframeUrl
                );
                oResolvedHashFragment = {
                    ui5ComponentName: sAppId,
                    applicationDependencies: oAppIndexData,
                    url: oAppIndexData.url
                };
            }

            const oURLParameters = await this.expandSapIntentParams(new URI(sEffectiveIframeUrl).query(true));
            oResolvedHashFragment.url += await this.getApplicationParameters(oURLParameters);

            const sBasicHash = UrlParsing.constructBasicHash(oParsedHash);
            oResolvedHashFragment.ui5ComponentId = `application-${sBasicHash}-component`;

            return oResolvedHashFragment;
        };

        /**
         * Loads the AppResolution module (as configured via init method) and gets the application info from it.
         * It also caches the application info and returns it from cache if caching is enabled.
         * @param {string} sAppId the application id.
         * @param {string} sIframeUrl the document URL used within the iframe.
         * @param {string} sAppIntent the application intent.
         * @returns {Promise<sap.ushell.appRuntime.ui5.services.AppLifeCycleAgent.AppResolutionAppInfo>} a promise containing the application info.
         * @private
         * @since 1.144.0
         */
        this.resolveAppIndexData = async function (sAppId, sIframeUrl, sAppIntent) {
            let oAppIndexData = that.getAppIndexDataFromCache(sAppId);
            if (!oAppIndexData) {
                if (!oAppResolution) {
                    [oAppResolution] = await ushellUtils.requireAsync([sAppResolutionModule.replace(/\./g, "/")]);
                }
                oAppIndexData = await oAppResolution.getAppInfo(sAppId, sIframeUrl, sAppIntent);
                that.addAppIndexDataToCache(sAppId, oAppIndexData);
            }
            // return a deep copy (as the URL is going to be modified with params by AppRuntime.js)
            return deepClone(oAppIndexData);
        };

        /**
         * @param {string} sAppId the application id.
         * @param {object} oAppIndexData the application info.
         * @param {boolean} [bForceAdd=false] whether to force add the application info to cache even if caching is disabled.
         * @private
         */
        this.addAppIndexDataToCache = function (sAppId, oAppIndexData, bForceAdd) {
            if (bEnableAppResolutionCache !== true && !bForceAdd) {
                return;
            }

            if (
                sAppId
                && oAppIndexData
                && !isEmptyObject(oAppIndexData)
                && oAppResolutionCache[sAppId] === undefined
            ) {
                oAppResolutionCache[sAppId] = deepClone(oAppIndexData);
            }
        };

        /**
         * @param {string} sAppId the application id.
         * @returns {object} the application info from cache or undefined.
         * @private
         * @since 1.144.0
         */
        this.getAppIndexDataFromCache = function (sAppId) {
            if (
                sAppId
                && oAppResolutionCache[sAppId] !== undefined
            ) {
                return deepClone(oAppResolutionCache[sAppId]);
            }
        };

        /**
         * @param {string} oApp the application.
         * @param {string} sAppIntent the application intent.
         * @param {string} oResolvedHashFragment the resolved hash fragment.
         * @private
         */
        this.setCurrentApp = function (oApp, sAppIntent, oResolvedHashFragment) {
            this._destroyApp(oRunningApp.oApp);
            this.resetCurrentApp();

            oRunningApp.oApp = oApp;
            oRunningApp.sAppIntent = sAppIntent;
            oRunningApp.resolvedHashFragment = oResolvedHashFragment;

            // Initializing the disableKeepAliveRestoreRouterRetrigger flag to true
            if (oRunningApp.oApp) {
                oRouterDisableRetriggerApplications[oRunningApp.oApp.getId()] = true;
            }
        };

        /**
         * Sets the current application attributes provided by {@link sap.ushell.services.AppLifeCycle.CurrentApplication.getInfo}.
         * This is later used to restore the application state in case of a keep-alive.
         * @param {sap.ushell.services.AppLifeCycle.AppInfo} oAppInfo The new application info.
         * @private
         */
        this.setCurrentAppAttributes = function (oAppInfo) {
            oRunningApp.oAppAttributes = oAppInfo;
        };

        /**
         * Returns the currently stored application attributes.
         * @returns {sap.ushell.services.AppLifeCycle.AppInfo|undefined} the application info.
         *
         * @since 1.146.0
         * @private
         */
        this.getCurrentAppAttributes = function () {
            return oRunningApp.oAppAttributes;
        };

        /**
         * @private
         */
        this.resetCurrentApp = function () {
            oRunningApp = {
                oApp: undefined,
                sAppIntent: undefined,
                resolvedHashFragment: undefined,
                oAppAttributes: {}
            };
        };

        this.cloneCurrentAppEntry = function (oEntry) {
            return {
                oApp: oEntry.oApp,
                sAppIntent: oEntry.sAppIntent,
                resolvedHashFragment: oEntry.resolvedHashFragment,
                oAppAttributes: oEntry.oAppAttributes
            };
        };

        /**
         * @param {object} oService the shell ui service.
         * @private
         */
        this.setShellUIService = function (oService) {
            oShellUIService = oService;
        };

        /**
         * @returns {boolean} whether the navigation can continue safely.
         * @private
         */
        this.checkDataLossAndContinue = function () {
            if (DirtyState.getDirtyFlagInternal()) {
                // eslint-disable-next-line no-alert
                if (window.confirm(sDataLossMessage)) {
                    DirtyState.setDirtyFlag(false).catch((oError) => {
                        Log.error(
                            "Error while resetting the dirty flag in AppLifeCycleAgent after user confirmed data loss.",
                            oError,
                            "sap.ushell.appRuntime.ui5.services.AppLifeCycleAgent::checkDataLossAndContinue"
                        );
                    });
                    return true;
                }
                return false;
            }
            return true;
        };

        /**
         * Use legacy restore flow for current application.
         *
         * @param {string} sComponentId The component id of the application
         * @param {boolean} bDisableRouterRetrigger
         *     A flag to disable or enable the router's re-trigger
         *     when a keep-alive application is restored
         *
         * @since 1.141.0
         * @private
         */
        this.useLegacyRestoreFlowForCurrentApplication = function (sComponentId, bDisableRouterRetrigger) {
            if (sComponentId && oRouterDisableRetriggerApplications[sComponentId]) {
                oRouterDisableRetriggerApplications[sComponentId] = bDisableRouterRetrigger;
            }
        };

        /**
         * Gets the current running app information.
         * This method is primarily intended for testing purposes.
         *
         * @returns {object} The current running app object containing oApp, sAppIntent, resolvedHashFragment, and oAppAttributes
         * @since 1.144.0
         * @private
         */
        this.getCurrentApp = function () {
            return oRunningApp;
        };

        /**
         * Destroys the given application instance safely.
         *
         * @param {object} oApp the application to be destroyed.
         * @since 1.144.0
         * @private
         */
        this._destroyApp = function (oApp) {
            if (!oApp) {
                return;
            }
            const sAppId = oApp.getId() || "<unknown>";
            try {
                oApp.destroy();
            } catch (oError) {
                Log.error(
                    // eslint-disable-next-line max-len
                    `exception when trying to close sapui5 application with id '${sAppId}' when running inside the appruntime iframe '${document.URL}'. This error must be fixed in order for the iframe to operate properly.\n`,
                    oError,
                    "sap.ushell.appRuntime.ui5.services.AppLifeCycleAgent::destroy"
                );
            }
        };

        this.reset = function () {
            this.resetCurrentApp();
            Object.keys(oCachedApplications).forEach((sKey) => {
                that._destroyApp(oCachedApplications[sKey].oApp);
                delete oCachedApplications[sKey];
            });
        };
    }

    return new AppLifeCycleAgent();
}, true);
