Editor/XGUIView.cs
using Editor;
using Sandbox;
using Sandbox.UI;
using System;
using System.Collections.Generic;
using System.Linq;
using XGUI.XGUIEditor;

namespace XGUI;

public partial class XGUIView : SceneRenderingWidget
{
	XGUIRootPanel Panel;
	XGUIRootComponent _rootComponent;

	public Window Window;
	public Panel WindowContent;

	// Add delegate for selection callback
	public Action<Panel> OnElementSelected { get; set; }
	public XGUIDesigner OwnerDesigner;

	public XGUIView()
	{
		MinimumSize = 300;
		Scene = new Scene();

		var cam = Scene.CreateObject();

		Camera = cam.AddComponent<CameraComponent>();
		_rootComponent = cam.AddComponent<XGUIRootComponent>();
		_rootComponent.MouseUnlocked = false;
		Scene.GameTick();
		Scene.GameTick();
		Scene.GameTick();
		Panel = _rootComponent.XGUIPanel;

	}

	public void CreateBlankWindow()
	{
		Window = new Window();
		Panel.AddChild( Window );

		WindowContent = new Panel();
		WindowContent.AddClass( "window-content" );
		Window.AddChild( WindowContent );

		Window.FocusWindow();
	}

	public void Setup()
	{
	}

	public override void OnDestroyed()
	{
		base.OnDestroyed();
		CleanUp();
	}
	public void CleanUp()
	{
		Scene.Destroy();
		Panel.Delete();
	}
	int mouseIconHash = 0;
	protected override void PreFrame()
	{
		base.PreFrame();

		Scene.GameTick();

		var mousePosLocal = Editor.Application.CursorPosition - ScreenPosition;
		var hash = HashCode.Combine( mousePosLocal, SelectedPanel, isDragging );
		if ( hash != mouseIconHash )
		{
			mouseIconHash = hash;
			Cursor = CursorShape.None;

			if ( isDragging ) Cursor = CursorShape.DragMove;

			int handleIndex = GetHandleAtPosition( mousePosLocal );
			if ( ShouldOnlyHorizontalResize( SelectedPanel ) )
			{
				UpdateResizeCursor( handleIndex, verticalenabled: false, diagonalenabled: false );
			}
			else
			{
				UpdateResizeCursor( handleIndex );
			}
		}
	}

	// Resize handle tracking
	private bool _isDraggingHandle = false;
	private int _activeHandle = -1; // -1 = none, 0-7 = handles clockwise from top-left
	private Vector2 _dragStartPos;
	private Rect _originalRect;
	public void OnOverlayDraw()
	{
		if ( SelectedPanel != null )
		{
			// Draw selection outline
			Paint.ClearPen();
			Paint.SetPen( Color.Cyan.WithAlpha( 0.8f ), 1.0f, PenStyle.Dot );
			Paint.DrawRect( SelectedPanel.Box.Rect );

			// Draw resize handles
			if ( ShouldOnlyHorizontalResize( SelectedPanel ) )
			{
				DrawResizeHandles( SelectedPanel.Box.Rect, verticalenabled: false, diagonalenabled: false );
			}
			else
			{
				DrawResizeHandles( SelectedPanel.Box.Rect );
			}
			DrawSnappingGuides();
		}
	}


	public Panel SelectedPanel;

	public Panel DraggingPanel;

	private bool isDragging = false;
	private bool isMouseDown = false;
	private Vector2 dragOffset;
	private Vector2 dragStartPos;
	protected override void OnMousePress( MouseEvent e )
	{
		base.OnMousePress( e );
		if ( e.Accepted )
			return;
		isMouseDown = true;

		ResizeMousePress( e );

		if ( e.Accepted )
			return;
		if ( e.Button != MouseButtons.Left )
			return;

		// Check if we're in nested selection mode (multiple clicks at the same position)
		CheckNestedSelectionMode( e.LocalPosition );

		TryInteractAtPosition( Window, e );

		if ( e.Accepted )
			return;

		// Find the panel under the mouse (excluding WindowContent itself)
		Panel hovered = FindPanelAtPosition( Window, e.LocalPosition, skipSelf: true, selectNested: _isNestedSelectionMode ) as Panel;

		if ( hovered == WindowContent )
		{
			hovered = Window;
		}
		Select( hovered );
	}

	private void Select( Panel panel )
	{
		SelectedPanel = panel;
		OnElementSelected?.Invoke( panel );
	}

