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

/**
 * @fileOverview The Unified Shell's AppLifeCycle service enables plug-ins to enquire the which
 *    application is currently displayed and listen to life cycle events.
 *
 * @version 1.148.1
 */
sap.ui.define([
    "sap/ui/base/EventProvider",
    "sap/ui/core/Component",
    "sap/ui/thirdparty/hasher",
    "sap/ushell/AppInfoParameters",
    "sap/ushell/Config",
    "sap/ushell/Container",
    "sap/ushell/EventHub",
    "sap/ushell/services/AppLifeCycle/ApplicationType",
    "sap/ushell/services/AppLifeCycle/AppInfoParameterName",
    "sap/ushell/services/AppLifeCycle/CurrentApplication",
    "sap/ushell/renderer/RendererManagedComponents",
    "sap/ushell/utils"
], (
    EventProvider,
    Component,
    hasher,
    AppInfoParameters,
    Config,
    Container,
    EventHub,
    ApplicationType,
    AppInfoParameterName,
    CurrentApplication,
    RendererManagedComponents,
    ushellUtils
) => {
    "use strict";

    const S_APP_LOADED_EVENT = "appLoaded";

    /**
     * @alias sap.ushell.services.AppLifeCycle
     * @class
     * @classdesc The Unified Shell's AppLifeCycle service.
     *
     * <b>Note:</b> To retrieve a valid instance of this service, it is necessary to call {@link sap.ushell.Container#getServiceAsync}.
     * <pre>
     *   sap.ui.require(["sap/ushell/Container"], async function (Container) {
     *     const AppLifeCycle = await Container.getServiceAsync("AppLifeCycle");
     *     // do something with the AppLifeCycle service
     *   });
     * </pre>
     *
     * @param {object} oAdapter not used
     * @param {object} oContainerInterface not used
     * @param {string} sParameters not used
     * @param {object} oServiceConfiguration not used
     *
     * @augments sap.ushell.services.Service
     * @hideconstructor
     *
     * @since 1.38
     * @public
     */
    function AppLifeCycle (oAdapter, oContainerInterface, sParameters, oServiceConfiguration) {
        this._oCurrentApplication = null;
        this._oCurrentApplicationContainer = null;
        this._oEventProvider = new EventProvider();

        // CONSTRUCTOR CODE //
        if (!Container.inAppRuntime() && !ushellUtils.isRunningInTestEnvironment()) {
            if (Config.last("/core/shell/enableMessageBroker")) {
                Container.getServiceAsync("MessageBroker").then(async (MessageBroker) => {
                    await MessageBroker.connect(MessageBroker.ClientAlias.FLP, () => { });

                    MessageBroker.subscribe(
                        MessageBroker.ClientAlias.FLP,
                        [{
                            channelId: "flp-app-info",
                            version: "1.0"
                        }],
                        this._handleIncomingMessageBrokerMessage.bind(this, MessageBroker)
                    );
                });
            }
        }
    }

    // Expose enums
    AppLifeCycle.prototype.ApplicationType = ApplicationType;
    AppLifeCycle.prototype.AppInfoParameterName = AppInfoParameterName;

    AppLifeCycle.prototype.init = function (oNavContainer) {
        oNavContainer.attachAfterNavigate(this.onAfterNavigate.bind(this));
    };

    AppLifeCycle.prototype._handleIncomingMessageBrokerMessage = function (MessageBroker, sClientId, sChannelId, sMessageName, data) {
        if (sChannelId === "flp-app-info" && sMessageName === "get-current-app-info" && this._oCurrentApplication) {
            this._oCurrentApplication.getAllAppInfo(true).then((appData) => {
                MessageBroker.publish("flp-app-info", MessageBroker.ClientAlias.FLP, Date.now().toString(), sMessageName, [sClientId], appData);
            });
        }
    };

    AppLifeCycle.prototype.onAfterNavigate = async function (oEvent) {
        let bNavToFlpComponent = false;
        const oTo = oEvent.getParameter("to");

        let oComponentInstance;
        if (oTo?.isA("sap.ushell.appIntegration.UI5ApplicationContainer")) {
            const oUI5ApplicationContainer = oTo;

            oComponentInstance = oUI5ApplicationContainer.getComponentHandle().getInstance();
        } else if (oTo?.isA("sap.ui.core.ComponentContainer")) {
            const oComponentContainer = oTo;

            oComponentInstance = Component.getComponentById(oComponentContainer.getComponent());
            if (oComponentInstance?.isA("sap.ushell.renderer.rendererTargetWrapper.Component")) {
                oComponentInstance = await oComponentInstance.getComponentInstance();
            }
        }

        // determine if we're dealing with home page by checking the component instance id
        if (oComponentInstance) {
            // In the past Homepage and AppFinder were the same component.
            // for compatibility reason bHomePage is also true for the AppFinder
            bNavToFlpComponent = RendererManagedComponents.isManagedComponentInstance(oComponentInstance);
        }

        // type can either be read from application container or set to UI5 if component instance exists
        let sApplicationType = ApplicationType.URL;
        let oApplicationContainer;
        if (oTo?.isA("sap.ushell.appIntegration.ApplicationContainer")) {
            oApplicationContainer = oTo;
            sApplicationType = oApplicationContainer.getApplicationType();
        }

        if (sApplicationType === ApplicationType.URL && oComponentInstance || sApplicationType === "SAPUI5") {
            sApplicationType = ApplicationType.UI5;
        }

        this._setCurrentApplicationObject(sApplicationType, oComponentInstance, bNavToFlpComponent, oApplicationContainer);
    };

    AppLifeCycle.prototype._setCurrentApplicationObject = function (sApplicationType, oComponentInstance, bHomePage, oApplicationContainer) {
        this._oCurrentApplicationContainer = oApplicationContainer;

        const oPreviousApplication = this._oCurrentApplication;
        this._oCurrentApplication = new CurrentApplication(
            sApplicationType,
            oComponentInstance,
            bHomePage,
            oApplicationContainer,
            this.useLegacyRestoreFlowForCurrentApplication.bind(this)
        );

        setTimeout(() => {
            /**
             * Destroy previous application only right before firing the appLoaded event.
             * Although, we have now 2 simultaneously valid "current applications".
             * Consumers like the ShellUIService rely on this event to update their internal current application.
             */
            if (oPreviousApplication) {
                oPreviousApplication.destroy?.();
            }
            this._oEventProvider.fireEvent(S_APP_LOADED_EVENT, this._oCurrentApplication);
            // shell analytics is listening to this event
            EventHub.last("trackHashChange");
            // Do not emit FESRAppLoaded event for Shell-home to prevent the close of the statistical record,
            // if it is not an application
            // and thus followed by a PagesRuntimeRendered event to enhance the data and eventually by firstSegmentCompleteLoaded event
            // to close the record.
            this._oCurrentApplication.getInfo(["technicalAppComponentId"]).then((oAppInfo) => {
                if (!RendererManagedComponents.isManagedComponentName(oAppInfo?.technicalAppComponentId)) {
                    EventHub.emit("FESRAppLoaded", { technicalName: oAppInfo.technicalAppComponentId });
                }
            });
            if (!Container.inAppRuntime() && !ushellUtils.isRunningInTestEnvironment()) {
                if (Config.last("/core/shell/enableMessageBroker")) {
                    Container.getServiceAsync("MessageBroker").then((MessageBroker) => {
                        this._oCurrentApplication.getAllAppInfo(true).then((oData) => {
                            MessageBroker.publish("flp-app-info", MessageBroker.ClientAlias.FLP, "new-app-info", "*", oData);
                        });
                    });
                }
            }
        }, 0);
    };

    /**
     * Use legacy restore flow for current application.
     *
     * Service is overwritten in appruntime
     *
     * @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
     */
    AppLifeCycle.prototype.useLegacyRestoreFlowForCurrentApplication = async function (sComponentId, bDisableRouterRetrigger) {
        // require lazily to avoid huge dependencies for appruntime
        const [AppLifeCycleAI] = await ushellUtils.requireAsync(["sap/ushell/appIntegration/AppLifeCycle"]);
        return AppLifeCycleAI.useLegacyRestoreFlowForCurrentApplication();
    };

    /**
     * Returns information about the currently running application or <code>undefined</code> if no application is running.
     *
     * @returns {sap.ushell.services.AppLifeCycle.CurrentApplication|undefined} Information object about currently running application or <code>undefined</code> if no application is running.
     * <b>Note:</b>
     * Return value is only valid after app is loaded. See {@link #attachAppLoaded} for details.
     * Before an app is loaded, <code>undefined</code> is returned.
     * @since 1.38
     * @public
     */
    AppLifeCycle.prototype.getCurrentApplication = function () {
        return this._oCurrentApplication;
    };

    /**
     * Attaches an event handler for the appLoaded event. This event handler will be triggered
     * each time an application has been loaded.
     *
     * @template ObjectToBePassedToHandler {object} Object that will be passed to the handler along with the event object when the event is fired.
     * @param {ObjectToBePassedToHandler} oData
     *     An object that will be passed to the handler along with the event object when the
     *     event is fired.
     * @param {function(sap.ui.base.Event, ObjectToBePassedToHandler)} fnFunction
     *     The handler function to call when the event occurs.
     * @param {object} oListener
     *     The object that wants to be notified when the event occurs (this context within the
     *     handler function).
     * @since 1.38
     * @public
     */
    AppLifeCycle.prototype.attachAppLoaded = function (oData, fnFunction, oListener) {
        this._oEventProvider.attachEvent(S_APP_LOADED_EVENT, oData, fnFunction, oListener);
    };

    /**
     * Detaches an event handler from the EventProvider.
     *
     * @param {function} fnFunction
     *     The handler function that has to be detached from the EventProvider.
     * @param {object} oListener
     *     The object that wanted to be notified when the event occurred
     * @since 1.38
     * @public
     */
    AppLifeCycle.prototype.detachAppLoaded = function (fnFunction, oListener) {
        this._oEventProvider.detachEvent(S_APP_LOADED_EVENT, fnFunction, oListener);
    };

    /**
     * Set current application object from AppRuntime in cFLP
     *
     * @param {string} sApplicationType The type of the current application.
     * @param {sap.ui.core.Component} oComponentInstance The instance of the component.
     * @param {boolean} bHomePage Indicator for a home page.
     * @param {string} oApplicationContainer The application container.
     * @param {string} sFramework The type of the current application running in iframe.
     *
     * @since 1.82
     * @private
     */
    AppLifeCycle.prototype.prepareCurrentAppObject = function (sApplicationType, oComponentInstance, bHomePage, oApplicationContainer, sFramework) {
        this._setCurrentApplicationObject(sApplicationType, oComponentInstance, bHomePage, oApplicationContainer, sFramework);
    };

    /**
     * Reloads the currently displayed app (used by RTA plugin).
     *
     * @since 1.84
     * @private
     * @ui5-restricted sap.ui.rta
     */
    AppLifeCycle.prototype.reloadCurrentApp = function () {
        const oCurrentApp = this.getCurrentApplication();
        EventHub.emit("reloadCurrentApp", {
            sAppContainerId: this._oCurrentApplicationContainer?.getId?.(),
            sAppId: oCurrentApp.componentInstance?.getId?.(),
            sCurrentHash: hasher.getHash(),
            date: Date.now()
        });
    };

    AppLifeCycle.prototype.setAppInfo = function (oAppInfo, bIsNewApp) {
        AppInfoParameters.setCustomAttributes(oAppInfo?.info);
        if (Config.last("/core/shell/enableMessageBroker")) {
            Container.getServiceAsync("MessageBroker").then((MessageBroker) => {
                this._oCurrentApplication.getAllAppInfo(true).then((oData) => {
                    MessageBroker.publish("flp-app-info", MessageBroker.ClientAlias.FLP, (bIsNewApp === true ? "new-app-info" : "app-info-update"), "*", oData);
                });
            });
        }
    };

    AppLifeCycle.hasNoAdapter = true;
    return AppLifeCycle;
}, true/* bExport */);
