Editor/Widgets/PropertySheet/PropertySheet.cs
using Sandbox;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Panelize;

public class PropertySheet : GridLayout
{
	public record Entry( SerializedProperty Property, Widget Control );
	public int Rows { get; private set; }
	public List<Entry> Entries { get; set; } = new();
	public Func<SerializedProperty, object> PropertyOrder { get; set; }
	public Func<string, object> GroupOrder { get; set; }
	Dictionary<string, PropertySheetGroup> groupWidgets = new();
	public PropertySheet()
	{
		Margin = new( 16, 8, 16, 8 );
		HorizontalSpacing = 10f;
		VerticalSpacing = 2f;
		SetColumnStretch( 1, 2 );
		SetMinimumColumnWidth( 0, 120 );
	}

	public void Rebuild()
	{
		groupWidgets.Clear();
		Clear(true);

		var orderProperties = Entries.ToArray().OrderBy( (e) => PropertyOrder?.Invoke(e.Property) );
		string[] groups = orderProperties.Select(p => p.Property.GroupName).Distinct().OrderBy(g => GroupOrder?.Invoke(g)).ToArray();

		foreach ( var group in groups )
		{
			AddGroup(group);
		}

		foreach((var prop, var control) in orderProperties)
		{
			CreatePropertyRow( prop, control );
		}
	}
	private void CreatePropertyRow(SerializedProperty prop, Widget control)
	{
		PropertySheetRow row = null;
		if ( control != null )
		{
			row = new( prop );
			row.Build( control );
		}
		else
		{
			row = PropertySheetRow.Create( prop );
		}

		if ( !row.IsValid() ) return;

		PropertySheetGroup group = GetOrCreateGroup( prop.GroupName );
		if ( group == null )
		{
			SetColumnStretch( 0 );
			AddCell( 0, Rows++, row );
		}
		else
		{
			group?.AddProperty( row );
		}
	}
	public void AddProperty(SerializedProperty prop, Widget control = null )
	{
		Entries.Add( new( prop, control ) );
	}
	public PropertySheetGroup GetOrCreateGroup(string name)
	{
		if ( string.IsNullOrEmpty( name ) ) return null;

		if ( !groupWidgets.TryGetValue( name, out PropertySheetGroup group ) )
		{
			group = AddGroup( name );
		}

		return group;
	}
	public PropertySheetGroup AddGroup(string name)
	{
		PropertySheetGroup group = new( name );
		AddCell( 0, Rows++, group, 2 );
		groupWidgets.Add(name, group);

		return group;
	}
	public void AddBuilder(Func<Widget> builder, string label = "", string group = "")
	{
		ArgumentNullException.ThrowIfNull( builder );

		if(!string.IsNullOrEmpty(group))
		{
			PropertySheetGroup groupWidget = GetOrCreateGroup( group );
			groupWidget.AddBuilder( builder, label );
		}
		else
		{
			Widget widget = builder();
			if(string.IsNullOrEmpty(label))
			{
				AddCell( 0, Rows++, widget, 2 );
			}
			else
			{
				PropertySheetLabel labelWidget = new()
				{ 
					Text = label 
				};

				AddCell(0, Rows, labelWidget );
				AddCell(1, Rows++, widget );
			}
		}
	}
}

internal class PropertySheetLabel : Widget
{
	public string Text { get; set; }
	public PropertySheetLabel()
	{
		MinimumHeight = Theme.RowHeight;
		MinimumWidth = 140f;
		HorizontalSizeMode = SizeMode.Flexible;
	}

	protected override void OnPaint()
	{
		base.OnPaint();

		Paint.Pen = Theme.ControlText.WithAlpha( Paint.HasMouseOver ? 1.0f : 0.6f );
		Paint.DrawText( LocalRect.Shrink( 8, 4 ), Text, TextFlag.LeftTop );
	}
}