	protected override void OnMouseMove( MouseEvent e )
	{
		base.OnMouseMove( e );

		if ( isMouseDown && !_isDraggingHandle && !isDragging )
		{
			var result = FindPanelAtPosition( WindowContent, e.LocalPosition, skipSelf: true );
			Panel hovered = result as Panel;
			if ( hovered != null )
			{
				// Start dragging
				isDragging = true;
				DraggingPanel = hovered;
				dragStartPos = e.LocalPosition;
				dragOffset = e.LocalPosition - hovered.Box.Rect.Position;
			}
		}

		if ( !isDragging )
		{
			ResizeMouseMove( e );
		}

		if ( isDragging && DraggingPanel != null )
		{
			// 1. Check for reparenting (hovering over a panel and within margin)
			Panel dropTarget = FindPanelAtPosition( WindowContent, e.LocalPosition, skipSelf: true, skip: DraggingPanel ) as Panel;
			if ( dropTarget != null && dropTarget != DraggingPanel.Parent && dropTarget != DraggingPanel )
			{
				var rect = dropTarget.Box.Rect;
				float margin = 4f;
				if ( e.LocalPosition.x > rect.Left + margin && e.LocalPosition.x < rect.Right - margin &&
					e.LocalPosition.y > rect.Top + margin && e.LocalPosition.y < rect.Bottom - margin )
				{
					return; // Don't allow reparenting if within margin, this will be reparenting
				}
			}

			// 2. If absolute, update position
			if ( DraggingPanel.ComputedStyle?.Position == PositionMode.Absolute )
			{
				DraggingPanel.Style.Position = PositionMode.Absolute;
				var newPosition = e.LocalPosition - dragOffset;
				newPosition = ApplySnappingToPosition( newPosition );
				newPosition -= DraggingPanel.Parent.Box.Rect.Position; // Adjust for WindowContent position

				// Apply snapping to nearby elements and parent container

				var node = OwnerDesigner.LookupNodeByPanel( DraggingPanel );
				if ( node != null )
				{
					// Get the panel's alignment settings
					var alignment = GetPanelAlignment( DraggingPanel );
					//Log.Info( $"Alignment Left: {alignment.Left}" );
					//Log.Info( $"Alignment Top: {alignment.Top}" );
					//Log.Info( $"Alignment Right: {alignment.Right}" );
					//Log.Info( $"Alignment Bottom: {alignment.Bottom}" );
					//Log.Info( $"New Position: {newPosition}" );

					// Clear any existing positioning styles first
					if ( alignment.Left )
						node.TryModifyStyle( "left", $"{newPosition.x}px" );
					else
						node.TryModifyStyle( "left", null );

					if ( alignment.Top )
						node.TryModifyStyle( "top", $"{newPosition.y}px" );
					else
						node.TryModifyStyle( "top", null );

					if ( alignment.Right && DraggingPanel.Parent != null )
					{
						// Calculate right value as: parent_width - (left + width)
						float parentWidth = DraggingPanel.Parent.Box.Rect.Width;
						float panelWidth = DraggingPanel.Box.Rect.Width;
						float rightValue = parentWidth - (newPosition.x + panelWidth);
						node.TryModifyStyle( "right", $"{rightValue}px" );
					}
					else
					{
						node.TryModifyStyle( "right", null );
					}

					if ( alignment.Bottom && DraggingPanel.Parent != null )
					{
						// Calculate bottom value as: parent_height - (top + height)
						float parentHeight = DraggingPanel.Parent.Box.Rect.Height;
						float panelHeight = DraggingPanel.Box.Rect.Height;
						float bottomValue = parentHeight - (newPosition.y + panelHeight);
						node.TryModifyStyle( "bottom", $"{bottomValue}px" );
					}
					else
					{
						node.TryModifyStyle( "bottom", null );
					}

					OwnerDesigner.ForceUpdate( false );
					DraggingPanel.Style.Dirty();
					DraggingPanel = OwnerDesigner.LookupPanelByNode( node );
					if ( DraggingPanel == null )
					{
						Log.Warning( "DraggingPanel is null after UI rebuild!" );
					}
					return;
				}
			}

			// 3. Rearranging among siblings (auto-layout)
			else
			{
				var parent = DraggingPanel.Parent;
				if ( parent != null )
				{
					var siblings = parent.Children.OfType<Panel>().Where( p => p != DraggingPanel ).ToList();
					Panel targetSibling = null;
					bool insertAfter = false;
					FlexDirection flexDirection = parent.ComputedStyle?.FlexDirection ?? FlexDirection.Column; // Default to column


					foreach ( var sibling in siblings )
					{
						var rect = sibling.Box.Rect;

						if ( flexDirection == FlexDirection.Row || flexDirection == FlexDirection.RowReverse )
						{
							// Horizontal
							if ( e.LocalPosition.x > rect.Left && e.LocalPosition.x < rect.Right )
							{
								if ( e.LocalPosition.x < rect.Left + rect.Width / 2 )
								{
									targetSibling = sibling;
									insertAfter = (flexDirection == FlexDirection.RowReverse) ^ false;
									break;
								}
								else if ( e.LocalPosition.x >= rect.Left + rect.Width / 2 )
								{
									targetSibling = sibling;
									insertAfter = (flexDirection == FlexDirection.RowReverse) ^ true;
									break;
								}
							}
						}
						else
						{
							// Vertical (column or column-reverse)
							if ( e.LocalPosition.y > rect.Top && e.LocalPosition.y < rect.Bottom )
							{
								if ( e.LocalPosition.y < rect.Top + rect.Height / 2 )
								{
									targetSibling = sibling;
									insertAfter = (flexDirection == FlexDirection.ColumnReverse) ^ false;
									break;
								}
								else if ( e.LocalPosition.y >= rect.Top + rect.Height / 2 )
								{
									targetSibling = sibling;
									insertAfter = (flexDirection == FlexDirection.ColumnReverse) ^ true;
									break;
								}
							}
						}
					}

					if ( targetSibling != null && targetSibling != DraggingPanel )
					{
						Log.Info( $"Rearranging {DraggingPanel} before {targetSibling}" );

						// Update MarkupNode tree as well
						var parentNode = OwnerDesigner.LookupNodeByPanel( parent );
						var draggedNode = OwnerDesigner.LookupNodeByPanel( DraggingPanel );
						var targetSiblingNode = OwnerDesigner.LookupNodeByPanel( targetSibling );
						Log.Info( $"Parent: {parentNode} aka {parent}, InsertBefore: {targetSiblingNode}, Dragged: {draggedNode}" );
						if ( parentNode != null && draggedNode != null && targetSiblingNode != null )
						{
							var nodeChildrenList = parentNode.Children as List<MarkupNode>;
							if ( nodeChildrenList != null )
							{
								nodeChildrenList.Remove( draggedNode );
								int nodeInsertIndex = nodeChildrenList.IndexOf( targetSiblingNode );
								if ( insertAfter )
									nodeInsertIndex++;
								nodeChildrenList.Insert( nodeInsertIndex, draggedNode );
								draggedNode.Parent = parentNode;
							}
						}

						OwnerDesigner.ForceUpdate( false );
						DraggingPanel.Style.Dirty();
						// Restore DraggingPanel after UI rebuild
						DraggingPanel = OwnerDesigner.LookupPanelByNode( draggedNode );
						if ( DraggingPanel == null )
						{
							Log.Warning( "DraggingPanel is null after UI rebuild!" );
						}
					}
				}
			}
		}
	}

