/*!
 * OpenUI5
 * (c) Copyright 2026 SAP SE or an SAP affiliate company.
 * Licensed under the Apache License, Version 2.0 - see LICENSE.txt.
 */

// A static class to show a busy indicator
sap.ui.define([
	'sap/ui/thirdparty/jquery',
	'../base/EventProvider',
	'./Popup',
	'./BusyIndicatorUtils',
	'sap/ui/core/Core',
	'sap/ui/core/library',
	'sap/ui/core/Lib',
	"sap/ui/performance/trace/FESR",
	"sap/ui/performance/trace/Interaction",
	"sap/base/Log",
	"sap/base/assert",
	"sap/base/util/now"
],
	function(
		jQuery,
		EventProvider,
		Popup,
		BusyIndicatorUtils,
		Core,
		coreLib,
		Library,
		FESR,
		Interaction,
		Log,
		assert,
		now
	) {
	"use strict";

	//shortcut for sap.ui.core.BusyIndicatorSize
	var BusyIndicatorSize = coreLib.BusyIndicatorSize;

	/**
	 * Provides methods to show or hide a waiting animation covering the whole
	 * page and blocking user interaction.
	 * @namespace
	 * @version 1.136.17
	 * @public
	 * @alias sap.ui.core.BusyIndicator
	 */
	var BusyIndicator = Object.assign( new EventProvider(), {
		oPopup: null,
		oDomRef: null,
		bOpenRequested: false,
		iDEFAULT_DELAY_MS: 1000,
		sDOM_ID: "sapUiBusyIndicator"
	});

	BusyIndicator.M_EVENTS = {
		Open: "Open",

		Close: "Close"
	};

	// This internal property keeps track if a show() call should be performed in case the core was not initialized yet.
	// When show() is called and this variable is undefined: a core init event-handler is attached.
	// When set to true || false internally, the core-init event-handler is not attached anymore.
	// This is to make sure the handler is only attached once.
	BusyIndicator._bShowIsDelayed = undefined;

	/**
	 * The <code>Open</code> event is fired, after the <code>BusyIndicator</code>
	 * has opened.
	 *
	 * @name sap.ui.core.BusyIndicator#Open
	 * @event
	 * @param {sap.ui.base.Event} oControlEvent is the event object
	 * @param {sap.ui.base.EventProvider} oControlEvent.getSource is the instance
	 *                                    that fired the event
	 * @param {object} oControlEvent.getParameters provides all additional parameters
	 *                                    that are delivered with the event
	 * @param {jQuery} oControlEvent.getParameters.$Busy is the jQuery object
	 *                                    of the BusyIndicator
	 * @public
	 */

	/**
	 * The <code>Close</code> event is fired, <strong>before</strong> the
	 * <code>BusyIndicator</code> has closed.
	 *
	 * @name sap.ui.core.BusyIndicator#Close
	 * @event
	 * @param {sap.ui.base.Event} oControlEvent is the event object
	 * @param {sap.ui.base.EventProvider} oControlEvent.getSource is the instance
	 *                                    that fired the event
	 * @param {object} oControlEvent.getParameters provides all additional parameters
	 *                                    that are delivered with the event
	 * @param {jQuery} oControlEvent.getParameters.$Busy is the jQuery object
	 *                                    of the BusyIndicator
	 * @public
	 */


	/**
	 * Sets up the BusyIndicator HTML and the Popup instance.
	 *
	 * @private
	 */
	BusyIndicator._init = function() {
		var oRootDomRef = document.createElement("div");
		oRootDomRef.id = this.sDOM_ID;

		const oBusyElement = BusyIndicatorUtils.getElement(BusyIndicatorSize.Large);
		oRootDomRef.appendChild(oBusyElement);

		this.oDomRef = oRootDomRef;

		this.oPopup = new Popup(oRootDomRef);
		this.oPopup.setModal(true, "sapUiBlyBusy");
		this.oPopup.setShadow(false);
		this.oPopup.attachOpened(function(oEvent) {
			this._onOpen(oEvent);
		}, this);
	};

	/**
	 * This is the event listener that is called when the Popup of the BusyIndicator
	 * has opened
	 *
	 * @param {jQuery.Event} oEvent is the event that is provided by the EventProvider
	 * @private
	 */
	BusyIndicator._onOpen = function(oEvent) {
		// Grab the focus once opened
		var oDomRef = document.getElementById(BusyIndicator.sDOM_ID);
		oDomRef.style.height = "100%";
		oDomRef.style.width = "100%";

		// setting the BusyIndicator's DOM to visible is done by the Popup

		var oAnimation = oDomRef.querySelector(".sapUiLocalBusyIndicator");
		oAnimation.className += " sapUiLocalBusyIndicatorFade";
		if (oDomRef) {
			oDomRef.focus();
		}

		// allow an event handler to do something with the indicator
		// and fire it after everything necessary happened
		this.fireOpen({
			$Busy: this.oPopup._$()
		});
	};

	/**
	 * Displays the <code>BusyIndicator</code> and starts blocking all user input.
	 * This only happens after some delay, and if, after that delay, the <code>BusyIndicator.hide()</code>
	 * has not yet been called in the meantime.
	 *
	 * There is a certain default value for the delay, which can be overridden.
	 *
	 * @public
	 * @param {int} [iDelay=1000] The delay in milliseconds before opening the <code>BusyIndicator</code>;
	 *                       It is not opened if <code>hide()</code> is called before the delay ends.
	 *                       If no delay (or no valid delay) is given, a delay of 1000 milliseconds is used.
	 */
	BusyIndicator.show = function(iDelay) {
		Log.debug("sap.ui.core.BusyIndicator.show (delay: " + iDelay + ") at " + Date.now());
		assert(iDelay === undefined || (typeof iDelay == "number" && (iDelay % 1 == 0)), "iDelay must be empty or an integer");

		if (BusyIndicator._bShowIsDelayed === undefined) {
			BusyIndicator._bShowIsDelayed = true;
			Core.ready(function() {
				BusyIndicator._bShowIsDelayed = undefined;
				if ((iDelay === undefined)
						|| ((iDelay != 0) && (parseInt(iDelay) == 0))
						|| (parseInt(iDelay) < 0)) {
					iDelay = this.iDEFAULT_DELAY_MS;
				}
				if (FESR.getActive()) {
					this._fDelayedStartTime = now() + iDelay;
				}

				// Initialize/create the BusyIndicator if this has not been done yet.
				// This has to be done before calling '_showNowIfRequested' because within
				// '_init' the BusyIndicator attaches itself to the Popup's open event and
				// to keep the correct order of 'show -> _showNowIfRequested -> _onOpen'
				// the attaching has to happen earlier.
				// Otherwise if an application attaches itself to the open event, this listener
				// will be called before the BusyIndicator's open listener.
				if (!this.oDomRef) {
					this._init();
				}

				this.bOpenRequested = true;
				if (iDelay === 0) { // avoid async call when there is no delay
					this._showNowIfRequested();
				} else {
					setTimeout(this["_showNowIfRequested"].bind(this), iDelay);
				}
			}.bind(this));
		}
	};

	/**
	 * Immediately displays the BusyIndicator if the application has not called
	 * hide() yet.
	 *
	 * @private
	 */
	BusyIndicator._showNowIfRequested = function() {
		Log.debug("sap.ui.core.BusyIndicator._showNowIfRequested (bOpenRequested: " + this.bOpenRequested + ") at " + Date.now());

		// Do not open if the request has been canceled in the meantime
		if (!this.bOpenRequested) {
			return;
		}

		// The current scroll position of window needs to be passed as offset to Popup
		// to keep the scroll position of the window.
		// Otherwise, the BusyIndicator may be placed partically off-screen initially
		// and the browser scrolls itself up to shift the whole BusyIndicator into the
		// viewport which result as that the window loses its scroll position
		var iOffsetX = (window.scrollX === undefined ? window.pageXOffset : window.scrollX);
		var iOffsetY = (window.scrollY === undefined ? window.pageYOffset : window.scrollY);
		var sOffset = iOffsetX + " " + iOffsetY;

		this.bOpenRequested = false; // opening request is handled

		// Actually open the popup
		this.oPopup.open(0, Popup.Dock.LeftTop, Popup.Dock.LeftTop, document, sOffset);
	};

	/**
	 * Removes the BusyIndicator from the screen.
	 *
	 * @public
	 */
	BusyIndicator.hide = function() {
		Log.debug("sap.ui.core.BusyIndicator.hide at " + Date.now());
		if (this._fDelayedStartTime) {  // Implies fesr header active
			// The busy indicator shown duration d is calculated with:
			// d = "time busy indicator was hidden" - "time busy indicator was requested" - "busy indicator delay"
			var fBusyIndicatorShownDuration = now() - this._fDelayedStartTime;
			Interaction.addBusyDuration((fBusyIndicatorShownDuration > 0) ? fBusyIndicatorShownDuration : 0);
			delete this._fDelayedStartTime;
		}
		var bi = BusyIndicator; // Restore scope in case we are called with setTimeout or so...

		// When hide() is called, a potential delayed show-call has to be ignored.
		// Since there is no Core.detachInit(), this flag is used to reject an existing core-init handler
		if (BusyIndicator._bShowIsDelayed === true) {
			BusyIndicator._bShowIsDelayed = false;
		}

		bi.bOpenRequested = false;

		if (bi.oDomRef) { // only if the BusyIndicator was shown before!
			// setting the BusyIndicator's DOM to invisible is not
			// necessary here - it will be done by the Popup in 'oPopup.close(0)'

			var oAnimation = bi.oDomRef.querySelector(".sapUiLocalBusyIndicator");
			jQuery(oAnimation).removeClass("sapUiLocalBusyIndicatorFade");

			// allow an event handler to do something with the indicator
			this.fireClose({
				$Busy: this.oPopup._$()
			});

			bi.oPopup.close(0);
		}

	};


	/*  EVENT HANDLING */

	/**
	 * Registers a handler for the {@link #event:Open Open} event.
	 *
	 * When called, the context of the event handler (its <code>this</code>) will be bound to <code>oListener</code>
	 * if specified, otherwise it will be bound to <code>sap.ui.core.BusyIndicator</code>.
	 *
	 * @param {function}
	 *            fnFunction The function to be called, when the event occurs
	 * @param {object}
	 *            [oListener] Context object to call the event handler with; defaults to
	 *            <code>sap.ui.core.BusyIndicator</code>
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @public
	 */
	BusyIndicator.attachOpen = function(fnFunction, oListener) {
		this.attachEvent(BusyIndicator.M_EVENTS.Open, fnFunction, oListener);
		return this;
	};

	/**
	 * Unregisters a handler from the {@link #event:Open Open} event.
	 *
	 * @param {function}
	 *            fnFunction The callback function to unregister
	 * @param {object}
	 *            [oListener] Context object on which the given function had to be called
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @public
	 */
	BusyIndicator.detachOpen = function(fnFunction, oListener) {
		this.detachEvent(BusyIndicator.M_EVENTS.Open, fnFunction, oListener);
		return this;
	};

	/**
	 * Registers a handler for the {@link #event:Close Close} event.
	 *
	 * When called, the context of the event handler (its <code>this</code>) will be bound to <code>oListener</code>
	 * if specified, otherwise it will be bound to <code>sap.ui.core.BusyIndicator</code>.
	 *
	 * @param {function}
	 *            fnFunction The function to be called, when the event occurs
	 * @param {object}
	 *            [oListener] Context object to call the event handler with; defaults to
	 *            <code>sap.ui.core.BusyIndicator</code>
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @public
	 */
	BusyIndicator.attachClose = function(fnFunction, oListener) {
		this.attachEvent(BusyIndicator.M_EVENTS.Close, fnFunction, oListener);
		return this;
	};

	/**
	 * Unregisters a handler from the {@link #event:Close Close} event.
	 *
	 * @param {function}
	 *            fnFunction The callback function to unregister
	 * @param {object}
	 *            [oListener] Context object on which the given function had to be called
	 * @returns {this} Reference to <code>this</code> in order to allow method chaining
	 * @public
	 */
	BusyIndicator.detachClose = function(fnFunction, oListener) {
		this.detachEvent(BusyIndicator.M_EVENTS.Close, fnFunction, oListener);
		return this;
	};

	BusyIndicator.fireOpen = function(mParameters) {
		this.fireEvent(BusyIndicator.M_EVENTS.Open, mParameters);
	};

	BusyIndicator.fireClose = function(mParameters) {
		this.fireEvent(BusyIndicator.M_EVENTS.Close, mParameters);
	};

	return BusyIndicator;

}, /* bExport= */ true);
