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

sap.ui.define([
    "sap/m/Label",
    "sap/m/Text",
    "sap/ui/core/mvc/Controller",
    "sap/ui/core/Fragment",
    "sap/ui/core/Title",
    "sap/ui/model/json/JSONModel",
    "sap/ushell/library",
    "sap/ushell/services/AppConfiguration",
    "sap/ushell/Config",
    "sap/ushell/resources",
    "sap/base/Log",
    "sap/base/util/isEmptyObject",
    "sap/ui/Device",
    "sap/ushell/Container",
    "sap/ushell/AppInfoParameters"
], (
    Label,
    Text,
    Controller,
    Fragment,
    Title,
    JSONModel,
    ushellLibrary,
    AppConfiguration,
    Config,
    resources,
    Log,
    isEmptyObject,
    Device,
    Container,
    AppInfoParameters
) => {
    "use strict";

    const Framework = ushellLibrary.Framework;
    const mapToFwk = AppInfoParameters.mapToFwk;

    return Controller.extend("sap.ushell.ui.footerbar.AboutDialog.controller", {

        /**
         * Triggered before the About Dialog is opened. Sets up it's model.
         * @since 1.89.0
         * @private
         */
        onBeforeOpen: function () {
            this._setupAboutDialogModel();
            this._updateHeaderButtonVisibility();

            // Set the first item in the list as selected
            const oAboutDialogMasterList = Fragment.byId("aboutDialogFragment", "aboutDialogEntryList");
            oAboutDialogMasterList.setSelectedItem(oAboutDialogMasterList.getItems()[0]);
        },

        /**
         * Triggered when the About Dialog's "Cancel" button is pressed.
         * @since 1.89.0
         * @private
         */
        onClose: function () {
            this._getDialog().close();
        },

        /**
         * Destroys the About Dialog control.
         * @since 1.89.0
         * @private
         */
        onAfterClose: function () {
            this._getDialog().destroy();
        },

        /**
         * Retrieves the About Dialog control by its ID, stores it into a local variable and returns it.
         * @returns {sap.m.Dialog} The About Dialog control.
         * @since 1.89.0
         */
        _getDialog: function () {
            if (!this.oAboutDialog) {
                this.oAboutDialog = Fragment.byId("aboutDialogFragment", "aboutDialog");
            }
            return this.oAboutDialog;
        },

        /**
         * Retrieve all data needed to build the dialog and store it into a model.
         * @since 1.89.0
         * @private
         */
        _setupAboutDialogModel: function () {
            const oAboutDialog = this._getDialog();

            // application info
            this._setupAppInfoModel().catch((oError) => {
                Log.error("Failed to setup application info model.", oError);
            });
            // system information
            const oSystemInformation = this._getSystemInformation();
            const oSystemInformationModel = new JSONModel(oSystemInformation);
            // user environment information
            const oUserEnvironmentInformation = this._getUserEnvironmentInformation();
            const oUserEnvironmentModel = new JSONModel(oUserEnvironmentInformation);

            oAboutDialog.setModel(oSystemInformationModel, "SysInfo");
            oAboutDialog.setModel(oUserEnvironmentModel, "UserEnvInfo");
        },

        /**
         * Retrieves the SplitApp object from the About Dialog fragment.
         * @returns {sap.m.SplitApp} The SplitApp object.
         * @since 1.130.0
         * @private
         */
        _getSplitAppObj: function () {
            const oResult = Fragment.byId("aboutDialogFragment", "aboutDialogContainer");
            if (!oResult) {
                Log.info("SplitContainer object can't be found");
            }
            return oResult;
        },

        /**
         * Triggered after the master area of the SplitApp is closed.
         * @since 1.130.0
         * @private
         */
        onAfterMasterClose: function () {
            this._getSplitAppObj().setMode("ShowHideMode");
            this._updateHeaderButtonVisibility();
        },

        /**
        * Triggered when an item in the master list is pressed.
        * @param {sap.ui.base.Event} oEvent The event object.
        * @since 1.130.0
        * @private
        */
        onItemPress: function (oEvent) {
            const sToPageId = oEvent.getParameter("listItem").getCustomData()[0].getValue();
            const sToPageIdOnFragment = Fragment.createId("aboutDialogFragment", sToPageId);
            this._getSplitAppObj().toDetail(sToPageIdOnFragment);
            if (Device.system.desktop) {
                this._getSplitAppObj().hideMaster();
            }
            if (Device.system.phone) {
                this._getSplitAppObj().setMode("HideMode");
                this.onAfterMasterClose();
                this._updateHeaderButtonVisibility();
            }
        },

        /**
         * Navigates back to the master area of the SplitApp.
         * @since 1.130.0
         * @private
         */
        navigateBackToMaster: function () {
            this._getSplitAppObj().backToTopMaster();
            this._updateHeaderButtonVisibility();
        },

        /**
         * Triggered before the master area of the SplitApp is opened.
         * @since 1.130.0
         * @private
         */
        onBeforeMasterOpen: function () {
            this._updateHeaderButtonVisibility();
        },

        /**
         * Triggered when the "Menu" button is pressed.
         * @since 1.130.0
         * @private
         */
        navToggleButtonPressHandler: function () {
            if (this._getSplitAppObj().isMasterShown()) {
                this._getSplitAppObj().hideMaster();
            } else {
                this._getSplitAppObj().showMaster();
            }
            this._updateHeaderButtonVisibility();
        },

        /**
         * Updates the visibility of the header buttons.
         * @since 1.130.0
         * @private
         */
        _updateHeaderButtonVisibility: function () {
            const bIsMasterShown = this._getSplitAppObj().isMasterShown();
            const oBackButton = Fragment.byId("aboutDialogFragment", "aboutDialogNavBackButton");
            const oMenuButton = Fragment.byId("aboutDialogFragment", "aboutDialogMenuButton");
            if (Device.system.phone) {
                oBackButton.setVisible(!bIsMasterShown);
                if (!oBackButton) {
                    Log.info("oBackButton object can't be found");
                }
            } else {
                if (Device.orientation.portrait) {
                    oMenuButton.setVisible(true);
                    oMenuButton.setPressed(bIsMasterShown);
                    oMenuButton.setTooltip(resources.i18n.getText(bIsMasterShown ? "ToggleButtonHide" : "ToggleButtonShow"));
                } else {
                    oMenuButton.setVisible(false);
                }
                if (!oMenuButton) {
                    Log.info("oMenuButton object can't be found");
                }
            }
        },

        /**
         * Sets up the application information model.
         * @since 1.89.0
         * @private
         * @returns {Promise} A promise that resolves when the application information model is set up.
         */
        _setupAppInfoModel: function () {
            const oAboutDialog = this._getDialog();

            return new Promise((resolve, reject) => {
                Container.getServiceAsync("AppLifeCycle")
                    .then((oAppLifeCycle) => {
                        const oCurrentApplication = oAppLifeCycle.getCurrentApplication();
                        return Promise.all([
                            oCurrentApplication.getAllAppInfo(true),
                            oCurrentApplication.getAllAppInfo(false),
                            this._getContentProviderLabel(oCurrentApplication)
                        ]);
                    })
                    .then((aResults) => {
                        const oMetaData = AppConfiguration.getMetadata();
                        const oAllAppInfoValues = aResults[0] || {};
                        const oAllAppInfo = aResults[1] || {};
                        const sProviderId = aResults[2];

                        // 1. title from WebGUI/WDA (PostMessage API)
                        // 2. title from metadata
                        oAllAppInfoValues.appTitle = oAllAppInfoValues.title || oMetaData.title || "";
                        oAllAppInfoValues.contentProviderLabel = sProviderId;

                        const oApplicationInformation = this._buildApplicationInformationObject(oAllAppInfoValues, oAllAppInfo);
                        oAboutDialog.setModel(new JSONModel(oApplicationInformation), "AppInfo");
                        resolve();
                    })
                    .catch((oError) => {
                        reject(oError);
                    });
            });
        },

        /**
         * Determines how to display the technicalAppComponentId field in the About Dialog
         * based on the application framework type.
         *
         * Display behavior per framework type:
         * - SAPUI5: Shows "SAPUI5 App ID" label with the component ID
         * - TR: Hidden (Transaction field is shown separately via "transaction")
         * - WDA: Shows "Web Dynpro ABAP App" label; if format is "WdaApp~WdaAppConfiguration",
         *        splits into two entries: "Web Dynpro ABAP App" and "App Configuration"
         * - WCF: Shows "Target ID" label
         * - URL/Other: Shows default "Technical Component ID" label
         *
         * @param {string} sAppFrameworkId The app framework ID (UI5, GUI, WDA, WCF, URL, etc.)
         * @param {string} sTechnicalAppComponentId The technical component ID value (parsed for WDA apps)
         * @returns {object} Display configuration object:
         *   - {show: true, labelKey: string} - Show with i18n label key
         *   - {show: true, labelKey: string, value: string} - Show with custom value
         *   - {show: false} - Hide field completely
         *   - {show: true, splitEntries: Array<{labelKey: string, value: string}>} - Multiple entries
         * @since 1.147.0
         * @private
         */
        _getTechCompIdDisplayConfig: function (sAppFrameworkId, sTechnicalAppComponentId) {
            switch (mapToFwk[sAppFrameworkId]) {
                case Framework.UI5:
                    return { show: true, labelKey: "sapui5AppId" };

                case Framework.GUI:
                    return { show: false };

                case Framework.WDA:
                    if (sTechnicalAppComponentId) {
                        const oMatch = sTechnicalAppComponentId.match(/^([^~]+)~(.+)$/);
                        if (oMatch) {
                            const sWdaApp = oMatch[1].trim();
                            const sAppConfig = oMatch[2].trim();
                            const sWdaAppConfiguration = sAppConfig || sWdaApp;
                            return {
                                show: true,
                                splitEntries: [
                                    { labelKey: "wdaApp", value: sWdaApp },
                                    { labelKey: "wdaAppConfiguration", value: sWdaAppConfiguration }
                                ]
                            };
                        }
                    }
                    return {
                        show: true,
                        splitEntries: [
                            { labelKey: "wdaApp", value: sTechnicalAppComponentId },
                            { labelKey: "wdaAppConfiguration", value: sTechnicalAppComponentId }
                        ]
                    };

                case Framework.WCF:
                    return { show: true, labelKey: "wcfTargetId" };

                default:
                    return { show: true, labelKey: "technicalAppComponentId" };
            }
        },

        /**
         * Adds technical component ID entries to the application info model data.
         * Handles both split entries (e.g., WDA app with configuration) and single entries.
         *
         * @param {object} oAppInfoModelData The application info model data object
         * @param {object} oTechCompIdConfig The technical component ID display configuration
         * @param {string} sParameterName The parameter name (usually "technicalAppComponentId")
         * @param {object} oParameterInfo The parameter info values
         * @since 1.147.0
         * @private
         */
        _addTechCompIdEntries: function (oAppInfoModelData, oTechCompIdConfig, sParameterName, oParameterInfo) {
            if (oTechCompIdConfig.splitEntries) {
                oTechCompIdConfig.splitEntries.forEach((oEntry) => {
                    oAppInfoModelData.entries.push({
                        parameterName: oEntry.labelKey,
                        value: `{i18n>${oEntry.labelKey}}`,
                        type: "label"
                    });
                    oAppInfoModelData.entries.push({
                        parameterName: oEntry.labelKey,
                        value: oEntry.value,
                        type: "text"
                    });
                });
                return;
            }

            if (!oTechCompIdConfig.show) {
                return;
            }

            oAppInfoModelData.entries.push({
                parameterName: sParameterName,
                value: `{i18n>${oTechCompIdConfig.labelKey}}`,
                type: "label"
            });

            oAppInfoModelData.entries.push({
                parameterName: sParameterName,
                value: oTechCompIdConfig.value || oParameterInfo[sParameterName],
                type: "text"
            });
        },

        /**
         * Builds the application information object for the About Dialog model.
         *
         * @param {object} oParameterInfo The parameter info values
         * @param {object} oParameterInfoExtended The extended parameter info with custom properties
         * @returns {object} The application information model data with entries and customEntries arrays
         * @private
         */
        _buildApplicationInformationObject: function (oParameterInfo, oParameterInfoExtended) {
            const aSortOrder = [
                "appTitle",
                "appId",
                "transaction",
                "appVersion",
                "appSupportInfo",
                "technicalAppComponentId",
                "appFrameworkId",
                "appFrameworkVersion",
                "contentProviderLabel"
            ];
            const oAppInfoModelData = {
                entries: [],
                customEntries: []
            };

            const sAppFrameworkId = oParameterInfo.appFrameworkId || "";
            const sTechnicalAppComponentId = oParameterInfo.technicalAppComponentId || "";
            const oTechCompIdConfig = this._getTechCompIdDisplayConfig(sAppFrameworkId, sTechnicalAppComponentId);
            aSortOrder.forEach((sParameterName) => {
                if (oParameterInfo[sParameterName]) {
                    if (sParameterName === "technicalAppComponentId") {
                        this._addTechCompIdEntries(
                            oAppInfoModelData,
                            oTechCompIdConfig,
                            sParameterName,
                            oParameterInfo
                        );
                    } else if (sParameterName === "appFrameworkId") {
                        oAppInfoModelData.entries.push({
                            parameterName: sParameterName,
                            value: `{i18n>${sParameterName}}`,
                            type: "label"
                        });

                        oAppInfoModelData.entries.push({
                            parameterName: sParameterName,
                            value: `{i18n>appFrameworkId.${oParameterInfo[sParameterName]}}`,
                            type: "text"
                        });
                    } else {
                        oAppInfoModelData.entries.push({
                            parameterName: sParameterName,
                            value: `{i18n>${sParameterName}}`,
                            type: "label"
                        });

                        oAppInfoModelData.entries.push({
                            parameterName: sParameterName,
                            value: oParameterInfo[sParameterName],
                            type: "text"
                        });
                    }
                }
            });

            this._addCustomPropertiesToModel(oAppInfoModelData, oParameterInfoExtended, oParameterInfo.transaction);

            return oAppInfoModelData;
        },

        /**
         * Adds custom properties to the application information model data.
         * If transaction is already shown in the standard entries, any custom entry
         * with the same transaction value is excluded to avoid duplication.
         *
         * @param {object} oAppInfoModelData The application info model data object
         * @param {object} oParameterInfoExtended The extended parameter info with custom properties
         * @param {string} sTransaction The standard transaction value already shown in entries
         * @private
         */
        _addCustomPropertiesToModel: function (
            oAppInfoModelData,
            oParameterInfoExtended,
            sTransaction
        ) {
            const aCustomProperties = Object.keys(oParameterInfoExtended)
                .filter((sParameter) => {
                    const oParameter = oParameterInfoExtended[sParameter];
                    return (oParameter.showInAbout);
                })
                .filter((sParameter) => {
                    const oParameter = oParameterInfoExtended[sParameter];
                    return !(sTransaction && oParameter.value === sTransaction);
                })
                .sort((sParameter1, sParameter2) => {
                    return sParameter1.toLowerCase() < sParameter2.toLowerCase() ? -1 : 1;
                })
                .map((sParameter) => {
                    const oParameter = oParameterInfoExtended[sParameter];
                    return {
                        parameterName: sParameter,
                        ...oParameter
                    };
                });

            aCustomProperties.forEach((oParameter) => {
                oAppInfoModelData.customEntries.push({
                    parameterName: oParameter.parameterName,
                    value: oParameter.label,
                    type: "label"
                });

                oAppInfoModelData.customEntries.push({
                    parameterName: oParameter.parameterName,
                    value: oParameter.value,
                    type: "text"
                });
            });
        },

        _getSystemInformation: function () {
            const oLogonSystem = Container.getLogonSystem();
            const oSysInfoModelData = {
                entries: []
            };

            const sProductName = oLogonSystem.getProductName();
            if (sProductName) {
                oSysInfoModelData.entries.push({
                    parameterName: "productName",
                    value: "{i18n>productName}",
                    type: "label"
                });
                oSysInfoModelData.entries.push({
                    parameterName: "productName",
                    value: sProductName,
                    type: "text"
                });
            }

            const sProductVersion = oLogonSystem.getProductVersion();
            if (sProductVersion) {
                oSysInfoModelData.entries.push({
                    parameterName: "productVersion",
                    value: "{i18n>productVersionFld}",
                    type: "label"
                });
                oSysInfoModelData.entries.push({
                    parameterName: "productVersion",
                    value: sProductVersion,
                    type: "text"
                });
            }

            const sSystemName = oLogonSystem.getSystemName();
            if (sSystemName) {
                oSysInfoModelData.entries.push({
                    parameterName: "systemName",
                    value: "{i18n>systemName}",
                    type: "label"
                });
                oSysInfoModelData.entries.push({
                    parameterName: "systemName",
                    value: sSystemName,
                    type: "text"
                });
            }

            const sSystemRole = oLogonSystem.getSystemRole();
            if (sSystemRole) {
                oSysInfoModelData.entries.push({
                    parameterName: "systemRole",
                    value: "{i18n>systemRole}",
                    type: "label"
                });
                oSysInfoModelData.entries.push({
                    parameterName: "systemRole",
                    value: sSystemRole,
                    type: "text"
                });
            }

            const sTenantRole = oLogonSystem.getTenantRole();
            if (sTenantRole) {
                oSysInfoModelData.entries.push({
                    parameterName: "tenantRole",
                    value: "{i18n>tenantRole}",
                    type: "label"
                });
                oSysInfoModelData.entries.push({
                    parameterName: "tenantRole",
                    value: sTenantRole,
                    type: "text"
                });
            }

            return oSysInfoModelData;
        },

        _getUserEnvironmentInformation: function () {
            const oUser = Container.getUser();
            const oUserEnvInfoModelData = {
                entries: []
            };

            const sDeviceType = this._getDeviceType();
            if (sDeviceType) {
                oUserEnvInfoModelData.entries.push({
                    parameterName: "deviceType",
                    value: "{i18n>deviceType}",
                    type: "label"
                });
                oUserEnvInfoModelData.entries.push({
                    parameterName: "deviceType",
                    value: sDeviceType,
                    type: "text"
                });
            }

            const sTheme = oUser.getTheme();
            oUserEnvInfoModelData.entries.push({
                parameterName: "activeTheme",
                value: "{i18n>activeTheme}",
                type: "label"
            });
            oUserEnvInfoModelData.entries.push({
                parameterName: "activeTheme",
                value: sTheme,
                type: "text"
            });

            const bOptimizedForTouchInput = oUser.getContentDensity() === "cozy";
            oUserEnvInfoModelData.entries.push({
                parameterName: "optimizedForTouch",
                value: "{i18n>AppearanceContentDensityLabel}",
                type: "label"
            });
            oUserEnvInfoModelData.entries.push({
                parameterName: "optimizedForTouch",
                value: bOptimizedForTouchInput ? "{i18n>yes}" : "{i18n>no}",
                type: "text"
            });

            const bTouchSupported = this._isTouchSupported();
            oUserEnvInfoModelData.entries.push({
                parameterName: "touchSupported",
                value: "{i18n>touchSupported}",
                type: "label"
            });
            oUserEnvInfoModelData.entries.push({
                parameterName: "touchSupported",
                value: bTouchSupported ? "{i18n>yes}" : "{i18n>no}",
                type: "text"
            });

            oUserEnvInfoModelData.entries.push({
                parameterName: "userAgentFld",
                value: "{i18n>userAgentFld}",
                type: "label"
            });
            oUserEnvInfoModelData.entries.push({
                parameterName: "userAgentFld",
                value: navigator.userAgent,
                type: "text"
            });

            return oUserEnvInfoModelData;
        },

        _getContentProviderLabel: function (oApplication) {
            if (Config.last("/core/contentProviders/providerInfo/enabled")) {
                return oApplication.getSystemContext().then((oSystemContext) => {
                    return oSystemContext.label;
                });
            }
            return Promise.resolve(undefined);
        },

        _getDeviceType: function () {
            let sDeviceType;
            if (Device.system.combi) {
                sDeviceType = "{i18n>configuration.form_factor_combi}";
            } else if (Device.system.desktop) {
                sDeviceType = "{i18n>configuration.form_factor_desktop}";
            } else if (Device.system.tablet) {
                sDeviceType = "{i18n>configuration.form_factor_tablet}";
            } else if (Device.system.phone) {
                sDeviceType = "{i18n>configuration.form_factor_phone}";
            }
            return sDeviceType;
        },

        _isTouchSupported: function () {
            return Device.support.touch && (Device.system.tablet || Device.system.phone || Device.system.combi);
        },

        itemFactory: function (id, context) {
            const sType = context.getProperty("type");

            switch (sType) {
                case "label":
                    return new Label({
                        text: context.getProperty("value")
                    });
                case "title":
                    return new Title({
                        text: context.getProperty("value")
                    });
                default: // case "text"
                    return new Text({
                        text: context.getProperty("value")
                    });
            }
        },

        /**
         * Formatter function to calculate the number of columns displayed in a form depending on the model content.
         * @param {object} modelData The object that stores the form's data.
         * @returns {int} Returns 1 in case there is a maximum of two properties in the object. Otherwise returns 2.
         * @since 1.89.0
         * @private
         */
        calculateNumberOfColumns: function (modelData) {
            if (!this.isFormVisible(modelData)) {
                return 1;
            }
            return Object.keys(modelData).length > 1 ? 2 : 1;
        },

        /**
         * Formatter function to check if a form should be visible on the About Dialog.
         * @param {object} modelData The object that stores the form's data.
         * @returns {boolean} Returns false in case the object is empty or undefined. Otherwise returns true.
         * @since 1.89.0
         * @private
         */
        isFormVisible: function (modelData) {
            return !!modelData && !isEmptyObject(modelData);
        }
    });
});
