Editor/Tileset/TilesetEditor/Autotile/AutotileBrushControl.cs
using Editor;
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security;

namespace SpriteTools.TilesetEditor;

public class AutotileBrushControl : Widget
{
	internal AutotileBrushListControl ParentList;
	internal AutotileBrush Brush;

	Drag dragData;
	bool draggingAbove = false;
	bool draggingBelow = false;

	Layout TileContent;
	List<AutotileTileControl> TileControls = new();

	public AutotileBrushControl ( AutotileBrushListControl list, AutotileBrush brush )
	{
		ParentList = list;
		Brush = brush;

		MouseClick = () =>
		{
			ParentList.SelectBrush( this );
		};

		VerticalSizeMode = SizeMode.Flexible;

		StatusTip = $"Select Brush \"{brush.Name}\"";
		Cursor = CursorShape.Finger;

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

		TileContent = Layout.Add( Layout.Row() );

		CreateTileControls();

		IsDraggable = true;
		AcceptDrops = true;
	}

	public override void OnDestroyed ()
	{
		base.OnDestroyed();

		if ( ParentList.SelectedBrush == this )
		{
			ParentList.SelectedBrush = null;
			ParentList.SelectedTile = null;
		}
	}

	void CreateTileControls ()
	{
		if ( TileContent is null ) return;
		TileContent.Clear( true );
		TileControls.Clear();

		var tileCount = Brush.TileCount;
		for ( int i = 0; i < tileCount; i++ )
		{
			var tileControl = new AutotileTileControl( this, i );
			TileContent.Add( tileControl );
			TileControls.Add( tileControl );
		}
	}

	[EditorEvent.Frame]
	void Frame ()
	{
		var tileCount = Brush?.TileCount ?? 0;
		if ( tileCount > 0 && tileCount != TileControls.Count )
		{
			CreateTileControls();
		}
	}

	protected override void OnPaint ()
	{
		MaximumWidth = ParentList.Width - 12;
		if ( Enabled )
		{
			if ( dragData?.IsValid ?? false )
			{
				Paint.SetBrushAndPen( Theme.WindowBackground.WithAlpha( 0.5f ) );
				Paint.DrawRect( LocalRect, 4 );
			}
			else if ( ParentList.SelectedBrush == this )
			{
				Paint.SetBrushAndPen( Theme.Highlight.Darken( 0.5f ) );
				Paint.DrawRect( LocalRect, 4 );
			}
			else if ( IsUnderMouse )
			{
				Paint.SetBrushAndPen( Theme.Text.WithAlpha( 0.1f ) );
				Paint.DrawRect( LocalRect, 4 );
			}
		}
		else
		{
			Paint.SetBrushAndPen( Theme.WindowBackground.WithAlpha( 0.5f ) );
			Paint.DrawRect( LocalRect );
		}

		var brushName = string.IsNullOrEmpty( Brush.Name ) ? $"Brush {ParentList.Buttons.IndexOf( this ) + 1}" : Brush.Name;
		Paint.SetBrushAndPen( Color.Transparent );
		var textRect = LocalRect.Shrink( 6, 4 );
		textRect.Top += 2f;
		Paint.DrawTextBox( textRect, brushName, Theme.TextControl, 8, 4, TextFlag.LeftTop );

		var tileCount = Brush.TileCount;
		Paint.SetBrushAndPen( Theme.TextLight );
		var size = 26f;
		var padding = 3;
		var tileWidth = MathF.Floor( Width / ( size + padding ) );
		for ( int i = 0; i < tileCount; i++ )
		{
			var x = i % tileWidth;
			var y = MathF.Floor( i / tileWidth );
			var tileRect = new Rect( LocalRect.TopLeft + new Vector2( 4, 26 ) + new Vector2( x * ( size + padding ), y * ( size + padding ) ), new Vector2( size, size ) );
			if ( TileControls.ElementAt( i ) is AutotileTileControl tileControl )
			{
				tileControl.Position = tileRect.Position;
			}
		}
		var maxY = 1 + MathF.Floor( tileCount / tileWidth );
		FixedHeight = 28 + ( size + padding ) * maxY;

		if ( draggingAbove )
		{
			Paint.SetPen( Theme.Highlight, 2f, PenStyle.Dot );
			Paint.DrawLine( LocalRect.TopLeft, LocalRect.TopRight );
			draggingAbove = false;
		}
		else if ( draggingBelow )
		{
			Paint.SetPen( Theme.Highlight, 2f, PenStyle.Dot );
			Paint.DrawLine( LocalRect.BottomLeft, LocalRect.BottomRight );
			draggingBelow = false;
		}
	}

