Editor/Inspector/AlignmentSelectorWidget.cs
using Editor;
using Sandbox.UI;
using System;
using System.Collections.Generic;

namespace XGUI.XGUIEditor
{
	/// <summary>
	/// A widget that displays and controls panel edge alignments similar to the Windows Forms Anchor property
	/// </summary>
	public class AlignmentSelectorWidget : PropertyEditor
	{
		// The panel and node being edited
		private Panel _targetPanel;
		private MarkupNode _targetNode;

		// UI elements
		private Widget _container;
		private Editor.Button _leftBtn;
		private Editor.Button _topBtn;
		private Editor.Button _rightBtn;
		private Editor.Button _bottomBtn;

		// Current alignment state
		private PanelAlignment _alignment = new PanelAlignment();

		// Boolean indicating if the position mode is absolute (only enable alignment in absolute mode)
		private bool _isAbsolutePosition;

		public AlignmentSelectorWidget( string propertyName, string displayName, bool isStyle = false )
			: base( propertyName, displayName, isStyle )
		{
		}

		/// <summary>
		/// Sets the current target panel and node to modify
		/// </summary>
		public void SetTarget( Panel panel, MarkupNode node, Dictionary<string, string> styles )
		{
			_targetPanel = panel;
			_targetNode = node;

			// Extract position mode and update enable state
			_isAbsolutePosition = styles.TryGetValue( "position", out var position ) && position == "absolute";
			RootWidget.Enabled = _isAbsolutePosition;

			// Update alignment state from styles
			_alignment = PanelAlignment.FromStyles( styles );
			UpdateButtonVisuals();
		}

		public override Widget CreateUI( Layout layout )
		{
			// Create a container widget for the editor, following the standard pattern
			var rootWidget = new Widget( null );
			rootWidget.Layout = Layout.Row();
			rootWidget.Layout.Margin = 0;
			rootWidget.Layout.Spacing = 2;

			// Add label like other editors
			rootWidget.Layout.Add( new Editor.Label( DisplayName ) { FixedWidth = 100 } );

			// Create the alignment control container
			_container = new Widget( null );
			rootWidget.Layout.Add( _container, 1 );
			_container.Layout = Layout.Column();
			_container.Layout.Spacing = 4;

			// Top row (with spacers for centering)
			var topRow = new Widget( null );
			_container.Layout.Add( topRow );
			topRow.Layout = Layout.Row();
			topRow.Layout.Spacing = 4;

			// Add first spacer (fixed width)
			var topLeftSpacer = new Widget( null );
			topLeftSpacer.FixedWidth = 60;
			topRow.Layout.Add( topLeftSpacer );

			// Add top button
			_topBtn = CreateAlignmentButton( "arrow_upward" );
			_topBtn.ToolTip = "Anchor to top edge";
			_topBtn.Clicked += () => ToggleAlignment( "top", !_alignment.Top );
			topRow.Layout.Add( _topBtn );

			// Add second spacer (fixed width - must match first spacer)
			var topRightSpacer = new Widget( null );
			topRightSpacer.FixedWidth = 60;
			topRow.Layout.Add( topRightSpacer );

			// Middle row
			var middleRow = new Widget( null );
			_container.Layout.Add( middleRow );
			middleRow.Layout = Layout.Row();
			middleRow.Layout.Spacing = 8; // More spacing in middle row

			// Left button
			_leftBtn = CreateAlignmentButton( "arrow_back" );
			_leftBtn.ToolTip = "Anchor to left edge";
			_leftBtn.Clicked += () => ToggleAlignment( "left", !_alignment.Left );
			middleRow.Layout.Add( _leftBtn );

			// Center panel (fixed width to maintain spacing)
			var centerPanel = new Widget( null );
			centerPanel.FixedWidth = 48;
			centerPanel.MinimumSize = new Vector2( 24, 24 );
			centerPanel.ToolTip = "Panel representation";
			centerPanel.SetStyles( "background-color: #444444; border: 1px solid #666666;" );
			middleRow.Layout.Add( centerPanel );

			// Right button
			_rightBtn = CreateAlignmentButton( "arrow_forward" );
			_rightBtn.ToolTip = "Anchor to right edge";
			_rightBtn.Clicked += () => ToggleAlignment( "right", !_alignment.Right );
			middleRow.Layout.Add( _rightBtn );

			// Bottom row (with spacers for centering)
			var bottomRow = new Widget( null );
			_container.Layout.Add( bottomRow );
			bottomRow.Layout = Layout.Row();
			bottomRow.Layout.Spacing = 4;

			// Add first spacer (fixed width)
			var bottomLeftSpacer = new Widget( null );
			bottomLeftSpacer.FixedWidth = 60;
			bottomRow.Layout.Add( bottomLeftSpacer );

			// Add bottom button
			_bottomBtn = CreateAlignmentButton( "arrow_downward" );
			_bottomBtn.ToolTip = "Anchor to bottom edge";
			_bottomBtn.Clicked += () => ToggleAlignment( "bottom", !_alignment.Bottom );
			bottomRow.Layout.Add( _bottomBtn );

			// Add second spacer (fixed width - must match first spacer)
			var bottomRightSpacer = new Widget( null );
			bottomRightSpacer.FixedWidth = 60;
			bottomRow.Layout.Add( bottomRightSpacer );

			// Add the root widget to the parent layout
			layout.Add( rootWidget );

			// Set the RootWidget property
			RootWidget = rootWidget;

			// Initial button states
			UpdateButtonVisuals();

			// Default to disabled until initialized with absolute positioning
			RootWidget.Enabled = false;

			return rootWidget;
		}

