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

/**
 * @fileoverview This file contains the EmbeddedUI5Handler class.
 * It handles the lifecycle of embedded UI5 applications.
 */
sap.ui.define([
    "sap/base/Log",
    "sap/ui/core/EventBus",
    "sap/ushell/appIntegration/ApplicationContainerCache",
    "sap/ushell/Container",
    "sap/ushell/resources",
    "sap/ushell/UI5ComponentType",
    "sap/ushell/utils",
    "sap/ushell/utils/LaunchpadError",
    "sap/ushell/utils/UrlParsing"
], (
    Log,
    EventBus,
    ApplicationContainerCache,
    Container,
    ushellResources,
    UI5ComponentType,
    ushellUtils,
    LaunchpadError,
    UrlParsing
) => {
    "use strict";

    /**
     * @alias sap.ushell.appIntegration.contracts.EmbeddedUI5Handler
     * @class
     * @classdesc Handles the lifecycle management of embedded UI5 applications,
     * including creation, storage, and restoration.
     * Can only be used for {@link sap.ushell.appIntegration.UI5ApplicationContainer}.
     *
     * @private
     */
    class EmbeddedUI5Handler {
        /**
         * Creates an embedded UI5 application.
         * @param {sap.ushell.appIntegration.UI5ApplicationContainer} oApplicationContainer The container for the application.
         * @param {object} oResolvedHashFragment The resolved hash fragment.
         * @param {sap.ushell.services.URLParsing.DecomposedHash} oParsedHash The parsed shell hash.
         * @returns {Promise} Resolves when the application is created.
         *
         * @private
         */
        async createApp (oApplicationContainer, oResolvedHashFragment, oParsedHash) {
            // We need to stop every application router to avoid inner-app hash change clashes
            // before creating a new component.
            ApplicationContainerCache.forEach((oCachedApplicationContainer) => {
                if (oCachedApplicationContainer === oApplicationContainer) {
                    return;
                }

                if (oCachedApplicationContainer.isA("sap.ushell.appIntegration.UI5ApplicationContainer")) {
                    oCachedApplicationContainer.stopRouter();
                }
            });

            // load ui5 component via service; core-ext-light will be loaded as part of the asyncHints
            const Ui5ComponentLoader = await Container.getServiceAsync("Ui5ComponentLoader");

            try {
                await Ui5ComponentLoader.createComponent(
                    oResolvedHashFragment,
                    [],
                    UI5ComponentType.Application
                );
            } catch (oError) {
                const sShellHash = UrlParsing.constructShellHash(oParsedHash, true);

                const oUpdatedError = new LaunchpadError(
                    oError.message,
                    {
                        translatedMessage: ushellResources.i18n.getText("failed_to_open_ui5_component"),
                        messageDetails: ushellResources.i18n.getText("cannot_load_ui5_component_details", [sShellHash])
                    },
                    oError
                );

                throw oUpdatedError;
            }
        }

        /**
         * Gets the navigation redirect hash if the component is a trampoline.
         * @param {sap.ushell.appIntegration.UI5ApplicationContainer} oApplicationContainer The application container.
         * @returns {Promise<string|undefined>} The redirect hash or undefined.
         *
         * @private
         */
        async getNavigationRedirectHash (oApplicationContainer) {
            const oComponentHandle = oApplicationContainer.getComponentHandle();
            if (!oComponentHandle) {
                return;
            }

            const oComponent = oComponentHandle.getInstance({});
            if (!oComponent) {
                return;
            }

            if (typeof oComponent.navigationRedirect !== "function") {
                return;
            }

            // oComponent refers to a trampoline application
            const oNavRedirectThenable = oComponent.navigationRedirect();
            if (!oNavRedirectThenable || typeof oNavRedirectThenable.then !== "function") {
                return;
            }

            try {
                const sHash = await ushellUtils.promisify(oNavRedirectThenable);
                return sHash;
            } catch {
                // fail silently
            }
        }

        /**
         * Stores the application state when hiding it.
         * @param {sap.ushell.appIntegration.UI5ApplicationContainer} oApplicationContainer The application container.
         * @returns {Promise} Resolves when the application is stored.
         *
         * @private
         */
        async storeApp (oApplicationContainer) {
            const oResolvedHashFragment = oApplicationContainer.getCurrentAppTargetResolution();
            EventBus.getInstance().publish("sap.ushell", "appKeepAliveDeactivate", oResolvedHashFragment);

            const oComponentHandle = oApplicationContainer.getComponentHandle();
            const oComponent = oComponentHandle.getInstance();

            // check first whether this app requires activate/deactivate
            if (oComponent.isKeepAliveSupported?.()) {
                oComponent.deactivate();
                return;
            }

            // fallback to suspend/restore
            if (typeof oComponent.suspend === "function") {
                oComponent.suspend();
            }

            const oRouter = oComponent.getRouter?.();
            if (oRouter && typeof oRouter.stop === "function") {
                oRouter.stop();
            }
        }

        /**
         * Restores the application after navigation.
         * @param {sap.ushell.appIntegration.UI5ApplicationContainer} oApplicationContainer The application container.
         * @param {object} oStorageEntry The storage entry with app state.
         * @returns {Promise} Resolves when the application is restored.
         *
         * @private
         */
        async restoreAppAfterNavigate (oApplicationContainer, oStorageEntry) {
            const oResolvedHashFragment = oApplicationContainer.getCurrentAppTargetResolution();
            EventBus.getInstance().publish("sap.ushell", "appKeepAliveActivate", oResolvedHashFragment);

            const oComponentHandle = oApplicationContainer.getComponentHandle();
            const oComponent = oComponentHandle.getInstance();

            // check first whether this app requires activate/deactivate (more modern way)
            if (oComponent.isKeepAliveSupported?.()) {
                oComponent.activate();
                return;
            }

            // fallback to suspend/restore (old way)
            if (typeof oComponent.restore === "function") {
                oComponent.restore();
            }

            const oRouter = oComponent.getRouter?.();
            if (oRouter && typeof oRouter.initialize === "function") {
                // default is false, which just continues the router assuming the current hash did not change
                // can be overridden by FEv2 see AppLifeCycle.getCurrentApplication().disableKeepAliveAppRouterRetrigger()
                // this fully restarts the router and re-triggers the matching for the current hash
                const bIgnoreInitialHash = !oStorageEntry.useLegacyRestoreFlow;
                oRouter.initialize(bIgnoreInitialHash);
            }
        }

        /**
         * Additional hook to initialize the app metadata.
         * @param {sap.ushell.appIntegration.UI5ApplicationContainer} oApplicationContainer the application container.
         *
         * @since 1.145.0
         * @private
         */
        initializeMetadata (oApplicationContainer) {
            const oComponentHandle = oApplicationContainer.getComponentHandle();

            // Note 2717290 / BCP 1880601193
            // Custom lifecycle event to have a stable hook to dynamically set fullWidth/ letterBox AFTER AppLifeCycle applied default behavior
            const oComponentInstance = oComponentHandle.getInstance();
            if (typeof oComponentInstance.active === "function") {
                oComponentInstance.active();
            }
        }

        /**
         * Checks whether there are id conflicts between the target application described by
         * @param {object} oResolvedHashFragment the resolved hash fragment of the target application
         * @param {sap.ushell.appIntegration.UI5ApplicationContainer} oOldApplicationContainer the old application container to check against
         * @param {sap.ushell.utils.StagedLogger.Stage} oLogger the logger to use
         * @returns {boolean} whether there are id conflicts
         *
         * @since 1.145.0
         * @private
         */
        hasIdConflicts (oResolvedHashFragment, oOldApplicationContainer, oLogger) {
            if (!oOldApplicationContainer || !oOldApplicationContainer.isA("sap.ushell.appIntegration.UI5ApplicationContainer")) {
                return false;
            }

            const sTargetUi5ComponentInstanceId = oResolvedHashFragment.ui5ComponentId;
            const sTargetTechnicalComponentId = oResolvedHashFragment.ui5ComponentName;
            const sTargetComponentName = oResolvedHashFragment.applicationDependencies?.name;

            const sOldUi5ComponentInstanceId = oOldApplicationContainer.getUi5ComponentId();
            const oOldResolvedHashFragment = oOldApplicationContainer.getCurrentAppTargetResolution();
            const sOldTechnicalComponentId = oOldResolvedHashFragment.ui5ComponentName;
            const sOldComponentName = oOldResolvedHashFragment.applicationDependencies?.name;

            if (sTargetUi5ComponentInstanceId === sOldUi5ComponentInstanceId) {
                oLogger.log(`Same component id: destroying application. (${oOldApplicationContainer.getCurrentAppId()})`, "→");
                return true;
            }

            if (sTargetTechnicalComponentId && sTargetTechnicalComponentId === sOldTechnicalComponentId) {
                oLogger.log(`Same technical component id: destroying application. (${oOldApplicationContainer.getCurrentAppId()})`, "→");
                return true;
            }

            if (sTargetComponentName && sTargetComponentName === sOldComponentName) {
                oLogger.log(`Same component name: destroying application. (${oOldApplicationContainer.getCurrentAppId()})`, "→");
                return true;
            }

            return false;
        }
    }

    return new EmbeddedUI5Handler();
});
