Editor/LevelDesignerDock.cs
using System;
using System.Buffers.Text;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using Editor;
using Sandbox;
using Sandbox.UI;
using Button = Editor.Button;
using Label = Editor.Label;

namespace DesignerBrowser.Editor;

[Dock("Editor", "Designer Browser", "local_fire_department")]
public class LevelDesignerDock : Widget
{	
	/// <summary>
	/// Asset has been clicked
	/// </summary>
	public Action<Asset> OnAssetHighlight;

	/// <summary>
	/// Asset has been clicked
	/// </summary>
	public Action<Asset[]> OnAssetsHighlight;

	/// <summary>
	/// Item has been selected
	/// </summary>
	public Action<object> OnHighlight;
	
	private NavigationView View;
	
	public LevelDesignerDock( Widget parent ) : base( parent )
	{
		View = new NavigationView( this );

		Layout = Layout.Column();
		
		Layout.Margin = 4;
		Layout.Spacing = 4;
		
		Layout.Add( View, 1 );
		
		OnAssetHighlight = a => EditorUtility.InspectorObject = a;
		OnAssetsHighlight = a => EditorUtility.InspectorObject = a;
		//OnAssetSelected = a => a.OpenInEditor();

		Rebuild(); 
	}

	private string cachedLevelDesignTag;
	private string LevelDesignTag
	{
		get
		{
			if (cachedLevelDesignTag is not null)
			{
				return cachedLevelDesignTag;
			}
			
			var cookie = ProjectCookie.Get<string>("leveldesigner.assettag", null);

			if (cookie is null)
				return null;
			
			cachedLevelDesignTag = cookie;

			return cachedLevelDesignTag;
		}
	}
	
	private List<string> superCategoriesCache;
	private IEnumerable<string> SuperCategories 
	{
		get
		{
			if (superCategoriesCache is not null)
			{
				return superCategoriesCache;
			}
			
			var cookie = ProjectCookie.Get<string>("leveldesigner.supercategories", null);
		
			if (cookie is null)
				return Enumerable.Empty<string>();;

			if (string.IsNullOrWhiteSpace(cookie) || !Base64.IsValid(cookie))
				return Enumerable.Empty<string>();;
		
			var tags = cookie.Base64Decode().Split('/', StringSplitOptions.RemoveEmptyEntries);
			superCategoriesCache = tags.ToList();

			return superCategoriesCache;
		}
	}
	
	private List<string> categoriesCache;
	private IEnumerable<string> Categories 
	{
		get
		{
			if (categoriesCache is not null)
			{
				return categoriesCache;
			}
			
			var cookie = ProjectCookie.Get<string>("leveldesigner.categories", null);
		
			if (cookie is null)
				return Enumerable.Empty<string>();;

			if (string.IsNullOrWhiteSpace(cookie) || !Base64.IsValid(cookie))
				return Enumerable.Empty<string>();;
		
			var tags = cookie.Base64Decode().Split('/', StringSplitOptions.RemoveEmptyEntries);
			categoriesCache = tags.ToList();

			return categoriesCache;
		}
	}

	[EditorEvent.Hotload]
	public void Rebuild()
	{
		superCategoriesCache = null;
		cachedLevelDesignTag = null;
		categoriesCache = null;
		
		View.ClearPages();

		View.AddSectionHeader("Super Categories");

		View.PageContents.Margin = new Margin(2f, 0, 0, 0);
		
		foreach ( var category in SuperCategories )
		{
			var option = new NavigationView.Option( category.ToTitleCase(), "" );
			
			option.CreatePage = () =>
			{
				var view = new NavigationView( null );
				view.AddSectionHeader(category.ToTitleCase());
				
				CreateContentForLevelCategory( view, category );

				return view;
			};

			View.AddPage( option ); 
		}
		
		var uncategorizedOption = new NavigationView.Option( "Uncategorized", "" );
		uncategorizedOption.CreatePage = () =>
		{
			var view = new NavigationView( null );
			view.AddSectionHeader("Uncategorized");
				
			CreateContentForUncategorized( view );

			return view;
		};

		View.AddPage( uncategorizedOption ); 
		
		var categoryEditor = new NavigationView.Option( "Category Editor", "" );
		categoryEditor.CreatePage = () =>
		{
			var scroll = new ScrollArea( null );
			scroll.Canvas = new Widget( scroll );
			scroll.Canvas.Layout = Layout.Column();
			scroll.Canvas.Layout.Margin = 6f;
			scroll.Canvas.Layout.Spacing = 6f;

			scroll.Canvas.VerticalSizeMode = SizeMode.CanShrink;

			CreateCategoryEditor(scroll.Canvas.Layout);

			return scroll;
		};

		View.AddPage( categoryEditor ); 
	}