	void Rename ()
	{
		var brush = Brush;
		OpenLineEditFlyout( Brush.Name, "What do you want to rename this Brush to?", null,
		name =>
		{
			if ( string.IsNullOrEmpty( name ) ) return;
			ParentList?.MainWindow?.PushUndo( "Rename Brush" );
			brush.Name = name;
			ParentList?.MainWindow?.SetDirty();
			ParentList?.MainWindow?.PushRedo();
		} );
	}

	void Delete ()
	{
		ParentList.DeleteBrush( Brush );
	}

	protected override void OnContextMenu ( ContextMenuEvent e )
	{
		base.OnContextMenu( e );

		var m = new Menu( this );

		m.AddOption( "Clear All Tiles", "clear", () =>
		{
			foreach ( var tile in Brush.Tiles )
			{
				tile.Tiles?.Clear();
			}
			ParentList?.MainWindow?.inspector?.UpdateSelectedAutotileSheet();
			ParentList?.MainWindow?.SetDirty();
		} );

		m.AddSeparator();

		m.AddOption( "Rename", "edit", Rename );
		m.AddOption( "Delete", "delete", Delete );

		m.OpenAtCursor( false );
	}

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

		dragData = new Drag( this );
		dragData.Data.Object = Brush;
		dragData.Execute();
	}

	public override void OnDragHover ( DragEvent ev )
	{
		base.OnDragHover( ev );

		if ( !TryDragOperation( ev, out var dragDelta ) )
		{
			draggingAbove = false;
			draggingBelow = false;
			return;
		}

		draggingAbove = dragDelta > 0;
		draggingBelow = dragDelta < 0;
	}

	public override void OnDragDrop ( DragEvent ev )
	{
		base.OnDragDrop( ev );

		if ( !TryDragOperation( ev, out var delta ) ) return;

		var list = ParentList.SerializedProperty.GetValue<List<AutotileBrush>>();
		var index = list.IndexOf( Brush );
		var movingIndex = index + delta;
		var layer = list[movingIndex];

		list.RemoveAt( movingIndex );
		list.Insert( index, layer );

		ParentList.SerializedProperty.SetValue( list );
		ParentList.UpdateList();
	}

	bool TryDragOperation ( DragEvent ev, out int delta )
	{
		delta = 0;
		var brush = ev.Data.OfType<AutotileBrush>().FirstOrDefault();

		if ( brush == null || ParentList == null ) return false;

		var layerList = ParentList.SerializedProperty.GetValue<List<AutotileBrush>>();
		var myIndex = layerList.IndexOf( Brush );
		var otherIndex = layerList.IndexOf( brush );
		if ( myIndex == -1 || otherIndex == -1 ) return false;

		delta = otherIndex - myIndex;
		return true;
	}

	private static void OpenLineEditFlyout ( string value, string message, Vector2? position, Action<string> onSubmit )
	{
		LineEdit entry = null;

		OpenFlyout( message, position,
			( popup, button ) =>
			{
				entry = new LineEdit( popup ) { Text = value };
				entry.ReturnPressed += () => button.MouseClick?.Invoke();

				popup.Layout.Add( entry );
				entry.Focus();
			},
			() =>
			{
				try
				{
					onSubmit( entry.Value );
				}
				catch ( Exception ex )
				{
					OpenErrorFlyout( ex, position );
				}
			} );
	}

	private static void OpenFlyout ( string message, Vector2? position, Action<PopupWidget, Button> onLayout = null, Action onSubmit = null )
	{
		var popup = new PopupWidget( null );
		popup.Layout = Layout.Column();
		popup.Layout.Margin = 16;
		popup.Layout.Spacing = 8;

		popup.Layout.Add( new Label( message ) );

		var button = new Button.Primary( "Confirm" );

		button.MouseClick += () =>
		{
			onSubmit?.Invoke();
			popup.Close();
		};

		onLayout?.Invoke( popup, button );

		var bottomBar = popup.Layout.AddRow();
		bottomBar.AddStretchCell();
		bottomBar.Add( button );
		popup.Position = position ?? Editor.Application.CursorPosition;
		popup.Visible = true;
	}

	private static void OpenErrorFlyout ( string title, string message, Vector2? position )
	{
		OpenFlyout( $"<h3>{title}</h3><p>{message}</p>", position );
	}

	private static void OpenErrorFlyout ( Exception ex, Vector2? position )
	{
		OpenErrorFlyout( "Error", SecurityElement.Escape( ex.Message ), position );
	}
}