Editor/DataTableEditor.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using DataTables;
using Editor;
using Sandbox;
using Sandbox.Diagnostics;
using Sandbox.Helpers;
using Application = Editor.Application;
using FileSystem = Editor.FileSystem;
using Json = DataTables.Json;

namespace DataTablesEditor;

public class DataTableEditor : DockWindow
{
	public bool CanOpenMultipleAssets => false;

	private Dictionary<string, WeakReference> _weakTable;

	private Asset _asset;
	private DataTable _dataTable;
	private TypeDescription _structType;

	private ToolBar _toolBar;

	public List<RowStruct> InternalEntries = new();
	public int EntryCount = 0;

	private TableView _tableView;

	private ControlSheet _sheet;

	private bool _isUnsaved = false;

	private string _previousJson;
	private EditorState _previousEditorState;

	private PropertyDescription[] _previousProperties;

	private string _defaultDockState;

	private UndoStack _undoStack = new();
	private UndoHistory _undoHistory;

	public DataTableEditor( Asset asset )
	{
		_asset = asset;
		_dataTable = _asset.LoadResource<DataTable>();
		_weakTable = _dataTable.WeakTable;

		if ( _dataTable is null )
		{
			Close();
			return;
		}
		_dataTable.Fix();

		_structType = TypeLibrary.GetType( _dataTable.StructType );

		_previousProperties = _structType.Properties.ToArray();

		EntryCount = _dataTable.EntryCount;

		foreach ( var row in _dataTable.StructEntries )
		{
			InternalEntries.Add( TypeLibrary.Clone( row ) );
		}

		_previousJson = SerializeEntries();
		_lastSaveJson = _previousJson;

		DeleteOnClose = true;

		Size = new Vector2( 1000, 800 );
		Title = $"Data Table Editor - {asset.Path}";
		SetWindowIcon( "equalizer" );

		AddToolbar();
		BuildMenuBar();

		Show();
		PopulateEditor();

		_previousEditorState = new EditorState(GetSelectedNames(), _sheetRowName, EntryCount);
	}

	private string SerializeEntries()
	{
		JsonArray jarray = new();
		jarray = Json.SerializeList( InternalEntries, true );
		return jarray.ToJsonString();
	}

	[Shortcut( "editor.undo", "CTRL+Z", ShortcutType.Window )]
	private void Undo()
	{
		if ( _undoStack.Undo() is UndoOp op )
		{
			InternalEntries = (List<RowStruct>)Json.DeserializeList( JsonNode.Parse( op.undoBuffer )?.AsArray(), typeof(List<RowStruct>) );
			_previousJson = SerializeEntries();
			MarkUnsaved();
			//PopulateEditor();
			_sheet.Clear( true );

			EditorState undoState = op.UndoEditorState;
			_sheetRowName = undoState.SheetRowName;
			UpdateViewAndEditor();

			EntryCount = undoState.EntryCount;
			_tableView.ListView.Selection.Clear();
			foreach ( var rowName in undoState.SelectedNames )
			{
				var row = InternalEntries.FirstOrDefault( x => x.RowName == rowName );
				if ( row is not null )
					_tableView.ListView.Selection.Add( row );
			}

			var _row = InternalEntries.FirstOrDefault( x => x.RowName == undoState.SheetRowName );
			if ( _row is not null )
				PopulateControlSheet( _row );
		}
	}

	[Shortcut( "editor.redo", "CTRL+Y", ShortcutType.Window )]
	private void Redo()
	{
		if ( _undoStack.Redo() is UndoOp op )
		{
			InternalEntries = (List<RowStruct>)Json.DeserializeList( JsonNode.Parse( op.redoBuffer )?.AsArray(), typeof(List<RowStruct>) );
			_previousJson = SerializeEntries();
			MarkUnsaved();
			//PopulateEditor();
			_sheet.Clear( true );

			EditorState redoState = op.RedoEditorState;
			_sheetRowName = redoState.SheetRowName;
			UpdateViewAndEditor();

			EntryCount = redoState.EntryCount;
			_tableView.ListView.Selection.Clear();
			foreach ( var rowName in redoState.SelectedNames )
			{
				var row = InternalEntries.FirstOrDefault( x => x.RowName == rowName );
				if ( row is not null )
					_tableView.ListView.Selection.Add( row );
			}

			var _row = InternalEntries.FirstOrDefault( x => x.RowName == redoState.SheetRowName );
			if ( _row is not null )
				PopulateControlSheet( _row );
		}
	}

