Editor/Sprite/SpriteEditor/AnimationList/AnimationButton.cs
using Editor;
using Sandbox;
using System.Collections.Generic;
using System.Linq;

namespace SpriteTools.SpriteEditor.AnimationList;

internal class AnimationButton : Widget
{
	MainWindow MainWindow;
	AnimationList AnimationList;
	public SpriteAnimation Animation;

	public bool Selected => Animation == MainWindow?.SelectedAnimation;
	LabelTextEntry labelText;

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

	public AnimationButton ( AnimationList list, MainWindow window, SpriteAnimation animation ) : base( null )
	{
		AnimationList = list;
		MainWindow = window;
		Animation = animation;
		StatusTip = $"Select {Animation.Name} Animation";
		Cursor = CursorShape.Finger;

		if ( Animation.Name == MainWindow?.SelectedAnimation?.Name )
		{
			MainWindow.SelectedAnimation = Animation;
		}

		Layout = Layout.Row();
		Layout.Margin = 4;

		var serializedObject = Animation.GetSerialized();
		serializedObject.TryGetProperty( nameof( SpriteAnimation.Name ), out var name );
		labelText = new LabelTextEntry( name );
		labelText.OnStopEditing = ( value ) =>
		{
			if ( string.IsNullOrEmpty( value ) || MainWindow.Sprite.Animations.Where( a => a.Name.ToLowerInvariant() == value.ToLowerInvariant() ).Count() > 1 )
			{
				labelText.Property.SetValue( labelText.lastSafeValue );
				AnimationList.ShowNamingError( value );
			}
			else
			{
				labelText.Property.SetValue( labelText.lastSafeValue );
				MainWindow.PushUndo( "Rename Animation" );
				labelText.Property.SetValue( value );
				MainWindow.PushRedo();
			}
			return false;
		};

		Layout.Add( labelText );

		var duplicateButton = new IconButton( "content_copy" );
		duplicateButton.ToolTip = "Duplicate";
		duplicateButton.StatusTip = "Duplicate Animation";
		duplicateButton.OnClick += () =>
		{
			DuplicateAnimationPopup();
		};
		Layout.Add( duplicateButton );

		Layout.AddSpacingCell( 4 );

		var deleteButton = new IconButton( "delete" );
		deleteButton.ToolTip = "Delete";
		deleteButton.StatusTip = "Delete Animation";
		deleteButton.OnClick += () =>
		{
			DeleteAnimationPopup();
		};
		Layout.Add( deleteButton );

		IsDraggable = true;
		AcceptDrops = true;
	}

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

		var m = new Menu( this );

		m.AddOption( "Rename", "edit", Rename );
		m.AddOption( "Duplicate", "content_copy", DuplicateAnimationPopup );
		m.AddOption( "Delete", "delete", DeleteAnimationPopup );

		m.OpenAtCursor( false );
	}

	protected override void OnPaint ()
	{
		if ( Selected )
		{
			Paint.SetBrushAndPen( Theme.Highlight.WithAlpha( 0.5f ) );
			Paint.DrawRect( LocalRect );
		}
		else if ( IsUnderMouse )
		{
			Paint.SetBrushAndPen( Theme.Text.WithAlpha( 0.1f ) );
			Paint.DrawRect( LocalRect );
		}

		if ( dragData?.IsValid ?? false )
		{
			Paint.SetBrushAndPen( Theme.WindowBackground.WithAlpha( 0.5f ) );
			Paint.DrawRect( LocalRect );
		}

		base.OnPaint();

		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 DeleteAnimationPopup ()
	{
		var popup = new PopupWidget( MainWindow );
		popup.Layout = Layout.Column();
		popup.Layout.Margin = 16;
		popup.Layout.Spacing = 8;

		popup.Layout.Add( new Label( $"Are you sure you want to delete this animation?" ) );

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


		button.MouseClick = () =>
		{
			Delete();
			popup.Visible = false;
		};

		popup.Layout.Add( button );

		var bottomBar = popup.Layout.AddRow();
		bottomBar.AddStretchCell();
		bottomBar.Add( button );

		popup.Position = Editor.Application.CursorPosition;
		popup.Visible = true;
	}

	void DuplicateAnimationPopup ()
	{
		var popup = new PopupWidget( MainWindow );
		popup.Layout = Layout.Column();
		popup.Layout.Margin = 16;
		popup.Layout.Spacing = 8;

		popup.Layout.Add( new Label( $"What would you like to name the duplicated animation?" ) );

		var entry = new LineEdit( popup );
		entry.Text = $"{Animation.Name} 2";
		var button = new Button.Primary( "Duplicate" );

		button.MouseClick = () =>
		{
			Duplicate( entry.Text );
			popup.Visible = false;
		};

		entry.ReturnPressed += button.MouseClick;

		popup.Layout.Add( entry );

		var bottomBar = popup.Layout.AddRow();
		bottomBar.AddStretchCell();
		bottomBar.Add( button );

		popup.Position = Editor.Application.CursorPosition;
		popup.Visible = true;

		entry.Focus();
	}

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

		dragData = new Drag( this );
		dragData.Data.Object = Animation;
		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;

		MainWindow.PushUndo( "Re-Order Animations" );

		var list = MainWindow.Sprite.Animations;
		var index = list.IndexOf( Animation );
		var movingIndex = index + delta;
		var anim = list[movingIndex];

		MainWindow.Sprite.Animations.RemoveAt( movingIndex );
		MainWindow.Sprite.Animations.Insert( index, anim );

		MainWindow.PushRedo();

		AnimationList.UpdateAnimationList();
	}

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

		if ( animation == null || Animation == null || animation == Animation )
		{
			return false;
		}

		var animationList = MainWindow.Sprite.Animations;
		var myIndex = animationList.IndexOf( Animation );
		var otherIndex = animationList.IndexOf( animation );

		if ( myIndex == -1 || otherIndex == -1 )
		{
			return false;
		}

		delta = otherIndex - myIndex;
		return true;
	}

	void Rename ()
	{
		labelText.Edit();
	}

	void Delete ()
	{
		MainWindow.PushUndo( $"Delete Animation {Animation.Name}" );
		MainWindow.Sprite.Animations.Remove( Animation );
		AnimationList.UpdateAnimationList();
		MainWindow.PushRedo();
	}

	void Duplicate ( string name )
	{
		if ( !string.IsNullOrEmpty( name ) && !MainWindow.Sprite.Animations.Any( a => a.Name.ToLowerInvariant() == name.ToLowerInvariant() ) )
		{
			MainWindow.PushUndo( $"Duplicate Animation {Animation.Name}" );
			var newAnimation = new SpriteAnimation( name )
			{
				FrameRate = Animation.FrameRate,
				Origin = Animation.Origin,
				LoopMode = Animation.LoopMode,
			};
			if ( Animation.Frames is not null )
			{
				newAnimation.Frames = new List<SpriteAnimationFrame>();
				foreach ( var frame in Animation.Frames )
				{
					newAnimation.Frames.Add( frame.Copy() );
				}
			}
			if ( Animation.Attachments is not null )
			{
				newAnimation.Attachments = new List<SpriteAttachment>();
				foreach ( var attachment in Animation.Attachments )
				{
					newAnimation.Attachments.Add( attachment.Copy() );
				}
			}
			int index = MainWindow.Sprite.Animations.IndexOf( Animation );
			MainWindow.Sprite.Animations.Insert( index + 1, newAnimation );
			AnimationList.UpdateAnimationList();
			MainWindow.PushRedo();
		}
		else
		{
			AnimationList.ShowNamingError( name );
		}
	}
}