Editor/ModelViewer/ClothingWidget.cs
using Editor;
using Editor.NodeEditor;
using Sandbox;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using static Sandbox.Clothing;
using static Sandbox.ClothingContainer;

[CustomEditor( typeof( ModelViewerClothingDresser ) )]
public class ClothingWidget : ComponentEditorWidget
{
	ListView listView = new ListView();

	Layout Icons;
	Layout SubCatergoryControl;
	List<ClothingData> clothingData = new();

	List<Clothing> ClothingList { get; set; } = new();

	string Sort = "Skin";
	string SubCategorySort = "";
	string SearchString = "";
	bool ShowEnabled = false;
	private List<ClothingData> filteredData;

	static SerializedObject Target { get; set; }
	public ControlWidget ClothingItemList { get; set; }
	
	private readonly string[] customSortOrder = new string[] { "Skin", "Facial", "Hair", "Hat", "Tops","Bottoms","Gloves","Footwear","None" };
	private readonly string[] customSortIconOrder = new string[] { "emoji_people", "sentiment_very_satisfied", "face", "add_reaction", "personal_injury", "front_hand", "airline_seat_legroom_reduced", "do_not_step", "question_mark" };
	
	struct ClothingData
	{
		public string Name;
		public Texture Texture;
		public Pixmap Pixmap;
		public ClothingCategory Category;
		public string SubCategory;
		public Clothing ClothingItem;
		public bool IsInClothingList;
	}

	public ClothingWidget( SerializedObject property ) : base( property )
	{
		SetSizeMode( SizeMode.Default, SizeMode.Default );

		Target = property;

		Layout = Layout.Column();
		Layout.Spacing = 8;
		Layout.Margin = 8;
		SubCatergoryControl = Layout.Row();
		SubCatergoryControl.Spacing = 3;

		_ = PopulateClothingDataAsync();

		var sortFilter = Layout.Add( new SegmentedControl() );
		var sortTypes = Enum.GetValues(typeof( Clothing.ClothingCategory ));
		foreach ( var sortType in customSortOrder )
		{
			sortFilter.AddOption(sortType);
			//for funny icons
			//sortFilter.AddOption(sortType,customSortIconOrder[sortType.Length]);
		}
		
		//Override the order of the sort types probably nasty
		Sort = customSortOrder[0];
		sortFilter.OnSelectedChanged = ( selection ) =>
		{
			SubCategorySort = "";
			Sort = selection;
			UpdateClothingDisplay();
			UpdateSubCategory();

			if(selection == "Hair")
			{
				ShowHairGradient( true );
			}
			else
			{
				ShowHairGradient( false );
			}

			if(selection == "Facial")
			{
				ShowBeardGradient( true );
			}
			else
			{
				ShowBeardGradient( false );
			}
		};
		
		Layout.AddSpacingCell( 2 );
		Layout.Add( SubCatergoryControl );

		var searchBar = new Widget( this );
		searchBar.Layout = Layout.Row();

		var search = new LineEdit();
		search.MinimumHeight = Theme.RowHeight;
		search.PlaceholderText = "Search...";
		search.TextEdited += ( text ) =>
		{
			SearchString = text;
			UpdateClothingDisplay();
		};

		var button = new Button.Clear( "Clear", "clear" );
		button.Pressed += () =>
		{
			search.Text = "";
			SearchString = "";
			UpdateClothingDisplay();
		};
		
		var reset = new Button( "Reset Clothing", "restart_alt" );
		reset.Pressed += () =>
		{
			ResetClothing();
		};

		searchBar.Layout.Add( search );
		searchBar.Layout.Add( button );
		searchBar.Layout.AddStretchCell();
		searchBar.Layout.Add( reset );

		Layout.Add( searchBar );

		Layout.AddSpacingCell( 2 );
		
		Layout.AddSeparator();
		Layout.AddSpacingCell( 4 );
		
		Icons = Layout.AddRow( 1 );
		Icons.Spacing = 3;
		Icons.Add( listView );
		
		
		Layout.AddSpacingCell( 4 );
		Layout.AddSeparator();

		var bottom = Layout.AddRow( 1 );

		var boofl = new ControlSheet();
		boofl.AddProperty(this, x => x.ShowEnabled );
		var updated = new Button();
		updated.Text = "Update";
		updated.Pressed += () =>
		{
			UpdateClothingDisplay();
		};
		//boofl.Text = "Show Enabled Clothing";
		//boofl.Value = ShowEnabled;
		//boofl.Pressed += () =>
		//{
		//	ShowEnabled = !boofl.Value;
		//	UpdateClothingDisplay();
		//};

		var citizen = Layout.Column();

		citizen.Add( new Label( "Citizen" ) );
		citizen.AddSpacingCell( 5 );
		var source = Target.GetProperty( "Source" );
		var sourcemodel = ControlWidget.Create( source );
		sourcemodel.MaximumWidth = 200;
		citizen.Add( sourcemodel );

		bottom.Add( citizen );
		bottom.AddStretchCell();
		bottom.Add( boofl );
		bottom.Add( updated );

		Layout.Add( bottom );

		UpdateClothingDisplay();
		UpdateSubCategory();
	}