	protected override void OnMouseReleased( MouseEvent e )
	{
		base.OnMouseReleased( e );
		isMouseDown = false;
		ResizeMouseReleased( e );

		_isSnappedX = false;
		_isSnappedY = false;

		if ( isDragging && DraggingPanel != null )
		{
			// 1. Check for reparenting (hovering over a panel and within margin)
			var result = FindPanelAtPosition( WindowContent, e.LocalPosition, skipSelf: true, skip: DraggingPanel );
			Panel dropTarget = result as Panel;
			if ( dropTarget != null && dropTarget != DraggingPanel.Parent && dropTarget != DraggingPanel )
			{
				var rect = dropTarget.Box.Rect;
				float margin = 6f;
				if ( e.LocalPosition.x > rect.Left + margin && e.LocalPosition.x < rect.Right - margin &&
					e.LocalPosition.y > rect.Top + margin && e.LocalPosition.y < rect.Bottom - margin )
				{
					// --- Reparent ---
					DraggingPanel.Parent = null;
					dropTarget.AddChild( DraggingPanel );

					// --- Update MarkupNode tree as well ---
					var draggedNode = OwnerDesigner.LookupNodeByPanel( DraggingPanel );
					var newParentNode = OwnerDesigner.LookupNodeByPanel( dropTarget );
					if ( draggedNode != null && newParentNode != null && draggedNode.Parent != null )
					{
						draggedNode.Parent.Children.Remove( draggedNode );
						newParentNode.Children.Add( draggedNode );
						draggedNode.Parent = newParentNode;

						OwnerDesigner.ForceUpdate();
						DraggingPanel.Style.Dirty();
						isDragging = false;
						DraggingPanel = null;
						return;
					}
				}
			}

			// 2. Rearranging among siblings (auto-layout) -- only on release!


			isDragging = false;
			DraggingPanel = null;
		}
	}