		/// <summary>
		/// Creates a button for alignment selection
		/// </summary>
		private Editor.Button CreateAlignmentButton( string text )
		{
			var btn = new Editor.Button( null );
			btn.Text = text;
			btn.MinimumSize = new Vector2( 24, 24 );
			btn.MaximumSize = new Vector2( 24, 24 );
			return btn;
		}

		/// <summary>
		/// Updates the visual state of buttons based on current alignment
		/// </summary>
		private void UpdateButtonVisuals()
		{
			// Ensure buttons have appropriate styling
			UpdateButtonStyle( _leftBtn, _alignment.Left );
			UpdateButtonStyle( _topBtn, _alignment.Top );
			UpdateButtonStyle( _rightBtn, _alignment.Right );
			UpdateButtonStyle( _bottomBtn, _alignment.Bottom );
		}

		/// <summary>
		/// Updates the visual style of a button based on its active state
		/// </summary>
		private void UpdateButtonStyle( Editor.Button btn, bool isActive )
		{
			if ( btn == null ) return;
			btn.SetStyles( $"Button {{ font-family: 'Material Icons'; padding: 0; border: 0; font-size: 13px;}}" );
			btn.Tint = isActive ? Theme.Primary : Color.FromRgb( 0x5b5d62 );
		}

		/// <summary>
		/// Toggles an alignment direction and updates the UI and node styles
		/// </summary>
		private void ToggleAlignment( string edge, bool newState )
		{
			// Don't allow removing both horizontal alignments
			if ( edge == "left" && !newState && !_alignment.Right )
				return;

			// Don't allow removing both vertical alignments
			if ( edge == "top" && !newState && !_alignment.Bottom )
				return;

			// Update alignment state
			switch ( edge )
			{
				case "left": _alignment.Left = newState; break;
				case "top": _alignment.Top = newState; break;
				case "right": _alignment.Right = newState; break;
				case "bottom": _alignment.Bottom = newState; break;
			}

			// Update button visuals
			UpdateButtonVisuals();

			// Apply the changes to the styles
			ApplyAlignmentChanges( edge, newState );
		}

