Editor/TagsPicker.cs
using System;
using System.Buffers.Text;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Editor;
using Sandbox;

namespace DesignerBrowser.Editor;

public class TagsPicker : Widget
{
	public Action Updated;

	private ScrollArea Scroller;

	public Func<AssetTagSystem.TagDefinition, bool> Filter;

	public TagsPicker( Widget parent = null ) : base( parent )
	{
		Layout = Layout.Column();

		Scroller = new ScrollArea( this );
		Scroller.VerticalScrollbarMode = ScrollbarMode.Off;
		Scroller.HorizontalScrollbarMode = ScrollbarMode.Off;

		Scroller.Canvas = new Widget( Scroller );
		Scroller.Canvas.Layout = Layout.Row();
		Scroller.Canvas.Layout.Alignment = TextFlag.Left;
		Scroller.Canvas.Layout.Spacing = 4;
		Scroller.Canvas.Layout.Margin = new Sandbox.UI.Margin( 4, 0 );

		Layout.Add( Scroller, 1 );

		var addButton = new IconButton("add");
		addButton.OnClick = () =>
		{
			var selector = new PopupWidget(addButton);
			selector.IsPopup = true;

			var grid = Layout.Grid();
			grid.Spacing = 4f;
			grid.Margin = 8f;
			grid.SetColumnStretch(1, 1, 1, 1, 1);
			//grid.SetColumnStretch(0);
			
			selector.Layout = grid;

			var gridColumns = 4;
			var count = 0;
			
			foreach (var tag in AssetTagSystem.All
				         .Where(x => !x.AutoTag)
				         .Where(x =>
				         {
					        return Filter is null || Filter(x);
				         }))
			{
				var column = count % gridColumns;
				var row = (count / gridColumns);
				
				var button = grid.AddCell(column, row, new TagButton()
				{
					Text = tag.Title,
					Icon = tag.IconPixmap,
					InternalName = tag.Tag,
					Value = Scroller.Canvas.Children.OfType<TagButton>().Any(x => x.InternalName == tag.Tag)
				} );

				button.Clicked = () =>
				{
					ToggleOption(tag, button);
					button.Value = Scroller.Canvas.Children.OfType<TagButton>().Any(x => x.InternalName == tag.Tag);
				};
				
				count++;
			}
			
			var newColumn = count % gridColumns;
			var newRow = (count / gridColumns);

			var tagAdd = grid.AddCell(newColumn, newRow, new LineEdit());
			tagAdd.PlaceholderText = "Add New Tag..";
			tagAdd.MinimumHeight = 32 - 8;
			tagAdd.ReturnPressed += () =>
			{
				var tagList = tagAdd.Text.Split( new[] { ',', ' ' } );

				foreach ( var tag in tagList )
				{
					var method = typeof(AssetTagSystem).GetMethod("EnsureRegistered", BindingFlags.Static | BindingFlags.NonPublic);
					method?.Invoke(null, new object[]{tag});
					//AssetTagSystem.EnsureRegistered(tag);
					AddOption(FindTag(tag));
					selector.Close();
				}
			};
			
			selector.FixedWidth = 360;
			selector.Show();
			selector.Position = addButton.ScreenRect.BottomRight;
			selector.ConstrainToScreen();
		};

		Scroller.Canvas.Layout.Add(addButton);
	}
	
	public string Serialize()
	{
		var str = "";
		foreach (var tagButton in Scroller.Canvas.Children.OfType<TagButton>())
		{
			str += tagButton.InternalName + '/';
		}
		return str.Base64Encode();
	}

	public void Deserialize(string data)
	{
		if (string.IsNullOrWhiteSpace(data) || !Base64.IsValid(data))
			return;
		
		var tags = data.Base64Decode().Split('/', StringSplitOptions.RemoveEmptyEntries);
		foreach (var tag in tags)
		{
			AddOption(FindTag(tag));
		}	
	}

	private void AddOption( AssetTagSystem.TagDefinition option )
	{
		if (Scroller.Canvas.Children.OfType<TagButton>().Any(x => x.InternalName == option.Tag))
			return;
		
		Scroller.FixedHeight = 24;

		var button = Scroller.Canvas.Layout.Add( new TagButton()
		{
			Text = option.Title,
			Icon = option.IconPixmap,
			InternalName = option.Tag
		} );

		button.Clicked = () =>
		{
			button.Destroy();
			Updated?.Invoke();
		};
		
		Updated?.Invoke();
	}

	private void ToggleOption(AssetTagSystem.TagDefinition option, TagButton tagButton)
	{
		var activeButton = Scroller.Canvas.Children.OfType<TagButton>()
			.FirstOrDefault(x => x.InternalName == option.Tag);
		
		if (activeButton is not null)
		{
			activeButton.Destroy();
			Updated?.Invoke();
			return;
		}

		AddOption(option);
	}

	private void AddOption(AssetTagSystem.TagDefinition? option)
	{
		if (option is null)
			return;
		
		AddOption(option.Value);
	}

	public static AssetTagSystem.TagDefinition? FindTag( string tagName )
	{
		if (tagName is null)
			return null;
		
		foreach ( var tag in AssetTagSystem.All )
		{
			if ( tag.Tag.Equals( tagName ) )
			{
				return tag;
			}
		}
		
		Log.Warning($"Didn't fing a tag for {tagName}");

		return null;
	}
}

public class TagButton : Button
{
	public string InternalName { get; set; }
	public bool Value { get; set; }
	public new Pixmap Icon { get; set; }

	private const float FontSize = 8.0f;

	protected override Vector2 SizeHint()
	{
		Paint.SetDefaultFont( FontSize );
		var textSize = Paint.MeasureText( $"{Text}" );

		var iconSize = new Vector2( 0, 0 );

		if ( Icon != null )
			iconSize = new Vector2( 16, 16 );

		var padding = new Vector2( 8, 4 ) * 2.0f;

		var totalWidth = textSize.x + iconSize.x;
		var totalSize = new Vector2( totalWidth, textSize.y ) + padding;
		return totalSize;
	}
	
	protected override void OnPaint()
	{
		var rect = LocalRect;

		Paint.ClearPen();
		Paint.ClearBrush();

		var color = (IsPressed || Value) ? Theme.Primary : Theme.ControlBackground;

		if ( Paint.HasMouseOver )
			color = color.Lighten( 0.5f );

		Paint.SetBrush( color );
		Paint.DrawRect( rect, rect.Height / 2f );

		Paint.ClearPen();
		Paint.ClearBrush();
		Paint.SetPen( Theme.ControlText );
		Paint.SetDefaultFont( FontSize );

		if ( Icon != null )
		{
			var iconRect = rect.Shrink( 8, 6 );
			iconRect.Width = iconRect.Height;

			Paint.Draw( iconRect, Icon );
			rect.Left += 16.0f;
		}

		Paint.DrawText( rect, $"{Text}" );
	}
}