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

/**
 * @fileOverview The UI integration's SAPUI5 control which supports application embedding.
 * @version 1.147.1
 */
sap.ui.define([
    "sap/base/Log",
    "sap/ui/core/Control",
    "sap/ushell/appIntegration/ApplicationContainerRenderer",
    "sap/ushell/library", // css style dependency
    "sap/ushell/utils"
], (
    Log,
    Control,
    ApplicationContainerRenderer,
    ushellLibrary,
    ushellUtils
) => {
    "use strict";

    // shortcut for sap.ushell.appIntegration.contracts.StatefulType
    const StatefulType = ushellLibrary.appIntegration.contracts.StatefulType;

    // shortcut for sap.ushell.components.container.ApplicationType
    const ApplicationType = ushellLibrary.components.container.ApplicationType;

    /**
     * @alias sap.ushell.appIntegration.ApplicationContainer
     * Creates a new container control embedding the application with the given URL. The default
     * application type is "URL" and allows to embed web applications into an <code>IFRAME</code>.
     * By default, the container is visible and occupies the whole width and height of its parent.
     *
     * @class
     * @classdesc A container control capable of embedding a variety of application types.
     *
     * <b>Note:</b> The browser does not allow to move an <code>IFRAME</code> around in the DOM
     * while keeping its state. Thus every rerender of the control is blocked.
     *
     * @extends sap.ui.core.Control
     * @since 1.15.0
     */ // eslint-disable-next-line max-len
    const ApplicationContainer = Control.extend("sap.ushell.appIntegration.ApplicationContainer", /** @lends sap.ushell.appIntegration.ApplicationContainer.prototype */ {
        metadata: {
            library: "sap.ushell",
            properties: {
                /**
                 * Whether the container is visible.
                 * @private
                 */
                visible: { type: "boolean", defaultValue: true },
                /**
                 * The container's height as a CSS size.
                 * @private
                 */
                height: { defaultValue: "100%", type: "sap.ui.core.CSSSize" },
                /**
                 * The container's width as a CSS size.
                 * @private
                 */
                width: { defaultValue: "100%", type: "sap.ui.core.CSSSize" },
                /**
                 * Whether the container is active.
                 * Only active containers are allowed to do certain actions.
                 * - ShellUIService interactions
                 * - PostMessage communication with the embedded application
                 * @private
                 */
                active: { defaultValue: true, type: "boolean" },
                /**
                 * This is the id of the first application loaded in the container.
                 * It is used for debugging and logging purposes.
                 * @private
                 */
                initialAppId: { defaultValue: "", type: "string" },
                /**
                 * This is the id of the current application in the container.
                 * It might be empty if no application is loaded or all applications are stored (KeepAlive).
                 * It is used for debugging and logging purposes.
                 * @private
                 */
                currentAppId: { defaultValue: "", type: "string" },
                /**
                 * The URL of the currently loaded application in the container.
                 * It is updated by the AppLifeCycle when a new application is loaded into the container.
                 * It is used for debugging and logging purposes.
                 * This is only relevant for iframe containers.
                 * @private
                 */
                currentAppUrl: { defaultValue: "", type: "string" },
                /**
                 * The hash of the current application.
                 * This is relevant for navigation purposes. It is used to keep track of hash changes which might be triggered when the application is restored.
                 * This is only relevant for iframe containers.
                 * @private
                 */
                currentHash: { defaultValue: "", type: "string" },
                /**
                 * Relevant instance of the ShellUIService scoped to the container.
                 * This is only relevant for iframe containers.
                 * @private
                 */
                shellUIService: { type: "object" },
                /**
                 * The stateful type of the application in the container.
                 * It is used to determine whether the container can be reused for a new application.
                 * @private
                 */
                statefulType: { defaultValue: StatefulType.NotSupported, type: "sap.ushell.appIntegration.contracts.StatefulType" },
                /**
                 * The beforeAppClose event handler registered by the iframe application.
                 * If set, this handler is called by the AppLifeCycle before destroying an iframe to give the application the chance to react
                 * on the close event and delay the destruction if needed.
                 * @private
                 */
                beforeAppCloseEvent: { type: "object" },
                /**
                 * Whether the current application in the container is a keep alive application.
                 * @private
                 */
                isKeepAlive: { defaultValue: false, type: "boolean" },
                /**
                 * Whether the current application in the container is fetched from cache or not.
                 * @private
                 */
                isFetchedFromCache: { defaultValue: false, type: "boolean" },
                /**
                 * Set of capabilities supported by the container.
                 * This is used during postMessage communication to check if the container supports the required interface.
                 * @private
                 */
                capabilities: { defaultValue: {}, type: "object" },

                // ==========================================
                // ========== ResolvedHashFragment ==========

                /**
                 * The ResolvedHashFragment of the current application in the container.
                 * @private
                 */
                currentAppTargetResolution: { type: "object" },
                /**
                 * appFrameworkId from app capabilities.
                 * This is part of the ResolvedHashFragment.
                 * @private
                 */
                frameworkId: { defaultValue: "", type: "string" },
                /**
                 * The type of the embedded application.
                 * @private
                 */
                applicationType: { defaultValue: ApplicationType.URL, type: "sap.ushell.components.container.ApplicationType" },
                /**
                 * Property set in the ResolvedHashFragment which controls whether the container should be opened with post.
                 * @private
                 */
                openWithPostByAppParam: { defaultValue: true, type: "boolean" },
                /**
                 * The list of reserved parameters.
                 * @private
                 */
                reservedParameters: { type: "object" },
                /**
                 * The system alias which is used to determine the system the container is running on.
                 * @private
                 */
                systemAlias: { defaultValue: "", type: "string" },
                /**
                 * The target navigation mode set in the ResolvedHashFragment. It communicates the app how it was opened.
                 * @private
                 */
                targetNavigationMode: { defaultValue: "", type: "string" },
                /**
                 * The URL to the embedded application.
                 * @private
                 */
                url: { defaultValue: "", type: "string" },

                /**
                 * Specifies the data-help-id attribute of the container.
                 *
                 * This attribute is used by SAP Companion/ WalkMe to identify the
                 * application for the purpose of providing help content.
                 *
                 * Additionally, the data-help-id attribute is used by automated
                 * testing tools.
                 *
                 * The id is updated for each application that is loaded in the container.
                 * When the application is stored or destroyed, the id is removed.
                 *
                 * @since 1.136.0
                 * @private
                 */
                dataHelpId: { type: "string", defaultValue: "" },

                /* ==========================================
                 * =============== Rendering ================
                 * General flow:
                 *  1) readyForRendering=false => render placeholder
                 *  2) readyForRendering=true => invalidation
                 *  3) final rendering
                 *  4) set renderComplete=true
                 *  5) rendered() resolves
                 */

                /**
                 * Whether the container is ready for rendering.
                 * Subclasses should prevent any preparation steps and the actual rendering if this
                 * property is set to <code>false</code>. The subclasses should render the container
                 * with the base renderer instead. This property should be set to <code>true</code
                 * by the AppLifeCycle whenever the container state is ready to be applied to the UI.
                 * @private
                 */
                readyForRendering: { defaultValue: false, type: "boolean" },

                /**
                 * Whether the container has been rendered completely.
                 * Subclasses should set this property once the actual rendering is done.
                 * It prevents the container from being invalidated.
                 * @private
                 */
                renderComplete: { defaultValue: false, type: "boolean" }
            },
            interfaces: [
                "sap.ushell.renderer.INavContainerPage"
            ]
        },
        renderer: ApplicationContainerRenderer
    });

    /**
     * Initialization of <code>ApplicationContainer</code> instance.
     *
     * @private
     */
    ApplicationContainer.prototype.init = function () {
        // set object default values again to prevent them from being "synced"
        this.setProperty("capabilities", {}, true);
        this.oRenderedDeferred = Promise.withResolvers();
    };

    /**
     * Overridden invalidate method which prevents invalidation once the container is rendered.
     * @returns {this} This to allow chaining.
     *
     * @private
     */
    ApplicationContainer.prototype.invalidate = function () {
        if (this.getRenderComplete()) {
            // always prevent re-rendering of application container once it's rendered
            return this;
        }

        return Control.prototype.invalidate.apply(this, arguments);
    };

    /**
     * Sets the rendering complete state of the application container.
     * @param {boolean} bComplete The new rendering complete state.
     * @returns {this} <code>this</code> to allow method chaining.
     *
     * @since 1.141.0
     * @private
     */
    ApplicationContainer.prototype.setRenderComplete = function (bComplete) {
        this.setProperty("renderComplete", bComplete);

        if (bComplete) {
            this.oRenderedDeferred.resolve();
        }

        return this;
    };

    /**
     * The ApplicationContainer is rendered multiple times.
     * This helper function awaits the final rendering triggered by
     * {@link sap.ushell.appIntegration.ApplicationContainer#setRenderComplete}.
     * @returns {Promise} Resolves once the ApplicationContainer is rendered
     *
     * @since 1.141.0
     * @private
     */
    ApplicationContainer.prototype.rendered = function () {
        return this.oRenderedDeferred.promise;
    };

    /**
     * Implementation of sap.ushell.renderer.INavContainerPage interface.
     * @param {boolean} bVisible The new visibility state of the control.
     *
     * @since 1.135.0
     * @private
     */
    ApplicationContainer.prototype.setVisibility = function (bVisible) {
        this.setProperty("visible", bVisible, true);

        if (bVisible) {
            this.removeStyleClass("sapUShellApplicationContainerHidden");
        } else {
            this.addStyleClass("sapUShellApplicationContainerHidden");
        }

        // set aria-hidden attribute directly on dom element,
        // as the control is not rerendered
        const oDomRef = this.getDomRef();
        if (oDomRef) {
            oDomRef.setAttribute("aria-hidden", !bVisible);
        }
    };

    /**
     * Sets the current help id.
     * @param {string} sDataHelpId The new help id.
     * @returns {this} <code>this</code> to allow method chaining.
     *
     * @since 1.136.0
     * @private
     */
    ApplicationContainer.prototype.setDataHelpId = function (sDataHelpId) {
        this.setProperty("dataHelpId", sDataHelpId);

        const oDomRef = this.getDomRef();
        if (oDomRef) {
            oDomRef.setAttribute("data-help-id", sDataHelpId);
        }

        return this;
    };

    /**
     * Sets the active state of the application container.
     * @param {string} bActive The new active state.
     * @returns {this} <code>this</code> to allow method chaining.
     *
     * @since 1.135.0
     * @private
     */
    ApplicationContainer.prototype.setActive = function (bActive) {
        this.setProperty("active", bActive);

        const oDomRef = this.getDomRef();
        if (oDomRef) {
            oDomRef.setAttribute("data-sap-ushell-active", bActive);
        }

        return this;
    };

    /**
     * Sets the initial application id.
     * @param {string} sAppId The initial application id.
     * @returns {this} <code>this</code> to allow method chaining.
     *
     * @since 1.135.0
     * @private
     */
    ApplicationContainer.prototype.setInitialAppId = function (sAppId) {
        this.setProperty("initialAppId", sAppId);

        const oDomRef = this.getDomRef();
        if (oDomRef) {
            oDomRef.setAttribute("data-sap-ushell-initial-app-id", sAppId);
        }

        return this;
    };

    /**
     * Sets the current application id.
     * @param {string} sAppId The new application id.
     * @returns {this} <code>this</code> to allow method chaining.
     *
     * @since 1.135.0
     * @private
     */
    ApplicationContainer.prototype.setCurrentAppId = function (sAppId) {
        this.setProperty("currentAppId", sAppId);

        const oDomRef = this.getDomRef();
        if (oDomRef) {
            oDomRef.setAttribute("data-sap-ushell-current-app-id", sAppId);
        }

        return this;
    };

    /**
     * Sets the stateful type of the application container.
     * @param {sap.ushell.appIntegration.contracts.StatefulType} sNewStatefulType The new stateful type.
     * @returns {this} <code>this</code> to allow method chaining.
     *
     * @since 1.135.0
     * @private
     */
    ApplicationContainer.prototype.setStatefulType = function (sNewStatefulType) {
        this.setProperty("statefulType", sNewStatefulType);

        const oDomRef = this.getDomRef();
        if (oDomRef) {
            oDomRef.setAttribute("data-sap-ushell-stateful-type", sNewStatefulType);
        }

        return this;
    };

    /**
     * Sets the framework id of the application.
     * @param {string} sFrameworkId The new framework id.
     * @returns {this} <code>this</code> to allow method chaining.
     *
     * @since 1.135.0
     * @private
     */
    ApplicationContainer.prototype.setFrameworkId = function (sFrameworkId) {
        this.setProperty("frameworkId", sFrameworkId);

        const oDomRef = this.getDomRef();
        if (oDomRef) {
            oDomRef.setAttribute("data-sap-ushell-framework-id", sFrameworkId);
        }

        return this;
    };

    /**
     * Method called by the appLifeCycle to post a "before close" message to the app
     * running in the iframe before the iframe its destroyed.
     * The message is sent only if the iframe registered to be informed with this message.
     * This mechanism was added to solve the change made in Chrome to disallow Sync XHR on
     * browser close.
     *
     * @returns {Promise}
     *  A promise object in case a post message is sent, and "undefined" in case no message needs
     *  to be sent. In case of real promise, it will be resolved when the iframe response back that
     *  it processed the message.
     *
     * @private
     */
    ApplicationContainer.prototype.sendBeforeAppCloseEvent = async function () {
        // can be implemented by sub classes
    };

    /**
     * Determine if the source of a received postMessage can be considered as trusted. We consider
     * the content window of the application container's iframe as trusted
     *
     * @param {object} oMessage
     *   the postMessage event object
     * @returns {boolean}
     *   true if source is considered to be trustworthy
     * @private
     * @since 1.24
     */
    ApplicationContainer.prototype.isTrustedPostMessageSource = function (oMessage) {
        // sub classes can override this method to implement their own logic
        return false;
    };

    /**
     * Returns the post message target for the application in the container.
     * Needs to be implemented by sub classes to enable post message communication with the embedded application.
     *
     * @private
     */
    ApplicationContainer.prototype.getPostMessageTarget = function () {
        // needs to be implemented by sub classes
        throw new Error("getPostMessageTarget is not implemented for this sub class of ApplicationContainer");
    };

    /**
     * Returns the post message target origin for the application in the container.
     * Needs to be implemented by sub classes to enable post message communication with the embedded application.
     *
     * @private
     */
    ApplicationContainer.prototype.getPostMessageTargetOrigin = function () {
        // needs to be implemented by sub classes
        throw new Error("getPostMessageTargetOrigin is not implemented for this sub class of ApplicationContainer");
    };

    /**
     * Sends a post message to the application in the container.
     * Needs to be implemented by sub classes to enable post message communication with the embedded application.
     *
     * @private
     */
    ApplicationContainer.prototype.sendRequest = async function () {
        // needs to be implemented by sub classes
        throw new Error("sendRequest is not implemented for this sub class of ApplicationContainer");
    };

    /**
     * Merges the given capabilities into the existing blue box capabilities.
     * Existing capabilities are overwritten.
     * @param {string[]} aNewCapabilities The additional capabilities.
     * @returns {this} this instance for method chaining
     *
     * @since 1.130.0
     * @private
     */
    ApplicationContainer.prototype.addCapabilities = function (aNewCapabilities) {
        const oCapabilities = this.getCapabilities();

        aNewCapabilities.forEach((sCapability) => {
            oCapabilities[`${sCapability}`.toLowerCase()] = true;
        });
        return this;
    };

    /**
     * Removes the given capabilities from the existing blue box capabilities.
     * If no capabilities are provided, all capabilities are removed.
     * @param {string[]} [aCapabilitiesToRemove] The capabilities to remove. If not provided, all capabilities are removed.
     * @returns {this} this instance for method chaining
     *
     * @since 1.130.0
     * @private
     */
    ApplicationContainer.prototype.removeCapabilities = function (aCapabilitiesToRemove) {
        const oCapabilities = this.getCapabilities();

        if (!aCapabilitiesToRemove) {
            this.setProperty("capabilities", {}, true);
            return this;
        }

        aCapabilitiesToRemove.forEach((sCapability) => {
            delete oCapabilities[`${sCapability}`.toLowerCase()];
        });
        return this;
    };

    /**
     * Checks if this application container supports the given capabilities.
     * @param {string[]} aRequiredCapabilities The capabilities to check.
     * @returns {boolean} true if the blue box capabilities contain the given service and action
     *
     * @since 1.130.0
     * @private
     */
    ApplicationContainer.prototype.supportsCapabilities = function (aRequiredCapabilities) {
        const oCapabilities = this.getCapabilities();

        return aRequiredCapabilities.every((sRequiredCapability) => {
            return oCapabilities[`${sRequiredCapability}`.toLowerCase()];
        });
    };

    /**
     * Checks if the iframe is valid and responsive.
     * @returns {boolean} Whether the iframe is valid.
     * @throws {Error} If the iframe is not valid.
     *
     * @since 1.141.0
     * @private
     */
    ApplicationContainer.prototype.isValid = function () {
        return true;
    };

    return ApplicationContainer;
});
