Editor/Widgets/SuiDetailsFields.cs
using System;
using System.Collections.Generic;
using Editor;
using Sandbox;
using SboxUiDesigner.Runtime;

namespace SboxUiDesigner.EditorUi.Widgets;

// =============================================================================
//  Details panel field widgets — all 100% custom paint, share the dark
//  rounded look of the search inputs in Palette/Hierarchy.
//
//  Common style:
//    background-color: rgb(20,20,19)         (#141413, same as search)
//    border: 1px solid rgba(255,255,255,0.06)
//    border-radius: 3px
//    color: rgb(220,224,230)
//    font-size: 11px
// =============================================================================

internal static class SuiFieldStyle
{
	public const string Input =
		"background-color: rgb(20,20,19);" +
		"border: 1px solid rgba(255,255,255,0.06);" +
		"border-radius: 3px;" +
		"color: rgb(220,224,230);" +
		// Same formula as the search inputs in Palette/Hierarchy which
		// vertical-center automatically: zero top/bottom padding + a tall
		// enough height for the font to breathe.
		"padding: 0 8px;" +
		"font-size: 11px;";

	public const string SubLabel = "color: rgb(140,145,153); font-size: 10px;";
}

/// <summary>Single-line text field — wraps a styled <see cref="LineEdit"/>.</summary>
public sealed class SuiTextField : Widget
{
	private readonly LineEdit _input;
	public event Action<string> ValueCommitted;

	public string Text
	{
		get => _input?.Text ?? "";
		set { if ( _input != null ) _input.Text = value ?? ""; }
	}

	public SuiTextField( Widget parent = null ) : base( parent )
	{
		FixedHeight = 26;
		SetStyles( "background-color: transparent; border: none;" );
		Layout = Layout.Row();
		Layout.Margin = 0;

		_input = new LineEdit( this );
		_input.FixedHeight = 26;
		_input.SetStyles( SuiFieldStyle.Input );
		_input.EditingFinished += () => ValueCommitted?.Invoke( _input.Text );
		Layout.Add( _input, 1 );
	}
}

/// <summary>
/// Number field — styled <see cref="LineEdit"/> with numeric parsing on commit.
/// </summary>
public sealed class SuiNumberField : Widget
{
	private readonly LineEdit _input;
	public event Action<float> ValueCommitted;

	public float Value
	{
		get => float.TryParse( _input?.Text, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var v ) ? v : 0f;
		set { if ( _input != null ) _input.Text = value.ToString( "0.###", System.Globalization.CultureInfo.InvariantCulture ); }
	}

	public SuiNumberField( Widget parent = null ) : base( parent )
	{
		FixedHeight = 26;
		SetStyles( "background-color: transparent; border: none;" );
		Layout = Layout.Row();
		Layout.Margin = 0;

		_input = new LineEdit( this );
		_input.FixedHeight = 26;
		_input.SetStyles( SuiFieldStyle.Input );
		_input.EditingFinished += () =>
		{
			var v = Value;
			ValueCommitted?.Invoke( v );
		};
		Layout.Add( _input, 1 );
	}
}

/// <summary>
/// Vector field — N labeled number boxes in a row. Use for X/Y, X/Y/W/H,
/// L/T/R/B etc. Each component has a small uppercase letter label to its left.
/// </summary>
public sealed class SuiVectorField : Widget
{
	private readonly List<(string label, SuiNumberField field)> _components = new();
	public event Action<int, float> ComponentCommitted;

	public SuiVectorField( string[] componentLabels, Widget parent = null ) : base( parent )
	{
		FixedHeight = 26;
		SetStyles( "background-color: transparent; border: none;" );
		Layout = Layout.Row();
		Layout.Margin = 0;
		Layout.Spacing = 6;

		for ( int i = 0; i < componentLabels.Length; i++ )
		{
			var idx = i;
			var lab = componentLabels[i];

			var pair = new Widget( this );
			pair.SetStyles( "background-color: transparent; border: none;" );
			pair.Layout = Layout.Row();
			pair.Layout.Margin = 0;
			pair.Layout.Spacing = 4;

			var letter = new SuiVectorLabel( lab );
			letter.FixedWidth = 12;
			pair.Layout.Add( letter );

			var f = new SuiNumberField();
			f.Parent = pair;
			f.ValueCommitted += v => ComponentCommitted?.Invoke( idx, v );
			pair.Layout.Add( f, 1 );

			_components.Add( (lab, f) );
			Layout.Add( pair, 1 );
		}
	}