	private void MarkUnsaved()
	{
		_isUnsaved = SerializeEntries() != _lastSaveJson;
		//Title = $"Data Table Editor - {_asset.Path}*";
	}

	private void MarkSaved()
	{
		_isUnsaved = false;
		//Title = $"Data Table Editor - {_asset.Path}";
	}

	private bool SheetFilter( SerializedProperty property )
	{
		var obj = property.Parent;
		obj.OnPropertyChanged = serializedProperty =>
		{
			MarkUnsaved();
		};
		return true;
	}

	private string EvaluateTitle()
	{
		return $"Data Table Editor - {_asset.Path}{(_isUnsaved ? "*" : "")}";
	}

	private int _mouseUpFrames;

	private RealTimeSince _timeSinceChange;

	[EditorEvent.Frame]
	private void Tick()
	{
		if ( !Visible )
			return;

		_undoHistory.UndoLevel = _undoStack.UndoLevel;

		Title = EvaluateTitle();

		/*_addOption.Enabled = !Game.IsPlaying;
		_duplicateOption.Enabled = !Game.IsPlaying;
		_deleteOption.Enabled = !Game.IsPlaying;*/

		_mouseUpFrames++;
		if ( Application.MouseButtons != 0 )
		{
			_mouseUpFrames = 0;
		}

		if ( _mouseUpFrames > 2 && _timeSinceChange > 0.5f )
		{
			if ( InternalEntries is null )
				return;

			string json = SerializeEntries();
			if ( json != _previousJson )
			{
				EditorState state = new(GetSelectedNames(), _sheetRowName, EntryCount);

				_undoStack.PushUndo("Modified a RowStruct", _previousJson, _previousEditorState );
				OnUndoPushed();
				_undoStack.PushRedo( json, state );
				_previousJson = json;
				_previousEditorState = state;
				MarkUnsaved();
				_timeSinceChange = 0;
			}
		}
	}

	private void UpdateViewAndEditor()
	{
		if ( !Visible )
			return;

		_tableEditor.Layout.Clear( true );

		_tableView = new TableView( _tableEditor );
		_tableView.ListView.BodyContextMenu = () =>
		{
			var m = new ContextMenu( this );

			m.AddOption( "Add", "add", () =>
			{
				AddEntry();
			} );

			m.AddOption( "Paste", "content_paste", () =>
			{
				Paste();
			} );

			m.OpenAtCursor();
		};
		_tableView.ListView.ItemContextMenu = o =>
		{
			var m = new ContextMenu( this );

			m.AddOption( "Copy", "content_copy", () =>
			{
				Copy();
			} );

			m.AddOption( "Delete", "delete", () =>
			{
				RemoveEntry();
			} );

			m.OpenAtCursor();
		};

		var structType = TypeLibrary.GetType( _structType.TargetType );

		foreach ( var member in TypeLibrary.GetFieldsAndProperties( structType ) )
		{
			var col = _tableView.AddColumn();

			if ( member.Name == "RowName" )
				col.TextColor = Color.Parse( "#e1913c" ).GetValueOrDefault();

			col.Name = member.Name;
			col.Value = o =>
			{
				if ( member.IsField )
				{
					FieldDescription field = structType.Fields.FirstOrDefault( x => x.IsNamed( member.Name ) );
					return field?.GetValue( o )?.ToString() ?? "";
				}

				PropertyDescription property =
					structType.Properties.FirstOrDefault( x => x.IsNamed( member.Name ) );
				return property?.GetValue( o )?.ToString() ?? "";
			};
		}

		_tableView.SetItems( InternalEntries.ToList() );
		_tableView.FillHeader();

		if ( InternalEntries.Count > 0 )
		{
			_tableView.ListView.Selection.Add( InternalEntries.First() );
			_sheetRowName = InternalEntries.First().RowName;
			PopulateControlSheet( InternalEntries.First() );
		}

		_tableView.ItemClicked = o =>
		{
			RowStruct row = (RowStruct)o;
			_sheetRowName = row.RowName;
			PopulateControlSheet( o );
		};

		_tableEditor.Layout.Add( _tableView );

		RowStruct row = InternalEntries.Find( o => o.RowName == _sheetRowName );
		if ( row is not null )
		{
			PopulateControlSheet( row );
		}
	}

