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

/**
 * @fileOverview
 *
 * Technical parameters do not normally appear in the URL as intent parameters
 * of a target application, unless explicitly entered by the user. They also do
 * not belong to the set of the application startup parameters. Instead, they
 * are accessed by the application on demand via an API call.
 *
 * Of course these parameters can appear in the target URL if the URL has
 * already been formed and entered in the address bar after clicking a link in
 * an e-mail for example. In this case technical parameters do appear in the
 * URL and this behavior should be kept stable. In principle, information should
 * never be removed from a URL once the URL enters the address bar. In this way,
 * the link can be copied or bookmarked for later access.
 *
 * Technical parameters should be used when their values originate from data
 * forwarded to, or generated during, client-side target resolution. Such data
 * could, for example, be included in inbounds.
 *
 * A technical parameter consists of a name and a value. Such a value
 * of a technical parameter is determined at runtime during navigation and
 * injected in the UI5 application component or the application container that
 * hosts the target application in an iframe. UI5 applications can, in practice,
 * access the raw value determined at runtime by
 * <code>this.getComponentData().technicalParameters</code>.
 *
 * However, in this file, we define <strong>a view</strong> over the raw value
 * which might transform the raw parameter value by executing additional logic.
 * Therefore, applications are encouraged to use
 * the <code>AppLifeCycle</code> service in the following way:
 * <pre>
 * sap.ui.require(["sap/ushell/Container"], function(Container) {
 *      Container.getServiceAsync("AppLifeCycle").then(function (oAppLifeCycle) {
 *          oAppLifeCycle.getCurrentApplication().getTechnicalParameter("sap-fiori-id");
 *      });
 * })
 * </pre>
 *
 * It is also possible to prevent applications from using the technical
 * parameters. If the application tries to access an internal parameter (e.g.,
 * sap-ui-app-id-hint) via the API, the application receives an error message.
 *
 * @version 1.148.1
 * @private
 */