		/// <summary>
		/// Applies alignment changes to the panel styles
		/// </summary>
		private void ApplyAlignmentChanges( string edge, bool newState )
		{
			if ( _targetNode == null || _targetPanel == null || !_isAbsolutePosition )
				return;

			// Current styles for checking/updating
			var styles = GetCurrentStyles();

			if ( newState )
			{
				// Calculate edge position based on current box/rect values
				string cssValue = CalculateEdgePosition( edge );

				// Apply the position to the style
				_targetNode.TryModifyStyle( edge, cssValue );

				// Special handling for horizontal and vertical stretching:
				// If both left+right set, remove width to enable stretching
				if ( edge == "left" || edge == "right" )
				{
					bool hasLeft = edge == "left" ? true : styles.ContainsKey( "left" );
					bool hasRight = edge == "right" ? true : styles.ContainsKey( "right" );

					// Both edges are now anchored, remove width to allow stretching
					if ( hasLeft && hasRight )
					{
						_targetNode.TryModifyStyle( "width", null );
					}
				}

				// Similarly for top+bottom with height
				if ( edge == "top" || edge == "bottom" )
				{
					bool hasTop = edge == "top" ? true : styles.ContainsKey( "top" );
					bool hasBottom = edge == "bottom" ? true : styles.ContainsKey( "bottom" );

					// Both edges are now anchored, remove height to allow stretching
					if ( hasTop && hasBottom )
					{
						_targetNode.TryModifyStyle( "height", null );
					}
				}

				NotifyValueChanged( new AlignmentChangeInfo { Edge = edge, Value = cssValue } );
			}
			else
			{
				// Remove the style property
				_targetNode.TryModifyStyle( edge, null );

				// When removing an alignment, we may need to add back width/height
				// based on the current box size so the element doesn't collapse

				// Handle horizontal case
				if ( edge == "left" || edge == "right" )
				{
					bool hasLeft = edge == "left" ? false : styles.ContainsKey( "left" );
					bool hasRight = edge == "right" ? false : styles.ContainsKey( "right" );

					// We no longer have both edges anchored, so set explicit width
					if ( !(hasLeft && hasRight) && !styles.ContainsKey( "width" ) )
					{
						string widthValue = $"{Math.Max( Math.Round( _targetPanel.Box.Rect.Width ), 10 )}px";
						_targetNode.TryModifyStyle( "width", widthValue );
					}
				}

				// Handle vertical case
				if ( edge == "top" || edge == "bottom" )
				{
					bool hasTop = edge == "top" ? false : styles.ContainsKey( "top" );
					bool hasBottom = edge == "bottom" ? false : styles.ContainsKey( "bottom" );

					// We no longer have both edges anchored, so set explicit height
					if ( !(hasTop && hasBottom) && !styles.ContainsKey( "height" ) )
					{
						string heightValue = $"{Math.Max( Math.Round( _targetPanel.Box.Rect.Height ), 10 )}px";
						_targetNode.TryModifyStyle( "height", heightValue );
					}
				}

				NotifyValueChanged( new AlignmentChangeInfo { Edge = edge, Value = null } );
			}
		}

		/// <summary>
		/// Helper method to get current styles as a dictionary
		/// </summary>
		private Dictionary<string, string> GetCurrentStyles()
		{
			if ( _targetNode == null || !_targetNode.Attributes.TryGetValue( "style", out var styleStr ) )
				return new Dictionary<string, string>();

			// Parse style string into dictionary
			var styles = new Dictionary<string, string>( StringComparer.OrdinalIgnoreCase );
			var declarations = styleStr.Split( ';', StringSplitOptions.RemoveEmptyEntries );

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

			return styles;
		}

		/// <summary>
		/// Calculates the position value for an edge based on current panel layout
		/// </summary>
		private string CalculateEdgePosition( string edge )
		{
			if ( _targetPanel == null || _targetPanel.Parent == null )
				return "0px";

			float value = 0;
			var rect = _targetPanel.Box.Rect;
			var parentRect = _targetPanel.Parent.Box.Rect;

			switch ( edge )
			{
				case "left":
					value = rect.Left - parentRect.Left;
					break;

				case "top":
					value = rect.Top - parentRect.Top;
					break;

				case "right":
					// Right edge is calculated as parent width - (panel left + panel width)
					value = parentRect.Width - (rect.Left - parentRect.Left + rect.Width);
					break;

				case "bottom":
					// Bottom edge is calculated as parent height - (panel top + panel height)
					value = parentRect.Height - (rect.Top - parentRect.Top + rect.Height);
					break;
			}

			return $"{Math.Max( Math.Round( value ), 0 )}px";
		}

		// This is just a dummy implementation to satisfy the abstract method
		public override void SetValueSilently( string value )
		{
			// Nothing to do here
		}

		// Class to pass alignment change info when notifying value changed
		public class AlignmentChangeInfo
		{
			public string Edge { get; set; }
			public string Value { get; set; }
		}
	}
}