	private Widget _rowEditor;
	private Widget _tableEditor;

	[EditorEvent.Hotload]
	private void PopulateEditor()
	{
		if ( !Visible )
			return;

		PropertyDescription[] properties = _structType.Properties.ToArray();
		foreach ( var property in properties )
		{
			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;
			}
		}

		DockManager.Clear();
		DockManager.RegisterDockType( "Table View", "equalizer", null, false );
		DockManager.RegisterDockType( "Row Editor", "tune", null, false );
		DockManager.RegisterDockType( "Undo History", "history", null, false );

		int i;
		for ( i = InternalEntries.Count - 1; i >= 0; i-- )
		{
			if ( InternalEntries[i] is null )
				InternalEntries.RemoveAt( i );
		}

		_rowEditor = new(this){ WindowTitle = "Row Editor" };
		_rowEditor.Layout = Layout.Column();
		_rowEditor.SetWindowIcon( "tune" );
		_rowEditor.Name = "Row Editor";

		ScrollArea scroll = new ScrollArea( _rowEditor );
		scroll.Canvas = new Widget( scroll )
		{
			Layout = Layout.Row(),
			VerticalSizeMode = SizeMode.CanGrow | SizeMode.Expand
		};

		scroll.Canvas.Layout.AddStretchCell();
		scroll.Canvas.OnPaintOverride = () =>
		{
			Paint.ClearPen();
			Paint.SetBrush( Theme.WidgetBackground );
			Paint.DrawRect( scroll.Canvas.LocalRect );

			return false;
		};

		_sheet = new ControlSheet();

		var layout = scroll.Canvas.Layout;

		var sheetCanvas = new Widget( scroll.Canvas );
		sheetCanvas.MinimumHeight = 200;
		sheetCanvas.Layout = Layout.Column();
		sheetCanvas.Layout.Add( _sheet );
		sheetCanvas.Layout.AddStretchCell();

		layout.Add( sheetCanvas );
		layout.AddStretchCell();

		var structType = TypeLibrary.GetType( _dataTable.StructType );
		if ( structType is null )
			return;

		_tableEditor = new(this){ WindowTitle = "Table View" };
		_tableEditor.Layout = Layout.Column();
		_tableEditor.SetWindowIcon( "equalizer" );
		_tableEditor.Name = "Table View";

		_tableView = new TableView( _tableEditor );
		_tableView.ListView.BodyContextMenu = () =>
		{
			var m = new ContextMenu( this );

			m.AddOption( "Add", "add", () =>
			{
				AddEntry();
			} );

			m.AddOption( "Paste", "content_paste", () =>
			{
				Paste();
			} );

			m.OpenAtCursor();
		};
		_tableView.ListView.ItemContextMenu = o =>
		{
			var m = new ContextMenu( this );

			m.AddOption( "Copy", "content_copy", () =>
			{
				Copy();
			} );

			m.AddOption( "Delete", "delete", () =>
			{
				RemoveEntry();
			} );

			m.OpenAtCursor();
		};

		foreach ( var member in TypeLibrary.GetFieldsAndProperties( structType ) )
		{
			var col = _tableView.AddColumn();

			if ( member.Name == "RowName" )
				col.TextColor = Color.Parse( "#e1913c" ).GetValueOrDefault();

			col.Name = member.Name;
			col.Value = o =>
			{
				if ( member.IsField )
				{
					FieldDescription field = structType.Fields.FirstOrDefault( x => x.IsNamed( member.Name ) );
					return field?.GetValue( o )?.ToString() ?? "";
				}

				PropertyDescription property =
					structType.Properties.FirstOrDefault( x => x.IsNamed( member.Name ) );
				return property?.GetValue( o )?.ToString() ?? "";
			};
		}

		_tableView.SetItems( InternalEntries.ToList() );
		_tableView.FillHeader();

		if ( InternalEntries.Count > 0 )
		{
			_tableView.ListView.Selection.Add( InternalEntries.First() );
			_sheetRowName = InternalEntries.First().RowName;
			PopulateControlSheet( InternalEntries.First() );
		}