	[Event( "assetsystem.changes" )]
	void OnAssetSystemChanges()
	{
		//Rebuild();
	}


	[Event( "content.changed" )]
	void OnContentChanged( string file )
	{
		//Rebuild();
	}


	private void SaveLevelAssetTag(string tag)
	{
		ProjectCookie.Set("leveldesigner.assettag", tag);
	}
	
	private void CreateCategoryEditor(Layout layout)
	{
		var levelAssetTag = layout.Add(new Widget(null));
		levelAssetTag.FixedHeight = 30f;
		levelAssetTag.Layout = Layout.Row();
		levelAssetTag.Layout.Spacing = 5f;
		levelAssetTag.ToolTip = "Assets tagged with this tag will appear in the asset browser";
		
		var label = levelAssetTag.Layout.Add(new Label("Level Asset Tag"));
		label.FixedWidth = 90f;
		label.Alignment = TextFlag.RightCenter;
		
		var levelAssetTagButton = levelAssetTag.Layout.Add(new TagButton());
		levelAssetTagButton.FixedWidth = 90f;
		
		levelAssetTagButton.Clicked = () =>
		{
			var selector = new PopupWidget(levelAssetTagButton);
			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))
			{
				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,
				} );

				button.Clicked = () =>
				{
					levelAssetTagButton.Text = tag.Title;
					levelAssetTagButton.Icon = tag.IconPixmap;
					levelAssetTagButton.InternalName = tag.Tag;
					SaveLevelAssetTag(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);
					var tagOption = TagsPicker.FindTag(tag);

					if (tagOption is not null)
					{
						levelAssetTagButton.Text = tagOption.Value.Title;
						levelAssetTagButton.Icon = tagOption.Value.IconPixmap;
						levelAssetTagButton.InternalName = tagOption.Value.Tag;
						SaveLevelAssetTag(tag);
					}
					selector.Close();
				}
			};
			
			selector.FixedWidth = 360;
			selector.Show();
			selector.Position = levelAssetTagButton.ScreenRect.BottomRight;
			selector.ConstrainToScreen();
		};

		var assetTag = TagsPicker.FindTag(ProjectCookie.Get<string>("leveldesigner.assettag", null));
		if (assetTag is not null)
		{
			levelAssetTagButton.Text = assetTag.Value.Title;
			levelAssetTagButton.Icon = assetTag.Value.IconPixmap;
			levelAssetTagButton.InternalName = assetTag.Value.Tag;
		}
		
		levelAssetTag.Layout.AddStretchCell();
		
		/*levelAssetTagButton.Updated += () => 
		{
			ProjectCookie.Set("leveldesigner.blacklisted", blacklistedTagsPicker.Serialize());
		};
		
		if (ProjectCookie.TryGetString("leveldesigner.blacklisted", out var blacklisted))
		{
			blacklistedTagsPicker.Deserialize(JsonSerializer.Deserialize<string>(blacklisted));
		}*/
		
		var superCategoriesWidget = layout.Add(new Widget(null));
		superCategoriesWidget.FixedHeight = 30f;
		superCategoriesWidget.Layout = Layout.Row();
		superCategoriesWidget.Layout.Spacing = 5f;
		
		label = superCategoriesWidget.Layout.Add(new Label("Super Categories"));
		label.FixedWidth = 90f;
		label.Alignment = TextFlag.RightCenter;

		var  superCategoriesTagsPicker = superCategoriesWidget.Layout.Add(new TagsPicker());
		superCategoriesTagsPicker.SetStyles($"background-color:{Theme.ControlBackground.Lighten(0.35f).Hex}");
		superCategoriesTagsPicker.Updated += () => 
		{
			ProjectCookie.Set("leveldesigner.supercategories", superCategoriesTagsPicker.Serialize());
			superCategoriesCache = null;
		};

		superCategoriesTagsPicker.Filter = definition =>
		{
			if (Categories.Contains(definition.Tag))
				return false;
			
			return LevelDesignTag != definition.Tag;
		};

		if (ProjectCookie.TryGetString("leveldesigner.supercategories", out var superCategories))
		{
			superCategoriesTagsPicker.Deserialize(JsonSerializer.Deserialize<string>(superCategories));
		}
		
		var categoriesWidget = layout.Add(new Widget(null));
		categoriesWidget.FixedHeight = 30f;
		categoriesWidget.Layout = Layout.Row();
		categoriesWidget.Layout.Spacing = 5f;
		
		label = categoriesWidget.Layout.Add(new Label("Categories"));
		label.FixedWidth = 90f;
		label.Alignment = TextFlag.RightCenter;
		
		var categoriesTagsPicker = categoriesWidget.Layout.Add(new TagsPicker());
		categoriesTagsPicker.SetStyles($"background-color:{Theme.ControlBackground.Lighten(0.35f).Hex}");
		categoriesTagsPicker.Updated += () => 
		{
			ProjectCookie.Set("leveldesigner.categories", categoriesTagsPicker.Serialize());
			categoriesCache = null;
		};
		
		if (ProjectCookie.TryGetString("leveldesigner.categories", out var categories))
		{
			categoriesTagsPicker.Deserialize(JsonSerializer.Deserialize<string>(categories));
		}
		
		categoriesTagsPicker.Filter = definition =>
		{
			if (SuperCategories.Contains(definition.Tag))
				return false;
			
			return LevelDesignTag != definition.Tag;
		};
		
		/*var blacklistedTags = layout.Add(new Widget(null));
		blacklistedTags.FixedHeight = 30f;
		blacklistedTags.Layout = Layout.Row();
		blacklistedTags.Layout.Spacing = 5f;
		
		label = blacklistedTags.Layout.Add(new Label("Blacklisted Tags"));
		label.FixedWidth = 90f;
		label.Alignment = TextFlag.RightCenter;
		
		var blacklistedTagsPicker = blacklistedTags.Layout.Add(new TagsPicker(null));
		blacklistedTagsPicker.SetStyles($"background-color:{Theme.ControlBackground.Lighten(0.35f).Hex}"); 
		blacklistedTagsPicker.Updated += () => 
		{
			ProjectCookie.Set("leveldesigner.blacklisted", blacklistedTagsPicker.Serialize());
		};
		
		if (ProjectCookie.TryGetString("leveldesigner.blacklisted", out var blacklisted))
		{
			blacklistedTagsPicker.Deserialize(JsonSerializer.Deserialize<string>(blacklisted));
		}*/

		var button = layout.Add( new Button.Primary("Save and Reload", "save"));
		button.Clicked = () =>
		{
			Rebuild();
		};

	}

	private void CreateContent(NavigationView view, IEnumerable<Asset> assets)
	{
		var subCategories = Categories;
		
		//also store all the asset entries we need for each subcategory
		var subCategoryEntries = new Dictionary<string, List<AssetEntry>>(); 

		var entries = assets
			.Select(x => new AssetEntry(new FileInfo( x.Path ), x));

		foreach (var assetEntry in entries)
		{
			var subcategory = assetEntry.Asset.Tags.FirstOrDefault(x => subCategories.Contains(x));

			if (string.IsNullOrWhiteSpace(subcategory))
				subcategory = "Uncategorized";
			
			if (!subCategoryEntries.ContainsKey(subcategory))
			{
				subCategoryEntries.Add(subcategory, new List<AssetEntry>());
			}
			
			subCategoryEntries[subcategory].Add(assetEntry);
		}

		foreach ( var subCategory in subCategoryEntries.Keys.OrderBy(x => x == "Uncategorized") )
		{
			var option = new NavigationView.Option( subCategory.ToTitleCase(), "" );
			
			option.CreatePage = () =>
			{
				var scroll = new ScrollArea( null );
				scroll.Canvas = new Widget( scroll );
				scroll.Canvas.Layout = Layout.Column();
				scroll.Canvas.Layout.Margin = 16f;
				scroll.Canvas.Layout.Spacing = 5f;

				scroll.Canvas.VerticalSizeMode = SizeMode.CanGrow;
				
				var assetList = scroll.Canvas.Layout.Add(new AssetList(null), 1);
				assetList.ViewMode = AssetListViewMode.LargeIcons;
		
				assetList.OnHighlight = ( o ) =>
				{
					if ( o is object[] object_list )
					{
						var entries = object_list.OfType<AssetEntry>().ToArray();
						if ( entries.Length > 0 && entries.Length == object_list.Length )
						{
							OnAssetsHighlight?.Invoke( entries.Select( x => x.Asset ).ToArray() );
							return;
						}
					}

					if ( o is AssetEntry ae )
					{
						OnAssetHighlight?.Invoke( ae.Asset );
					}
					else
					{
						OnHighlight?.Invoke( o );
					}
				};
				
				
				assetList.SetItems(subCategoryEntries[subCategory]);

				return scroll; 
			};

			view.AddPage( option ); 
		}
	}

	private void CreateContentForUncategorized(NavigationView view)
	{
		var assets = AssetSystem.All.Where(x => x.Tags.Contains(LevelDesignTag))
			.Where(x =>
			{
				foreach (var levelCategory in SuperCategories)
				{
					if (x.Tags.Contains(levelCategory))
						return false;
				}
				return true;
			});
		
		CreateContent(view, assets);
	}
	
	private void CreateContentForLevelCategory(NavigationView view, string levelCategory)
	{
		//Thesse are all the assets that belong to our level category
		var assets = AssetSystem.All.Where(x => x.Tags.Contains(LevelDesignTag) && x.Tags.Contains(levelCategory));

		CreateContent(view, assets);
	}
}