sap.ui.define([
], (
) => {
    "use strict";

    /**
     * Helper method to deny access to a technical value
     *
     * @param {string} sParameterName
     *  The name of the technical parameter
     *
     * @returns {Promise<string>}
     *  A promise that always rejects with an error message.
     *
     * @private
     */
    async function denyParameterValue (sParameterName) {
        throw new Error(`${sParameterName} is reserved for shell internal usage only`);
    }

    /**
     * Helper method to obtain a technical value directly from the application component.
     * This is the case if the current type of the application is not "UI5".
     *
     * @param {string} sParameterName
     * the name of the parameter
     * @param {object} oApplicationComponent
     * the component of the given application
     *
     * @returns {*}
     * results for the given parameter name from the application component
     */
    function getValueFromComponent (sParameterName, oApplicationComponent) {
        if (oApplicationComponent) {
            const oComponentData = oApplicationComponent.getComponentData() || {};
            const aTechnicalParameters = oComponentData.technicalParameters || {};
            return aTechnicalParameters[sParameterName];
        }

        return undefined;
    }

    /**
     * Helper method to obtain a technical value from the application component manifest.
     * This is the case if the current type of the application is "UI5".
     *
     * @param {string} sPath
     * the path where to find the parameter
     * @param {object} oApplicationComponent
     * the component of the given application
     *
     * @returns {Array<*>}
     * returns an array of results for the given path from the application component manifest
     */
    function getValueFromManifest (sPath, oApplicationComponent) {
        let vValue = oApplicationComponent.getManifestEntry(sPath);
        if (vValue && !Array.isArray(vValue)) {
            vValue = [vValue];
        }
        return vValue;
    }

    /**
     * Helper method to obtain a technical value from the application container.
     * This is the case if the current type of the application is not "UI5".
     *
     * @param {string} sParameterName
     * the name of the parameter
     * @param {object} oApplicationContainer
     * the container of the given application
     *
     * @returns {*}
     * results for the given parameter name from the application container
     */
    function getValueFromContainer (sParameterName, oApplicationContainer) {
        const aReservedParameters = oApplicationContainer.getReservedParameters();
        return aReservedParameters && aReservedParameters[sParameterName];
    }

    const oTechnicalParameters = {
        // Used by cross application navigation to define the target app.
        "sap-navigation-scope": {
            // Describes where the parameter value should be taken from in ClientSideTargetResolution
            injectFrom: "inboundParameter",
            // Sticky parameters should be propagated in the URL as intent parameters
            sticky: true,
            // Allows the parameter to be propagated under another name
            stickyName: "sap-navigation-scope-filter",
            // Indicates whether the parameter should be considered as intent parameter,
            // when it appears before the inner app route in the URL.
            isIntentParameter: true,
            getValue: function (sParameterName, oApplicationComponent, oApplicationContainer, sApplicationType) {
                return new Promise((fnSuccess, fnFail) => {
                    if (sApplicationType === "UI5") {
                        fnSuccess(getValueFromComponent(sParameterName, oApplicationComponent));
                    } else {
                        fnSuccess(getValueFromContainer(sParameterName, oApplicationContainer));
                    }
                });
            }
        },
        // Used by the about button to inform the user about the id of the
        // current application.
        "sap-fiori-id": {
            injectFrom: "startupParameter",
            isIntentParameter: true,
            getValue: function (sParameterName, oApplicationComponent, oApplicationContainer, sApplicationType) {
                return new Promise((fnSuccess, fnFail) => {
                    if (sApplicationType === "UI5") {
                        fnSuccess(getValueFromComponent(sParameterName, oApplicationComponent) || getValueFromManifest("/sap.fiori/registrationIds", oApplicationComponent));
                    } else {
                        fnSuccess(getValueFromContainer(sParameterName, oApplicationContainer));
                    }
                });
            }
        },
        // Not set or read by RTA
        // We still treat this as a technical parameter to avoid issues with existing usages (e.g. bookmarks) which might
        // leak this parameter into the startupParameters
        // Used by the RT plugin to determine whether the RT change was made by a key user or by a end-user.
        "sap-ui-fl-max-layer": {
            injectFrom: "startupParameter",
            isIntentParameter: true,
            getValue: function (sParameterName, oApplicationComponent) {
                return Promise.resolve().then(getValueFromComponent.bind(null, sParameterName, oApplicationComponent));
            }
        },
        // Used by RTA to determine which control variant id(s) should be
        // selected when the application is loaded.
        "sap-ui-fl-control-variant-id": {
            injectFrom: "startupParameter",
            isIntentParameter: false,
            getValue: function (sParameterName, oApplicationComponent) {
                return Promise.resolve().then(getValueFromComponent.bind(null, sParameterName, oApplicationComponent));
            }
        },
        // Not set or read by RTA
        // We still treat this as a technical parameter to avoid issues with existing usages (e.g. bookmarks) which might
        // leak this parameter into the startupParameters
        // Was used by RTA to determine draft app which user starts to adapt but don't want to activate immediately.
        "sap-ui-fl-version": {
            injectFrom: "startupParameter",
            isIntentParameter: true,
            getValue: function (sParameterName, oApplicationComponent) {
                return Promise.resolve().then(getValueFromComponent.bind(null, sParameterName, oApplicationComponent));
            }
        },
        // Used in CDM 3.0 based FLPs to navigate to specific applications.
        // The parameter should never be passed to the target application, or
        // exposed via public APIs.
        "sap-ui-app-id-hint": {
            injectFrom: "startupParameter",
            isIntentParameter: true,
            getValue: denyParameterValue
        },
        // The application component hierarchy (ACH) will for WDA and SAPGui
        // apps be provided as part of the configuration string of the target
        // mapping.
        // For UI5 apps it is contained in the app descriptor / manifest.
        "sap-ach": {
            injectFrom: "startupParameter",
            isIntentParameter: true,
            getValue: function (sParameterName, oApplicationComponent, oApplicationContainer, sApplicationType) {
                return new Promise((fnSuccess, fnFail) => {
                    if (sApplicationType === "UI5") {
                        fnSuccess(getValueFromComponent(sParameterName, oApplicationComponent) || getValueFromManifest("/sap.app/ach", oApplicationComponent));
                    } else {
                        fnSuccess(getValueFromContainer(sParameterName, oApplicationContainer));
                    }
                });
            }
        },
        // Used by ClientSideTargetResolution to execute a sequence of operations
        // on application startup parameters and selection variants in sap-xapp-state
        // before transferring these parameters to the target application
        "sap-prelaunch-operations": {
            injectFrom: "inboundParameter",
            isIntentParameter: true,
            getValue: denyParameterValue
        },
        // A parameter propagated across programmatic navigations by FLP
        // which indicates the preferred system a target application should
        // be launched into. This parameter ensures the user is kept within the
        // same content provider across navigations if possible - which is
        // desired behavior in multi-content provider scenarios.
        "sap-app-origin-hint": {
            injectFrom: "startupParameter",
            isIntentParameter: false,
            getValue: denyParameterValue
        },
        // The parameter that allows the navigation to a specific content provider
        "sap-app-origin": {
            injectFrom: "startupParameter",
            isIntentParameter: false,
            getValue: denyParameterValue
        }
    };

    /**
     * Method to obtain the value of a technical parameter
     *
     * @param {string} sParameterName
     * the name of the parameter
     * @param {object} oApplicationComponent
     * the component of the given application (only needed for UI5 applications)
     * @param {object} oApplicationContainer
     * the container of the given application (only needed for non UI5 applications)
     * @param {string} sApplicationType
     * the type of the application, in order to decide on how to get the value
     *
     * @returns {Promise}
     * returns a promise that resolve with array of all values, belonging to the
     * given parameter name or rejects with an error message. An array is returned
     * because multiple values might exist for a given technical parameter.
     *
     * @private
     */
    async function getParameterValue (sParameterName, oApplicationComponent, oApplicationContainer, sApplicationType) {
        if (!oTechnicalParameters[sParameterName]) {
            throw new Error(`${sParameterName} is not a known technical parameter`);
        }

        return oTechnicalParameters[sParameterName].getValue(
            sParameterName,
            oApplicationComponent,
            oApplicationContainer,
            sApplicationType
        );
    }
    /**
     * Takes the filter options and returns the matching technical parameter description.
     *
     * @param {object} oFilterOptions The filter options are used to determine the correct technical parameters
     * @returns {object[]} The matching technical parameter description
     */
    function getParameters (oFilterOptions) {
        if (!oFilterOptions) {
            oFilterOptions = {};
        }

        return Object.keys(oTechnicalParameters).filter((oTechnicalParameterName) => {
            const oParameter = oTechnicalParameters[oTechnicalParameterName];
            return Object.keys(oFilterOptions).every((sFilterName) => {
                const vDesiredPropertyValue = oFilterOptions[sFilterName];
                const bHasParameter = oParameter.hasOwnProperty(sFilterName);
                if (bHasParameter) {
                    return oParameter[sFilterName] === vDesiredPropertyValue;
                }
                return false;
            });
        }).map((sMatchingParameter) => {
            return Object.keys(oTechnicalParameters[sMatchingParameter]).reduce((oParameterDescription, sParameterAttributeName) => {
                oParameterDescription[sParameterAttributeName] = oTechnicalParameters[sMatchingParameter][sParameterAttributeName];
                return oParameterDescription;
            }, {
                name: sMatchingParameter
            });
        });
    }

    /**
     * Obtains an array of all parameter names.
     *
     * @returns {string[]} All technical parameter names defined in this file
     *
     * @private
     */
    function getParameterNames () {
        return Object.keys(oTechnicalParameters);
    }

    /**
     * Checks whether the given parameter is a technical parameter.
     *
     * @param {string} parameterName the name of the parameter
     *
     * @returns {boolean}
     *
     *  Whether the given parameter name is a technical parameter
     */
    function isTechnicalParameter (parameterName) {
        return oTechnicalParameters.hasOwnProperty(parameterName);
    }

    /**
     * Get the value of the given parameter.
     *
     * @param {string} sParameterName The name of the parameter whose value should be returned
     * @param {object} oApplicationComponent The  current application component
     * @param {object} oApplicationContainer The current application container
     * @param {string} sApplicationType The type of the current application
     * @returns {*} The results for the given parameter name
     */
    function getParameterValueSync (sParameterName, oApplicationComponent, oApplicationContainer, sApplicationType) {
        if (sApplicationType === "UI5") {
            return getValueFromComponent(sParameterName, oApplicationComponent);
        }
        return getValueFromContainer(sParameterName, oApplicationContainer);
    }

    return {
        getParameterValue: getParameterValue,
        getParameterValueSync: getParameterValueSync,
        getParameters: getParameters,
        getParameterNames: getParameterNames,
        isTechnicalParameter: isTechnicalParameter
    };
});
