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

/**
 * @fileOverview Overview Controller for the Navigation Sample Application.
 *
 * Displays startup parameters and AppState data received during navigation.
 * Provides both tree and text views of the AppState JSON structure.
 *
 * @since 1.147
 */
sap.ui.define([
    "sap/base/Log",
    "sap/ui/core/mvc/Controller",
    "sap/ui/model/json/JSONModel",
    "sap/m/MessageStrip",
    "sap/m/MessageToast",
    "sap/m/Popover",
    "sap/ushell/Container"
], (
    Log,
    Controller,
    JSONModel,
    MessageStrip,
    MessageToast,
    Popover,
    Container
) => {
    "use strict";

    const sComponent = "sap.ushell.sandbox.NavigationSample.controller.Overview";

    /**
     * Overview controller displaying startup parameters and AppState data.
     *
     * @class
     * @extends sap.ui.core.mvc.Controller
     * @alias sap.ushell.sandbox.NavigationSample.controller.Overview
     * @since 1.147
     * @private
     */
    return Controller.extend("sap.ushell.sandbox.NavigationSample.controller.Overview", {

        /**
         * Initializes view models and loads AppState data from the Navigation service.
         *
         * @private
         */
        onInit: function () {
            const oView = this.getView();

            this.oViewModel = new JSONModel({
                appStartDate: new Date(),
                showTreeView: true,
                sFormattedJSON: ""
            });
            oView.setModel(this.oViewModel, "overviewView");

            this.oTreeModel = new JSONModel({
                nodes: [{
                    text: "No AppState data available",
                    icon: "sap-icon://information",
                    type: "Inactive",
                    nodes: []
                }]
            });
            this.oAppStateModel = new JSONModel({ appstate: " no app state " });
            oView.setModel(this.oTreeModel, "appStateTree");
            oView.setModel(this.oAppStateModel, "AppState");

            const bStandalone = this.getOwnerComponent().getModel("app").getProperty("/isStandalone");
            if (!bStandalone) {
                Container.getServiceAsync("Navigation")
                    .then((oNavService) => oNavService.getStartupAppState(this.getOwnerComponent()))
                    .then((oAppState) => {
                        const oAppStateData = oAppState.getData();
                        const oModelData = { parameters: [] };

                        oModelData.stringifiedAppstate = JSON.stringify(oAppState.getData() || " no app state ");
                        oModelData.appStateKey = oAppState.getKey();

                        if (oAppStateData && typeof oAppStateData === "object" && !Array.isArray(oAppStateData)) {
                            Object.keys(oAppStateData).forEach((sParamName) => {
                                oModelData.parameters.push({
                                    name: sParamName,
                                    value: JSON.stringify(oAppStateData[sParamName])
                                });
                            });
                        }
                        this.oAppStateModel.setProperty("/appstate", oModelData);
                        this._setupAppStateWatcher();
                    })
                    .catch((oError) => {
                        Log.warning(`AppState could not be loaded: ${oError.message}`, null, sComponent);
                    });
            }
        },

        /**
         * Sets up a watcher on the AppState model to sync changes to view models.
         *
         * @private
         */
        _setupAppStateWatcher: function () {
            const oAppStateModel = this.getView().getModel("AppState");
            if (!oAppStateModel) {
                return;
            }

            const fnUpdate = () => {
                const sRaw = oAppStateModel.getProperty("/appstate/stringifiedAppstate");
                if (!sRaw) {
                    return;
                }
                let vParsed;
                try {
                    vParsed = JSON.parse(sRaw);
                } catch (oError) {
                    return;
                }
                if (typeof vParsed === "string" && vParsed.trim() === "no app state") {
                    return;
                }
                this._updateAppStateTree(sRaw);
            };

            fnUpdate();
            oAppStateModel.attachPropertyChange((oEvent) => {
                if (oEvent.getParameter("path") === "/appstate/stringifiedAppstate") {
                    fnUpdate();
                }
            });
        },

        /**
         * Toggles between tree and text view of AppState data.
         *
         * @param {sap.ui.base.Event} oEvent - The press event from the toggle button
         * @private
         */
        onToggleView: function (oEvent) {
            const bPressed = oEvent.getSource().getPressed();
            this.oViewModel.setProperty("/showTreeView", bPressed);
        },

        /**
         * Formatter function to invert a boolean value.
         *
         * @param {boolean} bValue - The value to invert
         * @returns {boolean} The inverted value
         * @private
         */
        invertBoolean: function (bValue) {
            return !bValue;
        },

        /**
         * Recursively converts JSON into a tree node structure for sap.m.Tree binding.
         *
         * @param {object|Array} oJson - JSON object or array to convert
         * @param {string} [sParentPath=""] - Path of the current node for debugging
         * @returns {Array} Array of tree nodes with text, icon, type, and nodes properties
         * @private
         */
        _buildTreeData: function (oJson, sParentPath = "") {
            const aNodes = [];
            const aExcludeProperties = ["__nav", "__metadata", "__proto__"];

            if (oJson === null) {
                return aNodes;
            }

            if (Array.isArray(oJson)) {
                oJson.forEach((vItem, iIndex) => {
                    const sCurrentPath = sParentPath ? `${sParentPath}[${iIndex}]` : `[${iIndex}]`;
                    if (vItem !== null && typeof vItem === "object") {
                        aNodes.push({
                            text: `[${iIndex}] (${Array.isArray(vItem) ? "Array" : "Object"})`,
                            icon: Array.isArray(vItem) ? "sap-icon://list" : "sap-icon://folder",
                            type: "Active",
                            nodes: this._buildTreeData(vItem, sCurrentPath)
                        });
                    } else {
                        aNodes.push({
                            text: `[${iIndex}]: ${vItem}`,
                            icon: "sap-icon://document",
                            type: "Inactive",
                            nodes: []
                        });
                    }
                });
                return aNodes;
            }

            if (typeof oJson === "object") {
                Object.keys(oJson).forEach((sKey) => {
                    if (aExcludeProperties.includes(sKey)) {
                        return;
                    }
                    const sCurrentPath = sParentPath ? `${sParentPath}.${sKey}` : sKey;
                    const vValue = oJson[sKey];
                    if (vValue !== null && typeof vValue === "object") {
                        aNodes.push({
                            text: `${sKey} (${Array.isArray(vValue) ? "Array" : "Object"})`,
                            icon: Array.isArray(vValue) ? "sap-icon://list" : "sap-icon://folder",
                            type: "Active",
                            nodes: this._buildTreeData(vValue, sCurrentPath)
                        });
                    } else {
                        aNodes.push({
                            text: `${sKey}: ${vValue}`,
                            icon: "sap-icon://document",
                            type: "Inactive",
                            nodes: []
                        });
                    }
                });
                return aNodes;
            }

            aNodes.push({
                text: String(oJson),
                icon: "sap-icon://document",
                type: "Inactive",
                nodes: []
            });
            return aNodes;
        },

        /**
         * Parses AppState JSON and updates view and tree models.
         *
         * @param {string} sAppStateData - Stringified AppState JSON
         * @private
         */
        _updateAppStateTree: function (sAppStateData) {
            try {
                const vParsedData = JSON.parse(sAppStateData);
                const sFormattedJSON = JSON.stringify(vParsedData, null, 2);
                this.oViewModel.setProperty("/sFormattedJSON", sFormattedJSON);
                const aTreeData = this._buildTreeData(vParsedData);
                this.oTreeModel.setData({ nodes: aTreeData });
            } catch (oError) {
                Log.warning("Failed to parse AppState JSON for tree view:", oError);
                this.oViewModel.setProperty("/sFormattedJSON", "Invalid JSON Format");
                this.oTreeModel.setData({
                    nodes: [{
                        text: "Invalid JSON Format",
                        icon: "sap-icon://error",
                        type: "Inactive",
                        nodes: []
                    }]
                });
            }
        },

        /**
         * Copies the formatted AppState JSON to the clipboard.
         *
         * @private
         */
        onCopyFormattedJSON: function () {
            try {
                const oAppStateModel = this.getView().getModel("AppState");
                if (!oAppStateModel) {
                    MessageToast.show("No AppState data to copy");
                    return;
                }
                const sRaw = oAppStateModel.getProperty("/appstate/stringifiedAppstate");
                if (!sRaw) {
                    MessageToast.show("No AppState data to copy");
                    return;
                }
                let vParsed;
                try {
                    vParsed = JSON.parse(sRaw);
                } catch {
                    MessageToast.show("No AppState data to copy");
                    return;
                }
                if (typeof vParsed === "string" && vParsed.trim() === "no app state") {
                    MessageToast.show("No AppState data to copy");
                    return;
                }
                const sFormattedJSON = JSON.stringify(vParsed, null, 2);
                if (navigator.clipboard && navigator.clipboard.writeText) {
                    navigator.clipboard.writeText(sFormattedJSON).then(() => {
                        MessageToast.show("Formatted JSON copied to clipboard");
                    });
                }
            } catch (oError) {
                Log.warning("Failed to copy JSON:", oError);
                MessageToast.show("Failed to copy JSON");
            }
        },

        /**
         * Toggles expand/collapse state of all tree nodes.
         *
         * @param {sap.ui.base.Event} oEvent - The press event from the toggle button
         * @private
         */
        onToggleExpandCollapse: function (oEvent) {
            const oToggleButton = oEvent.getSource();
            const bPressed = oToggleButton.getPressed();
            const oTree = this.getView().byId("appStateTree");
            if (bPressed) {
                this._expandAllNodes(oTree);
                oToggleButton.setIcon("sap-icon://collapse-all");
                MessageToast.show("Tree fully expanded");
            } else {
                this._collapseAllNodes(oTree);
                oToggleButton.setIcon("sap-icon://expand-all");
                MessageToast.show("Tree collapsed");
            }
        },

        /**
         * Opens an informational Popover next to the triggering control.
         *
         * @param {sap.ui.base.Event} oEvent - Press event from the triggering control
         * @private
         */
        onInfoPress: async function (oEvent) {
            const sKey = oEvent.getSource().data("infoKey");
            const oBundle = await this.getView().getModel("i18n").getResourceBundle();
            const sText = oBundle.getText(sKey);
            if (!this._infoPopover) {
                this._infoPopover = new Popover({
                    showHeader: false,
                    content: [
                        new MessageStrip({
                            type: "Information",
                            showCloseButton: false
                        })
                    ]
                });
                this.getView().addDependent(this._infoPopover);
            }
            this._infoPopover.getContent()[0].setText(sText);
            this._infoPopover.openBy(oEvent.getSource());
        },

        /**
         * Expands all nodes in the given tree control.
         *
         * @param {sap.m.Tree} oTree - The tree control instance
         * @private
         */
        _expandAllNodes: function (oTree) {
            if (oTree) {
                let aItems = oTree.getItems();
                for (let iPass = 0; iPass < 5; iPass++) {
                    for (let i = 0; i < aItems.length; i++) {
                        try {
                            oTree.expand(i);
                        } catch {
                            // fail silently
                        }
                    }
                    aItems = oTree.getItems();
                }
            }
        },

        /**
         * Collapses all nodes in the given tree control.
         *
         * @param {sap.m.Tree} oTree - The tree control instance
         * @private
         */
        _collapseAllNodes: function (oTree) {
            if (oTree) {
                const aItems = oTree.getItems();
                for (let i = aItems.length - 1; i >= 0; i--) {
                    try {
                        oTree.collapse(i);
                    } catch {
                        // fail silently
                    }
                }
            }
        }
    });
});