	private async Task PopulateClothingDataAsync()
	{
		var clothingres = await Task.Run( () => ResourceLibrary.GetAll<Clothing>() );
		await Task.Delay( 5 );
		ClothingList = Target.GetProperty( "ClothingList" ).GetValue<List<Clothing>>();
	
		foreach ( var item in clothingres )
		{
			if ( !item.IsValid() ) continue;
			if ( item.Icon.ToString() != null && !string.IsNullOrEmpty( item.Icon.Path ))
			{
				var entry = new ClothingEntry( item );
				var data = new ClothingData
				{
					Name = item.Title,
					Texture = Texture.Load( Editor.FileSystem.Content, item.Icon.Path ),
					Pixmap = Pixmap.FromFile( Editor.FileSystem.Content.GetFullPath( item.Icon.Path ) ),
					Category = item.Category,
					ClothingItem = item,
					SubCategory = item.SubCategory,
					IsInClothingList = ClothingList.Contains( item )
				};

				clothingData.Add( data );		
			}
		}

		UpdateSubCategory();
	
	}

	//This feels ugly but whatever.

	//Hair Tint
	private Layout colum;
	private Label hairlabel;
	private SerializedProperty gradient;
	private ControlWidget gradientControl;
	private Layout colorrow;
	private FloatSlider colorslider;

	private void ShowHairGradient ( bool show )
	{
		if ( show )
		{
			if( gradientControl != null) return;

			colum = Layout.Column();
			hairlabel = new Label( "Hair Tint" );
			colum.Add( hairlabel );
			gradient = Target.GetProperty( "HairTintGradient" );
			gradientControl = ControlWidget.Create( gradient );
			colum.Add( gradientControl );
			colorrow = Layout.Row();
			colorslider = new FloatSlider( this );
			colorslider.Value = Target.GetProperty( "HairTintValue" ).GetValue<float>();
			colorslider.OnValueEdited += () =>
			{
				UpdateHairColour( colorslider.Value / 100 );
			};
			colorrow.Add( colorslider );
			colum.Add( colorrow );
			Layout.Add( colum );
		}
		else
		{
			// remove the hair tint controls
			colum?.Destroy();
			colum = null;
			hairlabel?.Destroy();
			hairlabel = null;
			gradientControl?.Destroy();
			gradientControl = null;
			colorrow?.Destroy();
			colorrow = null;
			colorslider?.Destroy();
			colorslider = null;
		}
	}

	//Beard Tint
	private Layout beardcolum;
	private Label beardlabel;
	private SerializedProperty beardgradient;
	private ControlWidget beardgradientControl;
	private Layout beardcolorrow;
	private FloatSlider beardcolorslider;
	private FloatSlider beardcolorsliderControl;
	
	private void ShowBeardGradient ( bool show )
	{
		if ( show )
		{
			if( beardgradientControl != null) return;

			beardcolum = Layout.Column();
			beardlabel = new Label( "Beard Tint" );
			beardcolum.Add( beardlabel );
			beardgradient = Target.GetProperty( "BeardTintGradient" );
			beardgradientControl = ControlWidget.Create( beardgradient );
			beardcolum.Add( beardgradientControl );
			beardcolorrow = Layout.Row();
			beardcolorslider = new FloatSlider( this );
			beardcolorslider.Value = Target.GetProperty( "BeardTintValue" ).GetValue<float>();
			beardcolorslider.OnValueEdited += () =>
			{
				UpdateBeardColour( beardcolorslider.Value / 100 );
			};
			beardcolorrow.Add( beardcolorslider );
			beardcolum.Add( beardcolorrow );
			Layout.Add( beardcolum );
		}
		else
		{
			// remove the beard tint controls
			beardcolum?.Destroy();
			beardcolum = null;
			beardlabel?.Destroy();
			beardlabel = null;
			beardgradientControl?.Destroy();
			beardgradientControl = null;
			beardcolorrow?.Destroy();
			beardcolorrow = null;
			beardcolorslider?.Destroy();
			beardcolorslider = null;
		}
	}