	public void SetComponent( int index, float value )
	{
		if ( index < 0 || index >= _components.Count ) return;
		_components[index].field.Value = value;
	}
}

internal sealed class SuiVectorLabel : Widget
{
	public string Text { get; }

	public SuiVectorLabel( string text, Widget parent = null ) : base( parent )
	{
		Text = text ?? "";
		FixedHeight = 26;
		SetStyles( "background-color: transparent; border: none;" );
	}

	protected override void OnPaint()
	{
		Paint.SetDefaultFont( 10 );
		Paint.SetPen( new Color( 140 / 255f, 145 / 255f, 153 / 255f ) );
		Paint.DrawText( new Rect( 0, 0, Width, Height ), Text, TextFlag.Center );
	}
}

/// <summary>Boolean toggle — checkbox-style on the left, "On"/"Off" label.</summary>
public sealed class SuiToggleField : Widget
{
	public bool Checked { get; private set; }
	public event Action<bool> ValueChanged;

	public SuiToggleField( bool initial = false, Widget parent = null ) : base( parent )
	{
		Checked = initial;
		FixedHeight = 26;
		Cursor = CursorShape.Finger;
		SetStyles( SuiFieldStyle.Input );
	}

	public void SetChecked( bool value, bool fireEvent = false )
	{
		if ( Checked == value ) return;
		Checked = value;
		Update();
		if ( fireEvent ) ValueChanged?.Invoke( Checked );
	}

	protected override void OnPaint()
	{
		// Checkbox square on the left.
		var box = new Rect( 6, (Height - 14) / 2f, 14, 14 );
		var boxBg = Checked ? new Color( 0f, 0.55f, 1f, 1f ) : new Color( 30 / 255f, 30 / 255f, 29 / 255f );
		Paint.SetBrush( boxBg );
		Paint.SetPen( Color.White.WithAlpha( 0.18f ) );
		Paint.DrawRect( box, 2f );

		if ( Checked )
		{
			// Checkmark.
			Paint.SetPen( Color.White, 2f );
			Paint.DrawLine( new Vector2( box.Left + 3, box.Top + 7 ), new Vector2( box.Left + 6, box.Top + 10 ) );
			Paint.DrawLine( new Vector2( box.Left + 6, box.Top + 10 ), new Vector2( box.Left + 11, box.Top + 4 ) );
		}

		// Label "On" / "Off".
		Paint.SetDefaultFont( 10 );
		Paint.SetPen( new Color( 220 / 255f, 224 / 255f, 230 / 255f ) );
		Paint.DrawText( new Rect( 26, 0, Width - 28, Height ), Checked ? "On" : "Off", TextFlag.LeftCenter );
	}

	protected override void OnMousePress( MouseEvent e )
	{
		if ( !e.LeftMouseButton ) return;
		Checked = !Checked;
		// Callback may rebuild the parent (Refresh) and destroy this widget.
		// Update only AFTER, and only if we still exist.
		ValueChanged?.Invoke( Checked );
		if ( IsValid ) Update();
	}
}

/// <summary>
/// Dropdown field — paint-only widget showing the current value + chevron.
/// Click opens a Menu populated by <see cref="SetOptions"/>.
/// </summary>
public sealed class SuiDropdownField : Widget
{
	private string _value = "";
	private List<(string label, Action select)> _options = new();
	public event Action<string> ValueSelected;

	public string Value
	{
		get => _value;
		set { _value = value ?? ""; Update(); }
	}

	public SuiDropdownField( Widget parent = null ) : base( parent )
	{
		FixedHeight = 26;
		Cursor = CursorShape.Finger;
		SetStyles( SuiFieldStyle.Input );
	}

