Editor/DataTableEditorLauncher.cs
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using DataTables;
using Editor;
using Sandbox;
using FileSystem = Sandbox.FileSystem;

namespace DataTablesEditor;

[EditorForAssetType( "dt" )]
public class DataTableEditorLauncher : BaseWindow, IAssetEditor
{
	public static Dictionary<int, DataTableEditor> OpenAssetEditors = new();

	public bool CanOpenMultipleAssets => false;

	private Asset _asset;

	private DataTable _dataTable;

	public DataTableEditorLauncher()
	{
	}

	private void FillLayout()
	{
		DeleteOnClose = true;

		Size = new Vector2( 300, 75 );
		WindowTitle = "Select a RowStruct type";
		SetWindowIcon( "equalizer" );

		Layout = Layout.Column();
		Layout.Margin = 8;
		Layout.Spacing = 8;

		var dropdown = new Dropdown();
		dropdown.Icon = "error";
		dropdown.Text = "None";
		dropdown.PopulatePopup = widget =>
		{
			var structTypes = TypeLibrary.GetTypes().Where( x => x.TargetType.IsSubclassOf( typeof(RowStruct) ) )
				.OrderBy( x => x.Name );

			foreach ( var structType in structTypes )
			{
				bool passValidationCheck = true;
				foreach ( var property in structType.Properties.Where( x => x.IsPublic && !x.IsStatic ) )
				{
					var type = property.PropertyType;
					bool isList = type.IsAssignableTo( typeof(IList) );
					bool isDictionary = type.IsAssignableTo( typeof(IDictionary) );

					if ( type.IsGenericType && (!isList && !isDictionary) )
						passValidationCheck = false;
				}

				var btn = new DropdownButton( dropdown, structType.Name );
				btn.Value = structType;
				btn.Icon = "account_tree";

				if ( !passValidationCheck )
				{
					btn.Enabled = false;
					btn.Icon = "error";
					btn.ToolTip = "\u26a0\ufe0f This RowStruct type contains generic properties that are not supported.";
					btn.Label.Color = Color.White;
					btn.IconButton.Foreground = Theme.Red;
				}

				widget.Layout.Add( btn );
			}
		};

		var lbl = new Label( "Please assign a RowStruct type to this Data Table:" );

		Layout.AddStretchCell();
		Layout.Add( lbl );
		Layout.Add( dropdown );

		var row = Layout.AddRow();
		row.AddStretchCell();

		var confirmBtn = new Button.Primary( "Confirm", icon: "done" );
		confirmBtn.Enabled = false;
		confirmBtn.Clicked = () =>
		{
			var type = dropdown.Value as TypeDescription;
			_dataTable.StructType = type.FullName;
			_asset.SaveToDisk( _dataTable );
			OpenEditor();
		};

		dropdown.OnValueChanged = () =>
		{
			confirmBtn.Enabled = dropdown.Value is not null;
		};

		row.Add( confirmBtn );

		Layout.AddStretchCell();

		Show();
	}

	private void OpenEditor()
	{
		DataTableEditor editor = new(_asset);
		if ( !OpenAssetEditors.ContainsKey( _asset.Path.FastHash() ) )
			OpenAssetEditors.Add( _asset.Path.FastHash(), editor );

		Close();
	}

	public void AssetOpen( Asset asset )
	{
		if ( OpenAssetEditors.TryGetValue( asset.Path.FastHash(), out DataTableEditor editor ) )
		{
			if ( editor.IsValid )
			{
				editor.Show();
				editor.Focus();
				Close();
				return;
			}

			OpenAssetEditors.Remove( asset.Path.FastHash() );
		}

		_asset = asset;
		_dataTable = _asset.LoadResource<DataTable>();

		var structType = TypeLibrary.GetType( _dataTable.StructType );

		if ( structType is null && _dataTable.StructType is not null )
		{
			MissingStructDialog dialog = new($"Data Table Editor - {_asset.Path}", _dataTable.StructType); // @TODO: Revisit this. I'm not even sure if this dialog is necessary.
			Close();
			return;
		}

		if ( structType is null || !TypeLibrary.GetTypes().Any( x => x.TargetType.IsSubclassOf( typeof(RowStruct) ) ) )
		{
			FillLayout();
		}
		else
		{
			foreach ( var property in structType.Properties.Where( x => x.IsPublic && !x.IsStatic ) )
			{
				var type = property.PropertyType;
				bool isList = type.IsAssignableTo( typeof(IList) );
				bool isDictionary = type.IsAssignableTo( typeof(IDictionary) );

				if ( type.IsGenericType && (!isList && !isDictionary) )
				{
					StructDialog popup = new StructDialog( $"Data Table Editor - {asset.Path}" );
					popup.DeleteOnClose = true;
					popup.OnWindowClosed = delegate
					{
						Close();
					};
					popup.SetModal(on: true, application: true);
					popup.Hide();
					popup.Show();
					return;
				}
			}
			OpenEditor();
		}
	}

	public void SelectMember( string memberName )
	{
		throw new System.NotImplementedException();
	}

	protected override void OnPaint()
	{
		Paint.ClearPen();
		Paint.SetBrush( Theme.WidgetBackground );
		Paint.DrawRect( LocalRect );
	}

	public override void SetWindowIcon( string name )
	{
		Pixmap pixmap = new Pixmap( 128, 128 );
		pixmap.Clear( Color.Transparent );

		using ( Paint.ToPixmap( pixmap ) )
		{
			Rect rect = new Rect( 0.0f, 0.0f, 128f, 128f );
			Paint.ClearPen();
			Paint.SetBrush( Color.Parse( "#b0e24d" ).GetValueOrDefault() );
			Paint.DrawRect( in rect, 16f );
			Paint.SetPen( in Color.Black );
			Paint.DrawIcon( rect, name, 120f );
		}

		SetWindowIcon( pixmap );
	}
}