/*!
 * 
		SAP UI development toolkit for HTML5 (SAPUI5)
		(c) Copyright 2009-2015 SAP SE. All rights reserved
	
 */

sap.ui.define(["sap/base/Log"], function (Log) {
	"use strict";

	/**
	 * Utility is a static class providing support functions for other Network Graph classes.
	 *
	 * @static
	 * @private
	 * @since 1.50
	 * @alias sap.suite.ui.commons.networkgraph.Utils
	 */
	var Utils = {},
		timeFlag = null;

	Utils.SEMANTIC_CLASS_NAME = {
		BACKGROUND: 'backgroundSemanticColor',
		'BACKGROUND-COLOR': 'backgroundSemanticColor',
		BORDER: 'borderSemanticColor',
		'BORDER-COLOR': 'borderSemanticColor',
		OUTLINE: 'outlineSemanticColor',
		'OUTLINE-COLOR': 'outlineSemanticColor',
		TEXT: 'textSemanticColor',
		FILL: 'fillSemanticColor',
		STROKE: 'strokeSemanticColor',
		COLOR: 'textSemanticColor'
	};

	/**
	 * Polyfill for array find in IE.
	 * @param {array} arr Array to search in
	 * @param {object} callback Predicate function used to identify the item that is searched for
	 * @param {object} thisArg Argument of the callback function
	 * @returns {object} Object that has been searched for, or undefined if none was found
	 */
	Utils.find = function (arr, callback, thisArg) {
		var i;
		if (typeof arr.find === "function") {
			return arr.find(callback, thisArg);
		} else {
			for (i = 0; i < arr.length; i++) {
				if (callback.call(thisArg, arr[i], i, arr)) {
					return arr[i];
				}
			}
			return undefined;
		}
	};

	/**
	 * Trims string to given character count.
	 * @param {string} sText Text to trim
	 * @param {number} iCount Number of maximum character text can have (minus "...")
	 * @returns {string} Trimmed string
	 */
	Utils.trimText = function (sText, iCount) {
		if (sText && sText.length > iCount) {
			return sText.substring(0, iCount) + " ... ";
		}

		return sText;
	};

	/**
	 * throttles the function call upto particular delay time
	 * (only the first call gets executed and the rest are delayed by the delay time)
	 * @param {function} mainFunction - function to be throttled
	 * @param {number} delay - delay time
	 * @returns {function} throttled function.
	 */
	Utils.throttle = (mainFunction, delay) => {
		return (...args) => {
			if (timeFlag === null) {
				mainFunction(...args);
				timeFlag = setTimeout(() => {
					timeFlag = null;
				}, delay);
			}
		};
	};

	/**
	 * Helper function to convert CSS outline to border for drag image elements.
	 * This is needed because some browsers don't capture outline in drag images.
	 *
	 * @param {HTMLElement} oElement - The DOM element to apply border to
	 * @param {CSSStyleDeclaration} oComputedStyle - The computed style of the element
	 * @param {string} [sBorderProperty="border"] - The CSS border property to set ("border", "borderBottom", etc.)
	 * @private
	 */
	var _applyOutlineAsBorder = function (oElement, oComputedStyle, sBorderProperty) {
		var sOutlineWidth = oComputedStyle.outlineWidth;
		if (sOutlineWidth && sOutlineWidth !== "0px") {
			var sBorderValue = sOutlineWidth + " " + oComputedStyle.outlineStyle + " " + oComputedStyle.outlineColor;
			oElement.style[sBorderProperty || "border"] = sBorderValue;
			oElement.style.outline = "none";
		}
	};

	/**
	 * Clones the inner div element and applies basic styles for drag image.
	 *
	 * @param {HTMLElement} oInnerDiv - Inner div element to clone
	 * @returns {HTMLElement} The cloned and styled node
	 * @private
	 */
	var _cloneAndStyleNode = function (oInnerDiv) {
		var oClonedNode = oInnerDiv.cloneNode(true);
		var oComputedStyle = window.getComputedStyle(oInnerDiv);

		// Apply computed styles to ensure the clone looks identical
		oClonedNode.style.width = oComputedStyle.width;
		oClonedNode.style.height = oComputedStyle.height;
		oClonedNode.style.position = "absolute";
		oClonedNode.style.top = "-9999px";
		oClonedNode.style.left = "-9999px";
		oClonedNode.style.pointerEvents = "none";

		// Convert outline to border for the drag image
		_applyOutlineAsBorder(oClonedNode, oComputedStyle);

		// Retain border-radius for rounded corners
		var sBorderRadius = oComputedStyle.borderRadius;
		if (sBorderRadius && sBorderRadius !== "0px") {
			oClonedNode.style.borderRadius = sBorderRadius;
		}

		// Retain background color
		var sBackgroundColor = oComputedStyle.backgroundColor;
		if (sBackgroundColor) {
			oClonedNode.style.backgroundColor = sBackgroundColor;
		}

		return oClonedNode;
	};

	/**
	 * Processes header element styling in the cloned node.
	 *
	 * @param {HTMLElement} oClonedNode - The cloned node to process
	 * @param {HTMLElement} oInnerDiv - Original inner div element
	 * @private
	 */
	var _processHeaderElement = function (oClonedNode, oInnerDiv) {
		var oOriginalHeader = oInnerDiv.querySelector(".sapSuiteUiCommonsNetworkGraphDivHeader");
		if (!oOriginalHeader) {
			return;
		}

		var oHeaderComputedStyle = window.getComputedStyle(oOriginalHeader);
		var oHeaderElement = oClonedNode.querySelector(".sapSuiteUiCommonsNetworkGraphDivHeader");

		if (!oHeaderElement) {
			return;
		}

		// Retain header background color
		if (oHeaderComputedStyle.backgroundColor) {
			oHeaderElement.style.backgroundColor = oHeaderComputedStyle.backgroundColor;
		}

		// Convert header outline to border
		_applyOutlineAsBorder(oHeaderElement, oHeaderComputedStyle, "borderBottom");

		// Process separate header color wrapper if it exists (skip if combined class)
		if (!oHeaderElement.classList.contains("sapSuiteUiCommonsNetworkGraphDivHeaderColor")) {
			var oOriginalHeaderColor = oInnerDiv.querySelector(".sapSuiteUiCommonsNetworkGraphDivHeaderColor");
			if (oOriginalHeaderColor) {
				var oHeaderColorComputedStyle = window.getComputedStyle(oOriginalHeaderColor);
				var oHeaderColorElement = oHeaderElement.querySelector(".sapSuiteUiCommonsNetworkGraphDivHeaderColor");

				if (oHeaderColorElement) {
					// Retain border
					if (oHeaderColorComputedStyle.border && oHeaderColorComputedStyle.border !== "0px none rgb(0, 0, 0)") {
						oHeaderColorElement.style.border = oHeaderColorComputedStyle.border;
					}

					// Retain border-radius
					if (oHeaderColorComputedStyle.borderRadius && oHeaderColorComputedStyle.borderRadius !== "0px") {
						oHeaderColorElement.style.borderRadius = oHeaderColorComputedStyle.borderRadius;
					}

					// Retain background color
					if (oHeaderColorComputedStyle.backgroundColor) {
						oHeaderColorElement.style.backgroundColor = oHeaderColorComputedStyle.backgroundColor;
					}

					// Retain color
					if (oHeaderColorComputedStyle.color) {
						oHeaderColorElement.style.color = oHeaderColorComputedStyle.color;
					}
				}
			}
		}
	};

	/**
	 * Creates a custom drag image for the node that includes borders, border-radius, and background colors.
	 * This is necessary because the default browser drag image doesn't capture CSS outline properties for few browsers e.g. Chrome.
	 *
	 * @param {sap.suite.ui.commons.networkgraph.Node} oNode - The node being dragged
	 * @param {Event} oBrowserEvent - The native browser drag event
	 * @returns {HTMLElement|null} The cloned node attached to DOM, or null if creation failed. Caller is responsible for cleanup.
	 */
	Utils.createCustomDragImage = function (oNode, oBrowserEvent) {
		if (!oNode || !oBrowserEvent || !oBrowserEvent.dataTransfer) {
			return null;
		}

		// Get the graph instance to check the ghost image mode
		var oGraph = oNode.getParent();
		if (!oGraph) {
			return null;
		}

		try {
			var oDomRef = oNode.getDomRef();
			if (!oDomRef) {
				return null;
			}

			var oInnerDiv = oDomRef.querySelector(".sapSuiteUiCommonsNetworkGraphDivInner");
			if (!oInnerDiv) {
				return null;
			}

			// Clone and apply basic styles
			var oClonedNode = _cloneAndStyleNode(oInnerDiv);

			// Process header element styling
			_processHeaderElement(oClonedNode, oInnerDiv);

			// Attach to DOM temporarily
			document.body.appendChild(oClonedNode);

			// Set drag image with correct offset
			var oRect = oInnerDiv.getBoundingClientRect();
			var iOffsetX = oBrowserEvent.clientX - oRect.left;
			var iOffsetY = oBrowserEvent.clientY - oRect.top;
			oBrowserEvent.dataTransfer.setDragImage(oClonedNode, iOffsetX, iOffsetY);

			// Return the cloned node for cleanup by caller
			return oClonedNode;
		} catch (oError) {
			Log.warning("Utils: Failed to create custom drag image", oError);
			return null;
		}
	};

	/**
	 * Retains the scroll position of the graph after DOM operations.
	 * This method ensures that the viewport remains stable after operations like
	 * drag-drop, connection creation, or other DOM manipulations.
	 *
	 * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The graph control
	 * @param {number} scrollLeft - The horizontal scroll position to maintain
	 * @param {number} scrollTop - The vertical scroll position to maintain
	 */
	Utils.retainScrollPosition = function (oGraph, scrollLeft, scrollTop) {
		if (!oGraph || !oGraph.$scroller || !oGraph.$scroller[0]) {
			return;
		}

		const scrollerElement = oGraph.$scroller[0];

		// Use requestAnimationFrame for better performance and timing
		requestAnimationFrame(() => {
			try {
				scrollerElement.scrollLeft = scrollLeft;
				scrollerElement.scrollTop = scrollTop;

				// Fallback verification - ensure the scroll position was actually set
				if (Math.abs(scrollerElement.scrollLeft - scrollLeft) > 1 ||
					Math.abs(scrollerElement.scrollTop - scrollTop) > 1) {

					// If the scroll position wasn't set correctly, try again after DOM updates
					setTimeout(() => {
						scrollerElement.scrollLeft = scrollLeft;
						scrollerElement.scrollTop = scrollTop;
					}, 10);
				}
			} catch (error) {
				Log.error("retainScrollPosition: Error setting scroll position - " + error.message, "sap.suite.ui.commons.networkgraph.Utils");
			}
		});
	};

	/**
	 * Preserves the scroll position of the graph by scheduling restoration after DOM updates.
	 * This is a convenience wrapper that handles the setTimeout + restoration logic.
	 *
	 * @param {sap.suite.ui.commons.networkgraph.Graph} oGraph - The graph control
	 * @param {number} scrollLeft - The horizontal scroll position to restore
	 * @param {number} scrollTop - The vertical scroll position to restore
	 * @param {number} [iDelay=0] - Delay in milliseconds before restoring scroll position
	 * @public
	 */
	Utils.preserveScrollPosition = function (oGraph, scrollLeft, scrollTop, iDelay) {
		if (!oGraph || !oGraph.$scroller || !oGraph.$scroller[0]) {
			return;
		}

		const delay = typeof iDelay === "number" ? iDelay : 0;

		// Schedule restoration after DOM updates
		setTimeout(() => {
			Utils.retainScrollPosition(oGraph, scrollLeft, scrollTop);
		}, delay);
	};

	// RTL swap map for connection types: mirrors Left↔Right side labels.
	Utils.mRTLSwap = {
		"LeftToLeft": "RightToRight",
		"LeftToRight": "RightToLeft",
		"RightToLeft": "LeftToRight",
		"RightToRight": "LeftToLeft"
	};

	return Utils;
}, true);