	private void UpdateHairColour(float color)
	{
		var gradient = Target.GetProperty( "HairTintGradient" );
		var hairTintGradient = gradient.GetValue<Gradient>();
		var hairTint = hairTintGradient.Evaluate( color );
		var hairTintProperty = Target.GetProperty( "HairTint" );
		var hairTintValueProperty = Target.GetProperty( "HairTintValue" );
		hairTintValueProperty.SetValue( color * 100 );
		hairTintProperty.SetValue( hairTint );
	}

	private void UpdateBeardColour(float color)
	{
		var gradient = Target.GetProperty( "BeardTintGradient" );
		var beardTintGradient = gradient.GetValue<Gradient>();
		var beardTint = beardTintGradient.Evaluate( color );
		var beardTintProperty = Target.GetProperty( "BeardTint" );
		var beardTintValueProperty = Target.GetProperty( "BeardTintValue" );
		beardTintValueProperty.SetValue( color * 100 );
		beardTintProperty.SetValue( beardTint );
	}

	private void ShowEnabledClothing()
	{
		var enabledData = clothingData.Where( c => ClothingList.Contains( c.ClothingItem ) ).ToList();
		listView = CreateListView( enabledData );
	}

	private void UpdateSubCategory( )
	{
		SubCatergoryControl.Clear( true );

		var sortedData = ResourceLibrary.GetAll<Clothing>()
			.Where( x => x.Category == filteredData.FirstOrDefault().Category && x.Parent == null )
			.OrderBy( x => x.SubCategory )
			.GroupBy( x => x.SubCategory?.Trim() ?? string.Empty );

		var subcatFilter = new SegmentedControl();

		foreach ( var subCat in sortedData )
		{
			subcatFilter.AddOption( subCat.Key );
		}

		subcatFilter.OnSelectedChanged = ( selection ) =>
		{
			SubCategorySort = selection;
			UpdateClothingDisplay();
		};

		SubCatergoryControl.Add( subcatFilter );
	}

	private void UpdateClothingDisplay()
	{
		if ( ShowEnabled )
		{
			ShowEnabledClothing();
			return;
		}
		if ( string.IsNullOrWhiteSpace( SearchString  ) )
		{
			filteredData = clothingData.Where( c => c.Category.ToString() == Sort ).ToList();
			
			if ( !string.IsNullOrWhiteSpace( SubCategorySort ))
			{
				filteredData = filteredData.Where( c => c.SubCategory == SubCategorySort ).ToList();
			}
		}
		else
		{
			filteredData = clothingData.Where( c => c.Name.Contains( SearchString, StringComparison.OrdinalIgnoreCase ) ).ToList();
		}

		listView = CreateListView( filteredData );
	}

	private ListView CreateListView( List<ClothingData> data  )
	{
		listView.SetItems( data.Cast<object>() );
		listView.ItemSize = new Vector2( 96, 96 );
		listView.ItemAlign = Sandbox.UI.Align.SpaceBetween;
		listView.OnPaintOverride += () => PaintListBackground( listView );
		listView.ItemPaint = PaintBrushItem;
		listView.MinimumHeight = 400;

		listView.ItemSelected = ( item ) =>
		{
			if ( item is ClothingData data )
			{
				AddOrRemoveClothing( data );
				listView.Update();
				listView.UpdateIfDirty();
			}		
		};
		return listView;
	}

	private void ResetClothing()
	{
		var clothing = Target.GetProperty( "ClothingList" );
		var clothingList = clothing.GetValue<List<Clothing>>();

		clothingList.Clear();

		// Update the IsInClothingList property for all items
		for ( int i = 0; i < clothingData.Count; i++ )
		{
			var clothData = clothingData[i];
			clothData.IsInClothingList = clothingList.Contains( clothData.ClothingItem );
			clothingData[i] = clothData;
		}

		// Refresh the UI to reflect the changes
		UpdateClothingDisplay();
	}