	public void SetOptions( IEnumerable<string> labels )
	{
		_options.Clear();
		foreach ( var l in labels )
		{
			var captured = l;
			_options.Add( (l, () =>
			{
				_value = captured;
				// Invoke callback FIRST — it may rebuild parent (Refresh)
				// and destroy this widget. Update only if we survived.
				ValueSelected?.Invoke( captured );
				if ( IsValid ) Update();
			}
			) );
		}
	}

	protected override void OnPaint()
	{
		Paint.SetDefaultFont( 10 );
		Paint.SetPen( new Color( 220 / 255f, 224 / 255f, 230 / 255f ) );
		Paint.DrawText( new Rect( 8, 0, Width - 24, Height ), _value, TextFlag.LeftCenter );

		// Chevron.
		Paint.SetPen( new Color( 165 / 255f, 172 / 255f, 182 / 255f ), 1.5f );
		var cx = Width - 12f;
		var cy = Height / 2f;
		Paint.DrawLine( new Vector2( cx - 4, cy - 2 ), new Vector2( cx, cy + 2 ) );
		Paint.DrawLine( new Vector2( cx, cy + 2 ), new Vector2( cx + 4, cy - 2 ) );
	}

	protected override void OnMousePress( MouseEvent e )
	{
		if ( !e.LeftMouseButton || _options.Count == 0 ) return;
		var menu = new Menu( this );
		foreach ( var (label, sel) in _options )
		{
			menu.AddOption( label, null, sel );
		}
		// Open the menu anchored to the dropdown widget itself (just below it)
		// instead of at cursor — which was placing it wherever the user clicked,
		// often outside the panel.
		menu.OpenAt( ScreenPosition + new Vector2( 0, Height ) );
	}
}

// SuiColorField removed — SuiColorSwatchField (in its own file) provides the
// same purpose with more features (alpha checkerboard, clear button, copy/
// paste context menu) and is what AddColorRow uses.


/// <summary>
/// Anchor picker button — shows a tiny 3x3 grid icon highlighting the current
/// anchor + the anchor name + chevron. Click opens the anchor popup.
/// </summary>
public sealed class SuiAnchorPickerButton : Widget
{
	public SuiAnchor Anchor { get; private set; }
	public event Action Clicked;

	public SuiAnchorPickerButton( SuiAnchor initial, Widget parent = null ) : base( parent )
	{
		Anchor = initial;
		FixedHeight = 26;
		Cursor = CursorShape.Finger;
		SetStyles( SuiFieldStyle.Input );
	}

	public void SetAnchor( SuiAnchor a ) { Anchor = a; Update(); }

	protected override void OnPaint()
	{
		// 3x3 grid mini icon at left.
		float gridSize = 12f;
		float gx = 6f;
		float gy = (Height - gridSize) / 2f;
		var dotColor = new Color( 165 / 255f, 172 / 255f, 182 / 255f );
		var activeDot = new Color( 0f, 0.55f, 1f );
		Paint.SetPen( dotColor, 1f );
		for ( int row = 0; row < 3; row++ )
		{
			for ( int col = 0; col < 3; col++ )
			{
				bool isActive = IsAnchorCellActive( Anchor, col, row );
				Paint.SetBrush( isActive ? activeDot : dotColor.WithAlpha( 0.45f ) );
				Paint.ClearPen();
				var dotRect = new Rect( gx + col * 5, gy + row * 5, 3, 3 );
				Paint.DrawRect( dotRect, 1f );
			}
		}

		// Label.
		Paint.SetDefaultFont( 10 );
		Paint.SetPen( new Color( 220 / 255f, 224 / 255f, 230 / 255f ) );
		Paint.DrawText( new Rect( 28, 0, Width - 44, Height ), AnchorPrettyName( Anchor ), TextFlag.LeftCenter );

		// Chevron.
		Paint.SetPen( dotColor, 1.5f );
		var cx = Width - 12f;
		var cy = Height / 2f;
		Paint.DrawLine( new Vector2( cx - 4, cy - 2 ), new Vector2( cx, cy + 2 ) );
		Paint.DrawLine( new Vector2( cx, cy + 2 ), new Vector2( cx + 4, cy - 2 ) );
	}

