XGUI/Elements/ColourPickerControl.cs
using Sandbox.UI;
using System;
using System.Globalization;
namespace XGUI;

public class ColourPickerControl : Panel
{
	private Color _color;
	public Color CurrentColor
	{
		get => _color;
		set
		{
			if ( _color != value )
			{
				_color = value;
				UpdateVisuals(); // Update both display and inputs
				ValueChanged?.Invoke( value );
			}
		}
	}

	public event Action<Color> ValueChanged;

	private Panel _colorDisplayButton; // Clickable display
	private TextEntry _rInput;
	private TextEntry _gInput;
	private TextEntry _bInput;
	private TextEntry _aInput;

	private XGUIPopup _dropdownPanel;
	private bool _isDropdownOpen = false;

	public ColourPickerControl()
	{
		SetClass( "colourpicker", true );

		// RGB Inputs Container
		var rgbContainer = Add.Panel( "rgb-container" );

		// R Input
		_rInput = AddChild<TextEntry>( "" );
		_rInput.Numeric = true;
		//_rInput.OnTextEdited += ( string s ) => OnRgbInputChanged( s, 0 );
		_rInput.AddEventListener( "onblur", () => OnRgbInputChanged( _rInput.Text, 0 ) ); // Ensure update on losing focus
		_rInput.SetClass( "floatinput", true );

		// G Input
		_gInput = AddChild<TextEntry>( "" );
		_gInput.Numeric = true;
		//_gInput.OnTextEdited += ( string s ) => OnRgbInputChanged( s, 1 );
		_gInput.AddEventListener( "onblur", () => OnRgbInputChanged( _gInput.Text, 1 ) );
		_gInput.SetClass( "floatinput", true );

		// B Input
		_bInput = AddChild<TextEntry>( "" );
		_bInput.Numeric = true;
		//_bInput.OnTextEdited += ( string s ) => OnRgbInputChanged( s, 2 );
		_bInput.AddEventListener( "onblur", () => OnRgbInputChanged( _bInput.Text, 2 ) );
		_bInput.SetClass( "floatinput", true );

		// A Input (Optional)
		_aInput = AddChild<TextEntry>( "" );
		_aInput.Numeric = true;
		//_aInput.OnTextEdited += ( string s ) => OnRgbInputChanged( s, 3 );
		_aInput.AddEventListener( "onblur", () => OnRgbInputChanged( _aInput.Text, 3 ) );
		_aInput.SetClass( "floatinput", true );

		// Clickable Color Display
		_colorDisplayButton = Add.Panel( "colourwidget" );
		_colorDisplayButton.AddEventListener( "onclick", ToggleDropdown );

		// Dropdown Panel (Initially Hidden)
		_dropdownPanel = AddChild<XGUIPopup>( "colour-popup-panel" );
		_dropdownPanel.PopupSource = _colorDisplayButton;
		SetupDropdownContent( _dropdownPanel ); // Populate the dropdown

		UpdateVisuals(); // Set initial state
	}

	private void UpdateVisuals()
	{
		_colorDisplayButton.Style.BackgroundColor = CurrentColor;

		// Update text inputs without triggering their change events excessively
		if ( float.TryParse( _rInput.Text, out float rVal ) && rVal != CurrentColor.r )
			_rInput.Text = (CurrentColor.r * 1).ToString();
		else if ( string.IsNullOrWhiteSpace( _rInput.Text ) ) // Handle empty initial state
			_rInput.Text = (CurrentColor.r * 1).ToString();

		if ( float.TryParse( _gInput.Text, out float gVal ) && gVal != CurrentColor.g )
			_gInput.Text = (CurrentColor.g * 1).ToString();
		else if ( string.IsNullOrWhiteSpace( _gInput.Text ) )
			_gInput.Text = (CurrentColor.g * 1).ToString();

		if ( float.TryParse( _bInput.Text, out float bVal ) && bVal != CurrentColor.b )
			_bInput.Text = (CurrentColor.b * 1).ToString();
		else if ( string.IsNullOrWhiteSpace( _bInput.Text ) )
			_bInput.Text = (CurrentColor.b * 1).ToString();

		if ( float.TryParse( _aInput.Text, out float aVal ) && aVal != CurrentColor.a )
			_aInput.Text = (CurrentColor.a * 1).ToString();
		else if ( string.IsNullOrWhiteSpace( _aInput.Text ) )
			_aInput.Text = (CurrentColor.a * 1).ToString();

		// Update dropdown visibility
		_dropdownPanel.Style.Display = _isDropdownOpen ? DisplayMode.Flex : DisplayMode.None;
	}

	private void OnRgbInputChanged( string textValue, int componentIndex ) // 0=R, 1=G, 2=B
	{
		if ( float.TryParse( textValue, NumberStyles.Any, CultureInfo.InvariantCulture, out float floatVal ) )
		{
			// Clamp and convert to byte

			Color newColor = CurrentColor;
			bool changed = false;

			switch ( componentIndex )
			{
				case 0: // R
					if ( newColor.r != floatVal )
					{
						newColor = newColor.WithRed( floatVal );
						changed = true;
					}
					break;
				case 1: // G
					if ( newColor.g != floatVal )
					{
						newColor = newColor.WithGreen( floatVal );
						changed = true;
					}
					break;
				case 2: // B
					if ( newColor.b != floatVal )
					{
						newColor = newColor.WithBlue( floatVal );
						changed = true;
					}
					break;
				case 3: // A
					if ( newColor.a != floatVal )
					{
						newColor = newColor.WithAlpha( floatVal );
						changed = true;
					}
					break;
			}

			if ( changed )
			{
				CurrentColor = newColor; // This will trigger ValueChanged via the setter
			}
			// Even if not changed, ensure the input reflects the clamped byte value
			switch ( componentIndex )
			{
				case 0: _rInput.Text = floatVal.ToString(); break;
				case 1: _gInput.Text = floatVal.ToString(); break;
				case 2: _bInput.Text = floatVal.ToString(); break;
				case 3: _aInput.Text = floatVal.ToString(); break;
			}

		}
		// Optional: Handle invalid input (e.g., clear it, reset to old value, show error)
	}

	private void ToggleDropdown()
	{
		_isDropdownOpen = !_isDropdownOpen;
		if ( _dropdownPanel.Parent == this )
		{
			_dropdownPanel.Parent = FindRootPanel();
			_dropdownPanel.Style.Left = _colorDisplayButton.Box.Rect.Left;
			_dropdownPanel.Style.Top = _colorDisplayButton.Box.Rect.Bottom;
		}
		else
		{
			_dropdownPanel.Parent = this;
		}
		UpdateVisuals(); // Update display style of dropdown
	}

	private void SelectColorFromDropdown( Color chosenColor )
	{
		CurrentColor = chosenColor; // This updates visuals and triggers ValueChanged  
	}

	// --- Dropdown Content ---
	// Replace this with a more sophisticated picker eventually!
	private void SetupDropdownContent( Panel parent )
	{
		parent.Style.FlexDirection = FlexDirection.Column; // Or Row/Grid as needed 
		parent.SetClass( "panel", true );
		var widget = parent.AddChild<ColourWidget>();
		var grid = parent.Add.Panel( "swatch-grid" );

		widget.OnChange += SelectColorFromDropdown;

		// TODO: Add a proper color wheel / saturation / value picker here later
	}

	protected override int BuildHash() => HashCode.Combine( _color, _isDropdownOpen );
}