Editor/Internal/NodeEditorPlus/NodeGraph/NodePlug.cs
using Editor;

namespace NodeEditorPlus;

public abstract class NodePlug : GraphicsItem
{
	protected const float handleSize = 14;
	public NodeHandleConfig HandleConfig => Node.Graph.GetHandleConfig( PropertyType );
	protected NodePlug DropTarget => Node.Graph.DropTarget;

	public abstract Vector2 ConnectionPosition { get; }

	public string Title => Inner.DisplayInfo.Name;

	private Type _overridePropertyType;

	public NodeUI Node { get; private set; }
	public IPlug Inner { get; }

	public Type PropertyType
	{
		get => _overridePropertyType ?? Inner.Type;
		set => _overridePropertyType = value;
	}

	public bool Visible { get; set; }
	public bool Dragging { get; protected set; }

	public abstract bool IsConnected { get; }

	public ValueEditor Editor { get; }

	public float DefaultZIndex { get; set; }

	bool IsReconnecting => Reconnection is not null;
	Connection Reconnection;

	public NodePlug( NodeUI node, IPlug plug ) : base( node )
	{
		Size = new Vector2( 24, 24 );
		Node = node;
		Inner = plug;
		Visible = true;
		HoverEvents = node is not RerouteUI;

		Editor = plug.CreateEditor( node, this );

		Cursor = CursorShape.Finger;
	}

	public virtual void Layout()
	{
	}

	protected override void OnHoverEnter( GraphicsHoverEvent e )
	{
		var display = Inner.DisplayInfo;
		ToolTip = NodeUI.FormatToolTip( display.Name, display.Description, PropertyType, null );
		base.OnHoverEnter( e );
	}

	protected void DrawHandle( Color color, Rect handleRect, HandleShape shape )
	{
		if ( !Visible )
			return;

		if ( !string.IsNullOrEmpty( Inner.ErrorMessage ) )
		{
			color = Theme.Red;
		}

		Paint.ClearPen();
		Paint.SetBrush( color.WithAlpha( 1.0f ) );

		switch ( shape )
		{
			case HandleShape.Square:
				Paint.DrawRect( handleRect, 2.0f );
				break;

			case HandleShape.Arrow:
				Paint.DrawPolygon( handleRect.TopLeft, handleRect.Center with { x = handleRect.Right },
					handleRect.BottomLeft );
				break;
		}

	}

	public float PreferredWidth
	{
		get
		{
			if ( !Visible || !Inner.ShowLabel )
			{
				return 0f;
			}

			Paint.SetDefaultFont();

			var titleWidth = Paint.MeasureText( Title ).x;

			if ( Inner.DisplayInfo.Group is not { } groupName )
			{
				return titleWidth + 24f;
			}

			Paint.SetDefaultFont( size: 7, italic: true );
			return Paint.MeasureText( $"{groupName} > " ).x + titleWidth + 24f;
		}
	}

	protected void DrawLabel( Rect rect, bool unreachable, TextFlag flags )
	{
		if ( !Inner.ShowLabel || Inner.InTitleBar || Editor is { Enabled: true, HideLabel: true } )
		{
			return;
		}

		var color = Node.PrimaryColor.Lighten( 0.8f );

		if ( unreachable )
		{
			color = color.WithAlpha( 0.5f );
		}

		Paint.SetPen( color );
		Paint.SetDefaultFont();

		var titleWidth = Paint.MeasureText( Title ).x;
		Paint.DrawText( rect.Shrink( 0f, 0f, 0f, 2f ), Title, flags );

		if ( Inner.DisplayInfo.Group is not { } groupName )
		{
			return;
		}

		Paint.SetPen( color.Darken( 0.2f ) );
		Paint.SetDefaultFont( size: 7, italic: true );

		Paint.DrawText( rect.Shrink( 0f, 0f, titleWidth, 0f ), $"{groupName} > ", flags );
	}

	public override string ToString() => Inner.ToString();

	public void MousePressed( GraphicsMouseEvent ev )
	{
		OnMousePressed( ev );
	}

	public void MouseMove( GraphicsMouseEvent ev )
	{
		OnMouseMove( ev );
	}

	public void MouseReleased( GraphicsMouseEvent ev )
	{
		OnMouseReleased( ev );
	}

	protected override void OnMousePressed( GraphicsMouseEvent e )
	{
		base.OnMousePressed( e );

		if ( e.LeftMouseButton && (!e.HasCtrl && !e.HasShift) )
		{
			if ( Inner is IPlugIn plugIn )
			{
				if ( plugIn.ConnectedOutput is not null )
				{
					var output = plugIn.ConnectedOutput;
					plugIn.ConnectedOutput = null;
					Reconnection = Node.Graph.Items.OfType<Connection>()
						.FirstOrDefault( x => x.Input == this && x.Output.Inner == output );
				}
			}

			e.Accepted = true;
		}
	}

	protected override void OnMouseReleased( GraphicsMouseEvent e )
	{
		if ( Dragging )
		{
			Node.DroppedPlug( this, e.ScenePosition );
		}
		else if ( IsReconnecting )
		{
			Reconnection.Output.Node.DroppedPlug( Reconnection.Output, e.ScenePosition, Reconnection );
			Reconnection = null;
		}

		Dragging = false;
		Cursor = CursorShape.Finger;
		Update();
	}

	protected override void OnMouseMove( GraphicsMouseEvent e )
	{
		// TODO - minimum distance move

		if ( IsReconnecting )
		{
			Reconnection.Output.Node.DraggingPlug( Reconnection.Output, e.ScenePosition, Reconnection );
			return;
		}
		else
		{
			Dragging = true;
			Node.DraggingPlug( this, e.ScenePosition );
		}

		Cursor = CursorShape.DragLink;
		Update();
		e.Accepted = true;
	}
}

public enum HandleShape
{
	Square,
	Arrow
}

public record struct NodeHandleConfig( string Name, Color Color, HandleShape Shape = HandleShape.Square );