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

sap.ui.define([
    "sap/base/util/deepEqual",
    "sap/ui/base/Object",
    "sap/ushell/utils"
], (
    deepEqual,
    BaseObject,
    ushellUtils
) => {
    "use strict";

    const USER_API_PREFIX = "user.postapi.";

    /**
     * @typedef {string} sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceName
     * The name of the service.
     * All service names must start with 'user.postapi.'.
     * Only SAP internal APIs are exempt from this rule.
     *
     * @since 1.140.0
     * @private
     * @ui5-restricted sap.ui.rta
     */

    /**
     * @typedef {string} sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceInterface
     * The interface of the service.
     * The interface is used to identify the specific functionality within the service.
     *
     * @since 1.140.0
     * @private
     * @ui5-restricted sap.ui.rta
     */

    /**
     * @typedef {object} sap.ushell.services.PluginManager.PostMessagePluginInterface.OutCallDefinition
     * Defines the outgoing call interface for post message usage in the {@link sap.ushell.services.PluginManager.PostMessagePluginInterface.CommunicationObject}.
     * Needs to be defined otherwise those post messages might not be sent to applications.
     * @property {boolean} [isActiveOnly=true]
     * If true, the post message is sent to the current application. Any other inactive applications are ignored then.
     * @property {string[]} [distributionType=["all"]]
     * The distribution type of the post message.
     * The value "all" is transformed to <code>ignoreCapabilities=true</code>.
     * Any other value than "all" will cause errors.
     *
     * @since 1.140.0
     * @private
     * @ui5-restricted sap.ui.rta
     */

    /**
     * @typedef {object} sap.ushell.services.PluginManager.PostMessagePluginInterface.InCallDefinition
     * Defines the incoming call interface for post message usage in the {@link sap.ushell.services.PluginManager.PostMessagePluginInterface.CommunicationObject}.
     * This is the interface that defines the handler in case applications are sending post messages to the plugin.
     * @property {function (): Promise<any>} [executeServiceCallFn]
     * The function to execute the service call.
     *
     * @since 1.140.0
     * @private
     * @ui5-restricted sap.ui.rta
     */

    /**
     * @typedef {object} sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceDefinition
     * Defines the service definition for post message usage in the {@link sap.ushell.services.PluginManager.PostMessagePluginInterface.CommunicationObject}.
     *
     * @property {Object<sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceInterface, sap.ushell.services.PluginManager.PostMessagePluginInterface.InCallDefinition>} [inCalls]
     * Contains the interfaces for incoming calls.
     * @property {Object<sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceInterface, sap.ushell.services.PluginManager.PostMessagePluginInterface.OutCallDefinition>} [outCalls]
     * Contains the interfaces for outgoing calls.
     *
     * @since 1.140.0
     * @private
     * @ui5-restricted sap.ui.rta
     */

    /* eslint-disable max-len */
    /**
     * @typedef {Object<sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceName, sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceDefinition>} sap.ushell.services.PluginManager.PostMessagePluginInterface.CommunicationObject
     * Defines the communication object for post message APIs.
     * It is a mapping of {@link sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceName} to {@link sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceDefinition}.
     *
     * @example
     * {
     *   "user.postapi.myService": { // ServiceName
     *     inCalls: {
     *       "myInterface": { // ServiceInterface
     *         // InCallDefinition
     *         executeServiceCallFn: async function () {
     *           // Implementation of the service call
     *         }
     *       }
     *     },
     *     outCalls: {
     *       "myInterface": { // ServiceInterface
     *         // OutCallDefinition
     *         isActiveOnly: true,
     *         distributionType: ["all"] // obsolete, should not be set
     *       }
     *     }
     *   }
     * }
     *
     * @see sap.ushell.services.PluginManager.PostMessagePluginInterface.OutCallDefinition
     * @see sap.ushell.services.PluginManager.PostMessagePluginInterface.InCallDefinition
     *
     * @since 1.140.0
     * @private
     * @ui5-restricted sap.ui.rta
     */
    /* eslint-enable max-len */

    /**
     * @alias sap.ushell.services.PluginManager.PostMessagePluginInterface
     * @namespace
     * @description This namespace provides an interface independent of the runtime environment
     * for post message communication. However, it is only meant to be used in the context
     * of "application runtimes" (FLP, appruntime).
     *
     * It is not meant to be used in standalone iframes.
     *
     * @hideconstructor
     *
     * @private
     * @ui5-restricted sap.ui.rta
     */
    const PostMessagePluginInterface = BaseObject.extend("sap.ushell.services.PluginManager.PostMessagePluginInterface", {
        metadata: {
            publicMethods: [
                "registerPostMessageAPIs",
                "createPostMessageResult",
                "postMessageToApp", // only available in the outer shell
                "postMessageToFlp" // only available in the inner shell
            ]
        }
    });

    /**
     * Initializes the PostMessagePluginInterface.
     * @param {function (): Promise<any>} fnSendPostMessage The function to send post messages.
     * @param {function (string, function)} fnSetRequestHandler The function to add communication objects.
     * @param {function} fnSetDistributionPolicy The function to set distribution policies.
     * @param {boolean} bRunsInOuterShell Indicates if the interface runs in the outer shell.
     *
     * @private
     */
    PostMessagePluginInterface.prototype.init = function (fnSendPostMessage, fnSetRequestHandler, fnSetDistributionPolicy, bRunsInOuterShell) {
        this._fnSendPostMessage = fnSendPostMessage;
        this._fnSetRequestHandler = fnSetRequestHandler;
        this._fnSetDistributionPolicy = fnSetDistributionPolicy;
        this._bRunsInOuterShell = bRunsInOuterShell;
    };

    /**
     * Overrides the getInterface method to return the interface object.
     *
     * Depending on the runtime environment, it either provides the postMessageToFlp or postMessageToApp method.
     * @returns {object} The interface object containing the methods for post message communication.
     *
     * @private
     */
    PostMessagePluginInterface.prototype.getInterface = function () {
        const oInterface = BaseObject.prototype.getInterface.call(this);

        if (this._bRunsInOuterShell) {
            delete oInterface.postMessageToFlp;
        } else {
            delete oInterface.postMessageToApp;
        }

        return oInterface;
    };

    /**
     * Registers new post message APIs.
     *
     * @param {sap.ushell.services.PluginManager.PostMessagePluginInterface.CommunicationObject} oNewCommunicationObject The new communication object containing the APIs to register.
     * @param {boolean} [bSAPInternal=false] If true, the APIs are considered as internal SAP APIs and do not require the prefix 'user.postapi.'.
     * @returns {{ status: string, desc: string }} The result of the registration. <code>status</code> can be "success" or "error", and <code>desc</code> provides a description of the result.
     *
     * @private
     * @ui5-restricted sap.ui.rta
     */
    PostMessagePluginInterface.prototype.registerPostMessageAPIs = function (oNewCommunicationObject, bSAPInternal = false) {
        const oResult = {
            status: "success",
            desc: ""
        };

        try {
            this._validateCommunicationObject(oNewCommunicationObject, bSAPInternal);

            this._addCommunicationObject(oNewCommunicationObject);
        } catch (oError) {
            oResult.status = "error";
            oResult.desc = oError.message;
        }

        return oResult;
    };

    /**
     * Validates the communication object to ensure it contains the required structure and keys.
     * Throws an error if the validation fails.
     * @param {sap.ushell.services.PluginManager.PostMessagePluginInterface.CommunicationObject} oCommunicationObject The communication object to validate.
     * @param {boolean} bSAPInternal Indicates if the APIs are internal SAP APIs.
     *
     * @private
     */
    PostMessagePluginInterface.prototype._validateCommunicationObject = function (oCommunicationObject, bSAPInternal) {
        /* eslint-disable padded-blocks */
        if (oCommunicationObject === undefined || Object.keys(oCommunicationObject).length <= 0) {
            throw new Error("No handler was found to register");
        }

        for (const [sService, oServiceDefinition] of Object.entries(oCommunicationObject)) {
            if (typeof sService !== "string") {
                throw new Error("oPostMessageAPIs should contain only string keys");
            }

            if (!bSAPInternal && !sService.startsWith(USER_API_PREFIX)) {
                throw new Error(`all user custom Message APIs must start with '${USER_API_PREFIX}'`);
            }

            for (const [sType, oTypeDefinition] of Object.entries(oServiceDefinition)) {
                if (!["inCalls", "outCalls"].includes(sType)) {
                    throw new Error("api should contain either 'inCalls' or 'outCalls'");
                }

                for (const [sInterface, oInterfaceDefinition] of Object.entries(oTypeDefinition)) {
                    if (sType === "inCalls") {
                        // executeServiceCallFn is required for inCalls
                        if (typeof oInterfaceDefinition.executeServiceCallFn !== "function") {
                            throw new Error(`executeServiceCallFn for '${sService}.${sInterface}' must be a function`);
                        }

                    } else if (sType === "outCalls") {
                        // distributionType should not be used at all
                        if (Object.hasOwn(oInterfaceDefinition, "distributionType")) {
                            if (!Array.isArray(oInterfaceDefinition.distributionType)) {
                                throw new Error(`distributionType for '${sService}.${sInterface}' must be an array. Remove it from the definition to apply the default.`);
                            }
                            if (!deepEqual(oInterfaceDefinition.distributionType, ["all"])) {
                                throw new Error(`distributionType for '${sService}.${sInterface}' must be 'all'. Remove it from the definition to apply the default.`);
                            }
                        }
                    }
                }
            }
        }
        /* eslint-enable padded-blocks */
    };

    /**
     * Adds a communication object to the plugin interface.
     * @param {sap.ushell.services.PluginManager.PostMessagePluginInterface.CommunicationObject} oCommunicationObject The communication object to add.
     *
     * @private
     */
    PostMessagePluginInterface.prototype._addCommunicationObject = function (oCommunicationObject) {
        /* eslint-disable padded-blocks */
        for (const [sService, oServiceDefinition] of Object.entries(oCommunicationObject)) {
            for (const [sType, oTypeDefinition] of Object.entries(oServiceDefinition)) {
                for (const [sInterface, oInterfaceDefinition] of Object.entries(oTypeDefinition)) {
                    if (sType === "inCalls") {
                        this._fnSetRequestHandler(
                            `${sService}.${sInterface}`,
                            async (oMessageBody, oMessageEvent) => {
                                const oServiceParams = {
                                    oMessageData: JSON.parse(oMessageEvent.data),
                                    oMessage: oMessageEvent
                                };

                                return ushellUtils.promisify(oInterfaceDefinition.executeServiceCallFn(oServiceParams));
                            }
                        );

                    } else if (sType === "outCalls") {
                        // Defaults
                        const oDistributionPolicy = {
                            ignoreCapabilities: true,
                            onlyCurrentApplication: true
                        };

                        if (Object.hasOwn(oInterfaceDefinition, "isActiveOnly")) {
                            oDistributionPolicy.onlyCurrentApplication = !!oInterfaceDefinition.isActiveOnly;
                        }

                        this._fnSetDistributionPolicy(
                            `${sService}.${sInterface}`,
                            oDistributionPolicy
                        );
                    }
                }
            }
        }
        /* eslint-enable padded-blocks */
    };

    /**
     * Posts a message to the application.
     * Only available in the outer shell.
     * @param {sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceName} sServiceName The name of the service.
     * @param {sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceInterface} sInterface The interface to call.
     * @param {any} oBody The parameters to pass to the interface.
     * @returns {jQuery.Promise} A promise that resolves with the result of the post message.
     *
     * @private
     * @ui5-restricted sap.ui.rta
     */
    PostMessagePluginInterface.prototype.postMessageToApp = function (sServiceName, sInterface, oBody) {
        const pResult = this._fnSendPostMessage(`${sServiceName}.${sInterface}`, oBody);

        return ushellUtils.toDeferred(pResult);
    };

    /**
     * Posts a message to the FLP.
     * Only available in the inner shell.
     * @param {sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceName} sServiceName The name of the service.
     * @param {sap.ushell.services.PluginManager.PostMessagePluginInterface.ServiceInterface} sInterface The interface to call.
     * @param {any} oBody The parameters to pass to the interface.
     * @returns {jQuery.Promise} A promise that resolves with the result of the post message.
     *
     * @private
     * @ui5-restricted sap.ui.rta
     */
    PostMessagePluginInterface.prototype.postMessageToFlp = function (sServiceName, sInterface, oBody) {
        const pResult = this._fnSendPostMessage(`${sServiceName}.${sInterface}`, oBody);

        return ushellUtils.toDeferred(pResult);
    };

    /**
     * Sanitizes the result for the post message response.
     * @param {any} [oResult] The result object.
     * @returns {jQuery.Promise} A promise that resolves with the result object.
     *
     * @private
     * @ui5-restricted sap.ui.rta
     */
    PostMessagePluginInterface.prototype.createPostMessageResult = function (oResult = {}) {
        return ushellUtils.toDeferred(oResult);
    };

    return new PostMessagePluginInterface();
});
