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

/**
 * @fileOverview Provides control sap.ushell.ui.BookmarkDialog.
 * This control provides a dialog-only API for the bookmark functionality.
 */

sap.ui.define([
    "sap/base/Log",
    "sap/m/Button",
    "sap/m/Dialog",
    "sap/m/library",
    "sap/m/MessageBox",
    "sap/m/Text",
    "sap/m/VBox",
    "sap/ui/core/library",
    "sap/ui/core/mvc/XMLView",
    "sap/ui/Device",
    "sap/ui/model/json/JSONModel",
    "sap/ushell/Container",
    "sap/ushell/resources",
    "sap/ushell/utils/CallbackQueue"
], (
    Log,
    Button,
    Dialog,
    mobileLibrary,
    MessageBox,
    Text,
    VBox,
    coreLibrary,
    XMLView,
    Device,
    JSONModel,
    Container,
    ushellResources,
    CallbackQueue
) => {
    "use strict";

    // shortcut for sap.m.ButtonType
    const ButtonType = mobileLibrary.ButtonType;

    // shortcut for sap.ui.core.ValueState
    const ValueState = coreLibrary.ValueState;

    /**
     * @typedef {object} sap.ushell.ui.BookmarkDialog.ShowOptions
     * @property {string} [title] - Title for the bookmark tile
     * @property {string} [subtitle] - Subtitle for the bookmark tile
     * @property {string} [info] - Info text for the bookmark tile
     * @property {string} [icon] - Icon for the bookmark tile
     * @property {string} [keywords] - Keywords for the bookmark
     * @property {string} [target] - navigation target for the bookmark
     * It is best practice to provide the target in the form of a semantic object and action, e.g. "#SalesOrder-display"
     * However for external target it is also supported to provide a URL, e.g. "https://www.sap.com"
     * @property {string} [serviceUrl] - Service URL for dynamic tiles
     * @property {string} [numberUnit] - Number unit for dynamic tiles
     *
     * @since 1.147.0
     * @public
     */

    /**
     * @alias sap.ushell.ui.BookmarkDialog
     * @namespace
     * @description BookmarkDialog provides a dialog-only API for the bookmark functionality.
     *
     * @since 1.147.0
     * @public
     */
    class BookmarkDialog {
        #oCallBackQueue = new CallbackQueue();
        #aDestroyables = [];
        #oDialog;

        /**
         * Gets the bookmark tile view instance.
         * @returns {sap.ui.core.mvc.XMLView} The bookmark tile view instance
         *
         * @since 1.147.0
         * @private
         */
        #getBookmarkTileView () {
            return this.#oDialog.getContent()[0];
        }

        /**
         * Opens the bookmark dialog.
         * @param {sap.ushell.ui.BookmarkDialog.ShowOptions} oShowOptions - Options for the bookmark dialog
         * @returns {Promise} A promise resolving to the opened dialog
         *
         * @since 1.147.0
         * @public
         */
        show (oShowOptions) {
            return this.#oCallBackQueue.push(() => this.#show(oShowOptions));
        }

        /**
         * Opens the bookmark dialog.
         * @param {sap.ushell.ui.BookmarkDialog.ShowOptions} oShowOptions - Options for the bookmark dialog
         * @returns {Promise} A promise resolving to the opened dialog
         *
         * @since 1.147.0
         * @private
         */
        async #show (oShowOptions = {}) {
            this.#destroyAll();

            const oModel = new JSONModel({
                title: oShowOptions.title || "",
                subtitle: oShowOptions.subtitle || "",
                info: oShowOptions.info || "",
                icon: oShowOptions.icon || "",
                numberUnit: oShowOptions.numberUnit || "",
                keywords: oShowOptions.keywords || "",
                target: oShowOptions.target || "",
                serviceUrl: oShowOptions.serviceUrl,
                serviceRefreshInterval: oShowOptions.serviceRefreshInterval,
                dataSource: oShowOptions.dataSource,
                numberValue: 0
            });
            this.#aDestroyables.push(oModel);

            const oViewData = {
                serviceUrl: oShowOptions.serviceUrl,
                target: oShowOptions.target
            };

            const oView = await XMLView.create({
                viewName: "sap.ushell.ui.BookmarkDialog.SaveOnPage",
                viewData: oViewData
            });
            this.#aDestroyables.push(oView);

            this.#oDialog = this.#createDialog(oView);
            this.#aDestroyables.push(this.#oDialog);
            this.#oDialog.setModel(oModel);
            this.#oDialog.setModel(ushellResources.i18nModel, "i18n");

            this.#oDialog.open();
        }

        /**
         * Creates the bookmark dialog with the given content.
         * @param {sap.ui.core.Control} oContent - The content to display in the dialog
         * @returns {sap.m.Dialog} The created dialog instance
         *
         * @since 1.147.0
         * @private
         */
        #createDialog (oContent) {
            return new Dialog({
                id: "bookmarkDialog",
                title: "{i18n>addToHomePageBtn}",
                contentWidth: "25rem",
                content: oContent,
                beginButton: new Button("bookmarkOkBtn", {
                    text: "{i18n>saveBtn}",
                    type: ButtonType.Emphasized,
                    press: [this.#handleOKButtonPress, this]
                }),
                endButton: new Button("bookmarkCancelBtn", {
                    text: "{i18n>cancelBtn}",
                    press: [this.#handleCancelButtonPress, this]
                }),
                stretch: Device.system.phone,
                horizontalScrolling: false,
                afterClose: () => {
                    this.#destroyAll();
                }
            }).addStyleClass("sapContrastPlus");
        }

        /**
         * Handles the OK button press event.
         * Validates the bookmark data and saves the bookmark to the selected pages.
         *
         * @since 1.147.0
         * @private
         */
        #handleOKButtonPress () {
            if (this.#oDialog.getBusy()) {
                return;
            }

            const oBookmarkTileController = this.#getBookmarkTileView().getController();
            const oBookmarkData = oBookmarkTileController.getBookmarkTileData();
            let bValid = true;

            if (oBookmarkData.contentNodes.length === 0) {
                const oPageSelect = oBookmarkTileController.byId("SelectedNodesComboBox");
                if (oPageSelect) {
                    oPageSelect.setValueState(ValueState.Error);
                    oPageSelect.setValueStateText(ushellResources.i18n.getText("bookmarkPageSelectError"));
                    bValid = false;
                }
            }

            if (!oBookmarkData.title || oBookmarkData.title.length < 1) {
                const oTitle = oBookmarkTileController.byId("bookmarkTitleInput");
                if (oTitle) {
                    oTitle.setValueState(ValueState.Error);
                    oTitle.setValueStateText(ushellResources.i18n.getText("bookmarkTitleInputError"));
                    bValid = false;
                }
            }

            if (!bValid) {
                Container.getServiceAsync("MessageInternal").then((oMessageService) => {
                    oMessageService.info(ushellResources.i18n.getText("SaveAsTileDialog.MessageToast.ValidationFailed"));
                });
                return;
            }

            this.#oDialog.setBusy(true);
            this.#oDialog.close();
            this.#oDialog.destroy();

            const oBookmarkServicePromise = Container.getServiceAsync("BookmarkV2");
            const aAddToPagePromises = oBookmarkData.contentNodes.map((oContentNode) => {
                return oBookmarkServicePromise.then((oBookmarkService) => {
                    return oBookmarkService.addBookmark(oBookmarkData, oContentNode);
                })
                    .then(() => {
                        return {
                            pageId: oContentNode.id,
                            status: "resolved"
                        };
                    })
                    .catch((oError) => {
                        Log.error("Failed to add a bookmark: ", oError, "sap.ushell.ui.BookmarkDialog");
                        return {
                            pageId: oContentNode.id,
                            error: oError.message,
                            status: "failed"
                        };
                    });
            });

            Promise.all(aAddToPagePromises).then((aResults) => {
                Container.getServiceAsync("MessageInternal").then((oMessageService) => {
                    const iSuccess = aResults.filter((oResult) => {
                        return oResult.status === "resolved";
                    }).length;
                    const aFailed = aResults.filter((oResult) => {
                        return oResult.status === "failed";
                    });

                    if (iSuccess === 1) {
                        oMessageService.info(ushellResources.i18n.getText("SaveAsTileDialog.MessageToast.TileCreatedInPage"));
                    } else if (iSuccess > 1) {
                        oMessageService.info(ushellResources.i18n.getText("SaveAsTileDialog.MessageToast.TileCreatedInPages"));
                    }

                    if (aFailed.length) {
                        this.#showErrorDialog(aFailed, iSuccess, oBookmarkData.title);
                    }
                });
            });
        }

        /**
         * Handles the Cancel button press event.
         * Shows a confirmation dialog if there are unsaved changes.
         *
         * @since 1.147.0
         * @private
         */
        #handleCancelButtonPress () {
            const oBookmarkTileController = this.#getBookmarkTileView().getController();
            const oBookmarkData = oBookmarkTileController.getBookmarkTileData();

            if (oBookmarkData.title || oBookmarkData.subtitle || oBookmarkData.info || oBookmarkData.contentNodes.length) {
                const sDiscardAction = ushellResources.i18n.getText("SaveAsTileDialog.MessageBox.Action.Discard");
                const sMessage = ushellResources.i18n.getText("SaveAsTileDialog.MessageBox.Message.Discard");
                const sTitle = ushellResources.i18n.getText("SaveAsTileDialog.MessageBox.Title.Discard");

                MessageBox.show(sMessage, {
                    title: sTitle,
                    actions: [
                        sDiscardAction,
                        MessageBox.Action.CANCEL
                    ],
                    emphasizedAction: sDiscardAction,
                    onClose: function (sResult) {
                        if (sResult === sDiscardAction) {
                            this.#oDialog.close();
                            this.#destroyAll();
                        }
                    }.bind(this)
                });
            } else {
                this.#oDialog.close();
                this.#destroyAll();
            }
        }

        /**
         * Shows an error dialog when bookmark creation fails for one or more pages.
         * @param {object[]} aFailedBookmarks - Array of failed bookmark results with pageId and error
         * @param {int} iOfSuccessfulBookmarks - Number of successfully created bookmarks
         * @param {string} sBookmarkTitle - The title of the bookmark that was being created
         * @returns {Promise} A promise that resolves when the error dialog is shown
         *
         * @since 1.147.0
         * @private
         */
        async #showErrorDialog (aFailedBookmarks, iOfSuccessfulBookmarks, sBookmarkTitle) {
            const oMessageService = await Container.getServiceAsync("MessageInternal");
            let sErrorMessage;
            let sDetailMessage;

            if (aFailedBookmarks.length === 1) {
                if (iOfSuccessfulBookmarks === 0) {
                    sErrorMessage = ushellResources.i18n.getText("SaveAsTileDialog.MessageBox.SinglePageError");
                } else {
                    sErrorMessage = ushellResources.i18n.getText("SaveAsTileDialog.MessageBox.OnePageError");
                }
                sDetailMessage = ushellResources.i18n.getText("SaveAsTileDialog.MessageBox.PageErrorDetail",
                    [sBookmarkTitle, aFailedBookmarks[0].pageId]);
            } else if (aFailedBookmarks.length > 1) {
                if (iOfSuccessfulBookmarks === 0) {
                    sErrorMessage = ushellResources.i18n.getText("SaveAsTileDialog.MessageBox.AllPagesError");
                } else {
                    sErrorMessage = ushellResources.i18n.getText("SaveAsTileDialog.MessageBox.SomePagesError");
                }
                const sPageIds = aFailedBookmarks.map((oFailedPage) => {
                    return oFailedPage.pageId;
                }).join("\n");
                sDetailMessage = ushellResources.i18n.getText("SaveAsTileDialog.MessageBox.PagesErrorDetail", [sBookmarkTitle, sPageIds]);
            }

            const oDetailsBox = new VBox({
                items: [
                    new Text({
                        text: sDetailMessage
                    }).addStyleClass("sapUiSmallMarginBottom"),
                    new Text({
                        text: aFailedBookmarks.map((oFailedPage) => {
                            return oFailedPage.error;
                        }).join("\n")
                    }).addStyleClass("sapUiSmallMarginBottom"),
                    new Text({
                        text: ushellResources.i18n.getText("SaveAsTileDialog.MessageBox.PageErrorSolution")
                    })
                ]
            });

            oMessageService.errorWithDetails(sErrorMessage, oDetailsBox);
        }

        /**
         * Destroys all created UI elements and models.
         * Called when the dialog is closed or canceled.
         *
         * @since 1.147.0
         * @private
         */
        #destroyAll () {
            if (!this.#aDestroyables?.length) {
                return;
            }

            this.#aDestroyables.forEach((oDestroyable) => {
                oDestroyable.destroy();
            });
            this.#aDestroyables = [];
        }

        /**
         * Only for testing purposes
         * @private
         */
        reset () {
            this.#destroyAll();
        }
    }
    return new BookmarkDialog();
});