		_tableView.ItemClicked = o =>
		{
			//var pair = (KeyValuePair<string, RowStruct>)o;
			RowStruct row = (RowStruct)o;
			_sheetRowName = row.RowName;
			PopulateControlSheet( o );
		};

		_tableEditor.Layout.Add( _tableView );

		_rowEditor.Layout.Add( scroll );

		_undoHistory = new UndoHistory( this, _undoStack );
		_undoHistory.OnUndo = Undo;
		_undoHistory.OnRedo = Redo;
		_undoHistory.OnHistorySelected = SetUndoLevel;

		var flags = DockManager.DockProperty.HideCloseButton | DockManager.DockProperty.HideOnClose | DockManager.DockProperty.DisallowFloatWindow;

		DockManager.AddDock( null, _tableEditor, DockArea.Top, flags );
		DockManager.AddDock( null, _rowEditor, DockArea.Bottom, flags );
		DockManager.AddDock( _rowEditor, _undoHistory, DockArea.Inside, flags );
		DockManager.RaiseDock( "Row Editor" );
		DockManager.Update();

		_defaultDockState = DockManager.State;

		if ( StateCookie != "DataTableEditor" )
		{
			StateCookie = "DataTableEditor";
		}
		else
		{
			RestoreFromStateCookie();
		}
	}

	public void OnUndoPushed()
	{
		_undoHistory.History = _undoStack.Names;
	}

	private void SetUndoLevel( int level )
	{
		if ( _undoStack.SetUndoLevel( level ) is UndoOp op )
		{
			InternalEntries = (List<RowStruct>)Json.DeserializeList( JsonNode.Parse( op.redoBuffer )?.AsArray(), typeof(List<RowStruct>) );
			_previousJson = SerializeEntries();
			MarkUnsaved();
			//PopulateEditor();
			UpdateViewAndEditor();
		}
	}

	private string _sheetRowName;

	private void PopulateControlSheet( object o )
	{
		_sheet.Clear( true );

		_sheet.Margin = new Sandbox.UI.Margin( 8 );
		_sheet.Spacing = 4f;

		SerializedObject so = o.GetSerialized();
		var properties = so.AsEnumerable();

		var props = so.AsEnumerable().Where( x =>
				x.IsPublic && x.IsEditable && !x.HasAttribute( typeof(JsonIgnoreAttribute) ) &&
				!x.HasAttribute( typeof(HideAttribute) ) )
			.OrderBy( x => x.Order )
			.ThenBy( x => x.SourceFile )
			.ThenBy( x => x.SourceLine )
			.ToList();

		foreach ( var prop in props )
		{
			_sheet.AddRow( prop );
		}
	}

	public void BuildMenuBar()
	{
		var file = MenuBar.AddMenu( "File" );
		file.AddOption( "New", "note_add", New, "editor.new" ).StatusTip = "New Data Table";
		file.AddOption( "Open", "file_open", Open, "editor.open" ).StatusTip = "Open Data Table";
		file.AddOption( "Save", "save", Save, "editor.save" ).StatusTip = "Save Data Table";
		file.AddOption( "Save As...", "save_as", SaveAs, "editor.save-as" ).StatusTip = "Save Data Table As...";
		file.AddOption( "Quit", "logout", Close, "editor.quit" ).StatusTip = "Quit";

		var view = MenuBar.AddMenu( "View" );
		view.AboutToShow += () => OnViewMenu( view );
	}

	[Shortcut( "editor.new", "CTRL+N", ShortcutType.Window )]
	private void New()
	{
		var fd = new FileDialog( null )
		{
			Title = "New Data Table",
			DefaultSuffix = $".dt",
			Directory = Path.GetDirectoryName( _asset.AbsolutePath )
		};

		fd.SetNameFilter( "Data Table (*.dt)" );
		fd.SetModeSave();

		if ( !fd.Execute() )
			return;

		//
		Action newFile = () =>
		{
			var jobj = new JsonObject();
			jobj["StructType"] = _structType.FullName;
			File.WriteAllText( fd.SelectedFile, jobj.ToJsonString() );
			_asset = AssetSystem.RegisterFile( fd.SelectedFile );
			if ( _asset == null )
			{
				Log.Warning( $"Unable to register asset {fd.SelectedFile}" );

				return;
			}

			NewAsync( _asset.AbsolutePath );
		};

		if ( _isUnsaved )
		{
			CloseDialog dialog = new($"Data Table Editor - {_asset.Path}", () =>
			{
				Save();
				newFile();
			}, () =>
			{
				_isUnsaved = false;
				if ( _dataTable.StructEntries.Count == 0 )
					_dataTable.EntryCount = 0;
				newFile();
			});
			return;
		}

		newFile();
	}

	private async void NewAsync( string path )
	{
		var asset = AssetSystem.RegisterFile( path );

		while ( !asset.IsCompiledAndUpToDate )
		{
			await Task.Yield();
		}

		MainAssetBrowser.Instance?.UpdateAssetList();
		DataTableEditor editor = new(_asset); // @TODO: When saving, don't open a new editor! Do what ShaderGraph instead
		Close();
	}

	[Shortcut( "editor.open", "CTRL+O", ShortcutType.Window )]
	private void Open()
	{
		var fd = new FileDialog( null )
		{
			Title = "Open Data Table",
			DefaultSuffix = $".dt",
			Directory = Path.GetDirectoryName( _asset.AbsolutePath )
		};

		fd.SetNameFilter( "Data Table (*.dt)" );
		fd.SetModeOpen();

		if ( !fd.Execute() )
			return;

		Action open = () =>
		{
			_asset = AssetSystem.FindByPath( fd.SelectedFile );
			if ( _asset == null )
			{
				Log.Warning( $"Failed to find asset at location: {fd.SelectedFile}" );

				return;
			}

			DataTableEditor editor = new(_asset);
			Close();
		};

		if ( _isUnsaved )
		{
			CloseDialog dialog = new($"Data Table Editor - {_asset.Path}", () =>
			{
				Save();
				open();
			}, () =>
			{
				_isUnsaved = false;
				if ( _dataTable.StructEntries.Count == 0 )
					_dataTable.EntryCount = 0;
				open();
			});
			return;
		}

		open();
	}

	private void OnViewMenu( Menu view )
	{
		view.Clear();
		view.AddOption( "Restore To Default", "settings_backup_restore", RestoreDefaultDockLayout );
		view.AddSeparator();

		foreach ( var dock in DockManager.DockTypes )
		{
			var o = view.AddOption( dock.Title, dock.Icon );
			o.Checkable = true;
			o.Checked = DockManager.IsDockOpen( dock.Title );
			o.Toggled += ( b ) =>
			{
				DockManager.SetDockState( dock.Title, b );
			};
		}
	}

	protected override void RestoreDefaultDockLayout()
	{
		DockManager.State = _defaultDockState;

		SaveToStateCookie();
	}

	private Option _addOption;
	private Option _duplicateOption;
	private Option _deleteOption;
	private void AddToolbar()
	{
		_toolBar = new ToolBar( this, "DataTableToolbar" );
		AddToolBar( _toolBar, ToolbarPosition.Top );

		_toolBar.Movable = false;
		_toolBar.AddOption( "Save", "save", Save ).StatusTip = "Save Data Table";
		_toolBar.AddOption( "Save As...", "save_as", SaveAs ).StatusTip = "Save Data Table as";
		//_toolBar.AddOption( "Browse", "common/browse.png" ).StatusTip = "Filler";d
		_toolBar.AddSeparator();
		_addOption = _toolBar.AddOption( "Add", "add", AddEntry );
		_addOption.StatusTip = "Append a new entry";
		_duplicateOption = _toolBar.AddOption( "Duplicate", "copy_all", DuplicateEntry );
		_duplicateOption.StatusTip = "Appends a duplicate of the currently selected entry";
		_deleteOption = _toolBar.AddOption( "Delete", "delete_outline", RemoveEntry );
		_deleteOption.StatusTip = "Delete the currently selected entry";

		/*var stretch = new Widget();
		stretch.HorizontalSizeMode = SizeMode.CanGrow | SizeMode.Expand;
		_toolBar.AddWidget( stretch );

		var dropdown = new Dropdown( _dataTable.StructType );
		dropdown.FixedWidth = 200;
		dropdown.Icon = "account_tree";
		_toolBar.AddWidget( dropdown );*/
	}

	private void DuplicateEntry()
	{
		List<object> newSelections = new();
		foreach ( RowStruct row in _tableView.ListView.Selection )
		{
			var o = TypeLibrary.Clone<RowStruct>( row );
			o.RowName = $"NewEntry_{EntryCount++}";

			InternalEntries.Add( o );
			_tableView.AddItem( o );

			_sheetRowName = o.RowName;
			PopulateControlSheet( o );

			newSelections.Add( o );
		}

		_tableView.ListView.Selection.Clear();
		foreach ( var newSelection in newSelections )
		{
			_tableView.ListView.Selection.Add( newSelection );
			_tableView.ListView.ScrollTo( newSelection );
		}

		MarkUnsaved();
	}

	private List<string> GetSelectedNames()
	{
		List<string> result = new();

		foreach ( RowStruct row in _tableView.ListView.Selection )
			result.Add( row.RowName );

		return result;
	}

	private void RemoveEntry()
	{
		_sheet.Clear( true );

		_previousJson = SerializeEntries();
		_previousEditorState = new(GetSelectedNames(), _sheetRowName, EntryCount);

		int index = -1;
		foreach ( RowStruct row in _tableView.ListView.Selection )
		{
			var tuple = InternalEntries.Index().First( x => x.Item.RowName == row.RowName );
			index = tuple.Index - 1;

			_tableView.ListView.RemoveItem( row );
			InternalEntries.Remove( row );
		}

		_tableView.ListView.Selection.Clear();

		if ( index < 0 )
			index = 0;
		if ( index < InternalEntries.Count )
		{
			_tableView.ListView.Selection.Add( InternalEntries[index] );

			_sheetRowName = InternalEntries[index].RowName;
			PopulateControlSheet( InternalEntries[index] );
		}

		EditorState state = new(GetSelectedNames(), _sheetRowName, EntryCount);

		var json = SerializeEntries();
		_undoStack.PushUndo( $"Remove Row(s)", _previousJson, _previousEditorState );
		OnUndoPushed();
		_undoStack.PushRedo( json, state );
		_previousJson = json;
		_previousEditorState = state;
		MarkUnsaved();
	}

	private void AddEntry()
	{
		_previousJson = SerializeEntries();

		_previousEditorState = new(GetSelectedNames(), _sheetRowName, EntryCount);

		var o = TypeLibrary.Create<RowStruct>( _dataTable.StructType );
		o.RowName = $"NewEntry_{EntryCount++}";

		InternalEntries.Add( o );
		_tableView.AddItem( o );

		_tableView.ListView.Selection.Clear();
		_tableView.ListView.Selection.Add( o );

		_sheetRowName = o.RowName;
		PopulateControlSheet( o );

		_tableView.ListView.ScrollTo( o );

		EditorState state = new(GetSelectedNames(), _sheetRowName, EntryCount);

		var json = SerializeEntries();
		_undoStack.PushUndo( $"Add Entry {o.RowName}", _previousJson, _previousEditorState );
		OnUndoPushed();
		_undoStack.PushRedo( json, state );
		_previousJson = json;
		_previousEditorState = state;
		MarkUnsaved();
	}

	private List<object> _clipboard = new();

	[Shortcut( "editor.copy", "CTRL+C", ShortcutType.Window )]
	private void Copy()
	{
		_clipboard.Clear();
		foreach ( var selection in _tableView.ListView.Selection )
			_clipboard.Add( selection );
	}

	[Shortcut( "editor.paste", "CTRL+V", ShortcutType.Window )]
	private void Paste()
	{
		if ( _clipboard.Count == 0 )
			return;

		MarkUnsaved();

		List<object> newSelections = new();
		foreach ( var selection in _clipboard )
		{
			var row = selection as RowStruct;
			var o = TypeLibrary.Clone<RowStruct>( row );
			o.RowName = $"NewEntry_{EntryCount++}";

			InternalEntries.Add( o );
			_tableView.AddItem( o );

			PopulateControlSheet( o );

			newSelections.Add( o );
		}

		_tableView.ListView.Selection.Clear();
		foreach ( var newSelection in newSelections )
		{
			_tableView.ListView.Selection.Add( newSelection );
			_tableView.ListView.ScrollTo( newSelection );
		}
	}

	[Shortcut( "editor.delete", "DEL", ShortcutType.Window )]
	private void Delete()
	{
		RemoveEntry();
	}

	private string _lastSaveJson;

	[Shortcut( "editor.save", "CTRL+S", ShortcutType.Window )]
	private void Save()
	{
		MarkSaved();

		if ( InternalEntries.Count == 0 )
			EntryCount = 0;

		for ( int i = _dataTable.StructEntries.Count - 1; i >= 0; i-- )
		{
			var row = _dataTable.StructEntries[i];
			var internalRow = InternalEntries.Find( x => x.RowName == row.RowName );
			if ( internalRow is null )
				_dataTable.StructEntries.RemoveAt( i );
		}

		for ( int i = 0; i < InternalEntries.Count; i++ )
		{
			var internalRow = InternalEntries[i];
			var row = _dataTable.StructEntries.Find( x => x.RowName == internalRow.RowName );

			if ( row is not null )
			{
				TypeLibrary.Merge( row, internalRow );
			}
			else
			{
				var cloneRow = TypeLibrary.Clone( internalRow );
				if ( _weakTable.TryGetValue( internalRow.RowName, out WeakReference weakReference ) )
				{
					if ( !weakReference.IsAlive )
					{
						_weakTable[internalRow.RowName] = new WeakReference( cloneRow );
						_dataTable.StructEntries.Insert( i, cloneRow );
						continue;
					}

					RowStruct weakRow = (RowStruct)weakReference.Target;
					TypeLibrary.Merge( weakRow, internalRow );
					_dataTable.StructEntries.Insert( i, weakRow );
					continue;
				}

				_weakTable.Add( internalRow.RowName, new WeakReference( cloneRow ) );
				_dataTable.StructEntries.Insert( i, cloneRow );
			}
		}

		if (_dataTable.StructEntries.Count != InternalEntries.Count)
			Log.Error($"Data Table {_dataTable} failed to merge! {_dataTable.StructEntries.Count} != {InternalEntries.Count}"  );

		_lastSaveJson = SerializeEntries();
		_dataTable.EntryCount = EntryCount;
		if ( _asset.SaveToDisk( _dataTable ) )
			EditorUtility.PlayRawSound( "sounds/editor/success.wav" );
	}

	[Shortcut("editor.save-as", "CTRL+SHIFT+S", ShortcutType.Window)]
	private void SaveAs()
	{
		var fd = new FileDialog( null )
		{
			Title = "Save Data Table",
			DefaultSuffix = $".dt",
			Directory = Path.GetDirectoryName( _asset.AbsolutePath )
		};

		fd.SetNameFilter( "Data Table (*.dt)" );
		fd.SetModeSave();

		if ( !fd.Execute() )
			return;

		JsonObject jobj = new();
		jobj["StructType"] = _structType.FullName;
		jobj["StructEntries"] = Json.SerializeList( InternalEntries, true );
		File.WriteAllText( fd.SelectedFile, jobj.ToJsonString() );
		_asset = AssetSystem.RegisterFile( fd.SelectedFile );
		if ( _asset == null )
		{
			Log.Warning( $"Unable to register asset {fd.SelectedFile}" );

			return;
		}

		SaveAsAsync( _asset.AbsolutePath );
	}

	private async void SaveAsAsync( string path )
	{
		var asset = AssetSystem.RegisterFile( path );

		while ( !asset.IsCompiledAndUpToDate )
		{
			await Task.Yield();
		}

		MainAssetBrowser.Instance?.UpdateAssetList();
		DataTableEditor editor = new(_asset); // @TODO: When saving, don't open a new editor! Do what ShaderGraph instead

		_isUnsaved = false;
		Close();
	}

	protected override bool OnClose()
	{
		if ( _isUnsaved )
		{
			CloseDialog dialog = new($"Data Table Editor - {_asset.Path}", () =>
			{
				Save();
				Close();
			}, () =>
			{
				_isUnsaved = false;
				if ( _dataTable.StructEntries.Count == 0 )
					_dataTable.EntryCount = 0;
				Close();
			});
		}
		else
		{
			DataTableEditorLauncher.OpenAssetEditors.Remove( _asset.Path.FastHash() );
		}

		return !_isUnsaved;
	}

	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 );
	}
}