	private void TryInteractAtPosition( Panel root, MouseEvent e )
	{
		var pos = e.LocalPosition;
		// recursively look for a tab button anywhere in the root panel 
		foreach ( var child in root.Children )
		{
			if ( child != null )
			{
				if ( child is Sandbox.UI.Button tabButton && tabButton.Parent?.Parent is TabContainer tabContainer )
				{
					if ( tabButton.Box.Rect.IsInside( pos ) )
					{
						//find the <tab> node (not the button's node) that the button belongs to

						Log.Info( tabContainer );

						// find tab entry in the tab container
						var tabentry = tabContainer.Tabs.FirstOrDefault( x => x.Button == tabButton );

						var page = tabentry.Page;
						Log.Info( page );

						// select, else click if already selected
						if ( page == SelectedPanel )
						{
							// Click event
							tabButton.Click();
							e.Accepted = true;
						}
						else
						{
							// Select the owner <tab> node
							Select( page );
							e.Accepted = true;
						}

						return;
					}
				}
				else if ( child is Panel panel )
				{
					TryInteractAtPosition( panel, e );
				}
			}
		}
	}

	// Utility: Recursively find the hovering panel at a position
	private object FindPanelAtPosition( Panel root, Vector2 pos, bool skipSelf = false, Panel skip = null, bool selectNested = false )
	{
		if ( root == null || root == skip ) return null;

		// Store all panels at this position that have corresponding markup nodes
		List<Panel> panelsAtPosition = selectNested ? new List<Panel>() : null;
		bool foundAny = false;

		// First check all children (reverse order to prioritize elements on top)
		foreach ( var child in root.Children.OfType<Panel>().Reverse() )
		{
			var found = FindPanelAtPosition( child, pos, skipSelf: false, skip: skip, selectNested: selectNested );

			if ( found != null )
			{
				foundAny = true;

				if ( selectNested )
				{
					if ( found is List<Panel> foundList )
					{
						panelsAtPosition.AddRange( foundList );
					}
					else if ( found is Panel foundPanel )
					{
						panelsAtPosition.Add( foundPanel );
					}
				}
				else
				{
					return found; // Return the first (deepest) child found
				}
			}
		}

		// Then check if this panel contains the position AND has a corresponding markup node
		if ( !skipSelf && root.Box.Rect.IsInside( pos ) )
		{
			// Only consider panels that have a corresponding markup node
			bool hasMarkupNode = OwnerDesigner != null && OwnerDesigner.LookupNodeByPanel( root ) != null;
			// Is visible (example, if the panel is a child of a tab that isnt selected)
			bool isVisible = root.IsVisible;

			if ( hasMarkupNode && isVisible )
			{
				if ( selectNested )
				{
					panelsAtPosition.Add( root );
				}
				else if ( !foundAny )
				{
					return root; // Return this panel if no children were found
				}
			}
		}

		// Return the list for nested selection mode, or null for normal mode
		if ( selectNested && panelsAtPosition.Count > 0 )
		{
			return panelsAtPosition;
		}

		return null;
	}

	// Keep track of which nested panel we're selecting at a specific position
	private Dictionary<Vector2, int> _panelSelectionIndices = new Dictionary<Vector2, int>();

	// Helper to handle multiple clicks at the same position
	private Vector2 _lastClickPosition;
	private bool _isNestedSelectionMode = false;

	// Add this to your OnMousePress method before calling FindPanelAtPosition
	private void CheckNestedSelectionMode( Vector2 position )
	{
		// If clicking at the same spot, enable nested selection mode
		if ( Vector2.Distance( _lastClickPosition, position ) < 1.0f )
		{
			_isNestedSelectionMode = true;
		}
		else
		{
			_isNestedSelectionMode = false;
			_panelSelectionIndices.Clear();
		}

		_lastClickPosition = position;
	}

	private PanelAlignment GetPanelAlignment( Panel panel )
	{
		if ( panel == null )
			return new PanelAlignment();

		var node = OwnerDesigner.LookupNodeByPanel( panel );
		if ( node == null )
			return new PanelAlignment();

		string styleAttr = node.Attributes.GetValueOrDefault( "style", "" );
		var styles = ParseStyleAttribute( styleAttr );

		return XGUIEditor.PanelAlignment.FromStyles( styles );
	}

	// Helper method to parse style strings
	private Dictionary<string, string> ParseStyleAttribute( string styleString )
	{
		var styles = new Dictionary<string, string>( System.StringComparer.OrdinalIgnoreCase );
		if ( string.IsNullOrWhiteSpace( styleString ) )
			return styles;

		var declarations = styleString.Split( ';', StringSplitOptions.RemoveEmptyEntries );

		foreach ( var declaration in declarations )
		{
			var parts = declaration.Split( ':', 2 );
			if ( parts.Length == 2 )
			{
				string property = parts[0].Trim();
				string value = parts[1].Trim();

				if ( !string.IsNullOrEmpty( property ) )
				{
					styles[property] = value;
				}
			}
		}
		return styles;
	}

}