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

/**
 * @fileOverview The Application Container for UI5 applications.
 */
sap.ui.define([
    "sap/base/Log",
    "sap/ui/core/ComponentContainer",
    "sap/ui/core/EventBus",
    "sap/ushell/appIntegration/ApplicationContainer",
    "sap/ushell/appIntegration/UI5ApplicationContainerRenderer",
    "sap/ushell/Container",
    "sap/ushell/library", // css style dependency
    "sap/ushell/utils"
], (
    Log,
    ComponentContainer,
    EventBus,
    ApplicationContainer,
    UI5ApplicationContainerRenderer,
    Container,
    ushellLibrary,
    ushellUtils
) => {
    "use strict";

    /**
     * @alias sap.ushell.appIntegration.UI5ApplicationContainer
     * @class
     *
     * @extends sap.ushell.appIntegration.ApplicationContainer
     *
     * @since 1.134.0
     * @private
     */ // eslint-disable-next-line max-len
    const UI5ApplicationContainer = ApplicationContainer.extend("sap.ushell.appIntegration.UI5ApplicationContainer", /** @lends sap.ushell.appIntegration.IframeApplicationContainer.prototype */ {
        metadata: {
            library: "sap.ushell",
            properties: {
                /**
                 * The component handle - for SAPUI5 components, this contains a handle to the
                 * component metadata and constructor which might already be loaded
                 */
                componentHandle: { type: "object" },

                ui5ComponentId: { type: "string" },

                ui5ComponentName: { type: "string" }
            },
            aggregations: {
                child: { type: "sap.ui.core.Control", multiple: false, visibility: "hidden" }
            }
        },
        renderer: UI5ApplicationContainerRenderer
    });

    UI5ApplicationContainer.prototype.init = function () {
        ApplicationContainer.prototype.init.apply(this, arguments);
    };

    UI5ApplicationContainer.prototype.onBeforeRendering = function () {
        if (!this.getReadyForRendering()) {
            return;
        }

        this._createComponentContainer();
    };

    UI5ApplicationContainer.prototype._createComponentContainer = function () {
        if (this.getAggregation("child")) {
            // do not create the component container if it already exists
            return;
        }

        const oComponent = this.getComponentHandle().getInstance();

        const oComponentContainer = new ComponentContainer({
            id: this.getUi5ComponentId().replace("-component", "-content"),
            component: oComponent
        });

        oComponentContainer.setHeight(this.getHeight());
        oComponentContainer.setWidth(this.getWidth());
        oComponentContainer.addStyleClass("sapUShellApplicationContainer");

        this.setAggregation("child", oComponentContainer);

        return this._publishAppComponentLoaded(oComponent);
    };

    UI5ApplicationContainer.prototype.setReadyForRendering = function (bReadyForRendering) {
        ApplicationContainer.prototype.setReadyForRendering.apply(this, arguments);

        if (!this.getRenderComplete()) {
            /*
             * This is a permanent workaround for poorly written apps.
             *
             * Some apps rely on a early rendering in their component and
             * routing logic. However, the UI5 lifecycle only guarantees
             * access to the DOM in the onAfterRendering phase.
             *
             * With I2f977331657a6b897c1214f74d8e01514e343a01 we separated
             * the component creation from its rendering by some additional ms.
             * This caused issues especially with the route matching handling of
             * apps. The route matching already starts in the init phase of the
             * component. Before the change, the creation and rendering was
             * already separated, but the rendering happened faster. Therefore,
             * access to DOM succeeded (by accident) within the route matched
             * handlers.
             *
             * As a workaround, we enforce a sync rendering here.
             */
            this.rerender();
        }

        return this;
    };

    /**
     * Fires the EventBus event ["sap.ushell", "appComponentLoaded"]
     * This is SAP internally used e.g. for custom focus handling.
     *
     * @param {sap.ui.core.UIComponent} oComponent The newly created component.
     *
     * @private
     */
    UI5ApplicationContainer.prototype._publishAppComponentLoaded = async function (oComponent) {
        const PluginManager = await Container.getServiceAsync("PluginManager");
        await ushellUtils.promisify(PluginManager.getPluginLoadingPromise("RendererExtensions"));

        await ushellUtils.awaitTimeout(0);

        EventBus.getInstance().publish("sap.ushell", "appComponentLoaded", { component: oComponent });
    };

    /**
     * Invoke <code>getRouter.stop()<code> on the oComponentAn event handler for the onNewAppInstantiated event
     * This method might be called before the component is created.
     *
     * @private
     */
    UI5ApplicationContainer.prototype.stopRouter = function () {
        const oComponent = this.getComponentHandle()?.getInstance();
        if (!oComponent || !oComponent.isA("sap.ui.core.UIComponent")) {
            return;
        }

        const oRouter = oComponent.getRouter();
        if (!oRouter) {
            return;
        }

        Log.info(`router stopped for instance ${oComponent.getId()}`);
        oRouter.stop();
    };

    /**
     * Destroys the child aggregation.
     */
    UI5ApplicationContainer.prototype._destroyChild = function () {
        const oChild = this.getAggregation("child");
        if (!oChild) {
            return;
        }

        const sComponentName = oChild.getComponentInstance().getMetadata().getComponentName();
        Log.debug(`unloading component ${sComponentName}`, null, "sap.ushell.appIntegration.ApplicationContainer");
        this.destroyAggregation("child");
    };

    UI5ApplicationContainer.prototype.exit = function () {
        this._destroyChild();

        // before initial render the child aggregation is not set
        const oComponentHandle = this.getComponentHandle();
        if (oComponentHandle) {
            oComponentHandle.destroy();
        }

        ApplicationContainer.prototype.exit.apply(this, arguments);
    };

    return UI5ApplicationContainer;
});