	private void AddOrRemoveClothing( ClothingData data )
	{
		var clothing = Target.GetProperty( "ClothingList" );
		var clothingList = clothing.GetValue<List<Clothing>>();

		if ( clothingList.Contains( data.ClothingItem ) )
		{
			clothingList.Remove( data.ClothingItem );
		}
		else if ( !clothingList.Contains( data.ClothingItem ) )
		{
			clothingList.Add( data.ClothingItem );
		}

		for ( int i = 0; i < clothingData.Count; i++ )
		{
			var clothData = clothingData[i];
			clothData.IsInClothingList = clothingList.Contains( clothData.ClothingItem );
			clothingData[i] = clothData;
		}

		UpdateClothingDisplay();
	}

	private void PaintBrushItem( VirtualWidget widget )
	{
		var brush = (ClothingData)widget.Object;

		Paint.Antialiasing = true;
		Paint.TextAntialiasing = true;
		
		if ( widget.Hovered && !brush.IsInClothingList )
		{		
			Paint.ClearPen();
			Paint.SetBrush( widget.Hovered ? Theme.Green.WithAlpha(0.10f) : Color.White.WithAlpha( 0.10f ) );
			Paint.SetPen( widget.Hovered ? Theme.Green.WithAlpha(0.50f) : Color.White.WithAlpha( 0.50f ) );
			Paint.DrawRect( widget.Rect.Grow( 2 ), 3 );
		}
		if ( brush.IsInClothingList )
		{
			Paint.ClearPen();
			Paint.SetBrush( widget.Hovered ? Theme.Red.WithAlpha( 0.10f ) : Theme.Green.WithAlpha( 0.10f ) );
			Paint.SetPen( widget.Hovered ? Theme.Red.WithAlpha( 0.50f ) : Theme.Green.WithAlpha( 0.50f ) );
			Paint.DrawRect( widget.Rect.Shrink( 2 ), 3 );
		}

		Paint.ClearPen();
		Paint.SetBrush( Color.White.WithAlpha( 0.01f ) );
		Paint.SetPen( Color.White.WithAlpha( 0.05f ) );
		Paint.DrawRect( widget.Rect.Shrink( 2 ), 3 );

		Paint.Draw( widget.Rect.Shrink( widget.Hovered ? 2 : 6 ), brush.Pixmap );

		var rect = widget.Rect;

		var textRect = rect.Shrink( 4 );
		textRect.Top = textRect.Top + 50;
		textRect.Top = textRect.Top + 25;

		Paint.ClearPen();
		Paint.SetBrush( Color.Black.WithAlpha( 0.5f ));
		Paint.DrawRect( textRect, 0.0f );

		Paint.Antialiasing = true;

		Paint.SetPen( Theme.Blue, 2.0f );
		Paint.ClearBrush();
		Paint.SetFont( "Poppins", 6, 700 );
		Paint.DrawText( textRect, brush.Name );

		if ( !brush.IsInClothingList && widget.Hovered )
		{
			Paint.ClearPen();
			Paint.SetBrush( Color.White );
			Paint.SetPen( Theme.Green.WithAlpha(0.75f), 20.0f );
			Paint.SetFont( "Poppins", 48, 200 );
			Paint.DrawText( widget.Rect, "+" );
		}
		else if( brush.IsInClothingList && widget.Hovered)
		{
			Paint.ClearPen();
			Paint.SetBrush( Color.Red );
			Paint.SetPen( Theme.Red.WithAlpha( 0.75f ), 20.0f );
			Paint.SetFont( "Poppins", 48, 200 );
			Paint.DrawText( widget.Rect, "-" );
		}
	}

	private bool PaintListBackground( Widget widget )
	{
		Paint.ClearPen();
		Paint.SetBrush( Theme.ControlBackground );
		Paint.DrawRect( widget.LocalRect );

		return false;
	}
}

file class TextureWidget : Widget
{
	Pixmap pixmap;

	public TextureWidget( Pixmap pixmap, Widget parent ) : base( parent )
	{
		this.pixmap = pixmap;

		this.MinimumSize = new Vector2( 96, 96 );
	}

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

		Paint.Antialiasing = true;

		Paint.ClearPen();
		//Paint.SetBrush( Theme.Red );
		Paint.DrawRect( LocalRect );

		Paint.Draw( LocalRect.Contain( pixmap.Size ), pixmap );
	}
}