	protected override void OnMousePress( MouseEvent e )
	{
		if ( e.LeftMouseButton ) Clicked?.Invoke();
	}

	private static bool IsAnchorCellActive( SuiAnchor a, int col, int row )
	{
		// 0=left/top 1=center/middle 2=right/bottom
		switch ( a )
		{
			case SuiAnchor.TopLeft:      return col == 0 && row == 0;
			case SuiAnchor.TopCenter:    return col == 1 && row == 0;
			case SuiAnchor.TopRight:     return col == 2 && row == 0;
			case SuiAnchor.MiddleLeft:   return col == 0 && row == 1;
			case SuiAnchor.MiddleCenter: return col == 1 && row == 1;
			case SuiAnchor.MiddleRight:  return col == 2 && row == 1;
			case SuiAnchor.BottomLeft:   return col == 0 && row == 2;
			case SuiAnchor.BottomCenter: return col == 1 && row == 2;
			case SuiAnchor.BottomRight:  return col == 2 && row == 2;

			// Stretch Horizontal — middle row, both ends.
			case SuiAnchor.StretchHorizontal:
				return row == 1 && (col == 0 || col == 2);

			// Stretch Vertical — middle column, both ends.
			case SuiAnchor.StretchVertical:
				return col == 1 && (row == 0 || row == 2);

			// Fill — four corners light up.
			case SuiAnchor.Stretch:
				return (col == 0 || col == 2) && (row == 0 || row == 2);

			default: return false;
		}
	}

	private static string AnchorPrettyName( SuiAnchor a ) => a switch
	{
		SuiAnchor.TopLeft => "Top Left",
		SuiAnchor.TopCenter => "Top Center",
		SuiAnchor.TopRight => "Top Right",
		SuiAnchor.MiddleLeft => "Middle Left",
		SuiAnchor.MiddleCenter => "Middle Center",
		SuiAnchor.MiddleRight => "Middle Right",
		SuiAnchor.BottomLeft => "Bottom Left",
		SuiAnchor.BottomCenter => "Bottom Center",
		SuiAnchor.BottomRight => "Bottom Right",
		SuiAnchor.Stretch => "Fill (Stretch)",
		SuiAnchor.StretchHorizontal => "Stretch Horizontal",
		SuiAnchor.StretchVertical => "Stretch Vertical",
		_ => a.ToString(),
	};
}

/// <summary>Browse-asset button — folder icon + transparent bg, hover tint.</summary>
public sealed class SuiBrowseButton : Widget
{
	public event Action Clicked;
	private bool _hover, _pressed;
	public string Icon { get; set; } = "folder_open";

	public SuiBrowseButton( Widget parent = null ) : base( parent )
	{
		FixedWidth = 28;
		FixedHeight = 26;
		Cursor = CursorShape.Finger;
		SetStyles( SuiFieldStyle.Input );
	}

	protected override void OnPaint()
	{
		if ( _pressed )
		{
			Paint.SetBrushAndPen( Color.White.WithAlpha( 0.10f ) );
			Paint.DrawRect( LocalRect, 3f );
		}
		else if ( _hover )
		{
			Paint.SetBrushAndPen( Color.White.WithAlpha( 0.06f ) );
			Paint.DrawRect( LocalRect, 3f );
		}
		Paint.SetPen( new Color( 220 / 255f, 224 / 255f, 230 / 255f ) );
		Paint.DrawIcon( LocalRect, Icon, 14, TextFlag.Center );
	}

	protected override void OnMouseEnter() { _hover = true; Update(); }
	protected override void OnMouseLeave() { _hover = false; _pressed = false; Update(); }
	protected override void OnMousePress( MouseEvent e ) { if ( e.LeftMouseButton ) { _pressed = true; Update(); } }
	protected override void OnMouseReleased( MouseEvent e )
	{
		if ( _pressed && e.LeftMouseButton )
		{
			_pressed = false;
			Update();
			if ( _hover ) Clicked?.Invoke();
		}
	}
}