Editor/GraphicsItems/EditableAltCurve.KeyVisible.cs
using Editor;
using System;
using static AltCurves.AltCurve;

namespace AltCurves.GraphicsItems;
public partial class EditableAltCurve
{
	/// <summary>
	/// The visible keyframe dragged via KeyframeHandle
	/// Parent of the visible movable tangent handles
	/// </summary>
	public class KeyVisible : GraphicsItem
	{
		/// <summary>
		/// Index of the keyframe in the curve
		/// </summary>
		public int Index { get; init; }

		public Keyframe Keyframe
		{
			get => _keyframe;
			set
			{
				_keyframe = value;
				Position = _transform.CurveToWidgetPosition( new( _keyframe.Time, _keyframe.Value ) );

				TangentIn.Tangent = _keyframe.TangentIn;
				TangentOut.Tangent = _keyframe.TangentOut;
				UpdateTangentVisiblity();

				Update();
			}
		}
		private Keyframe _keyframe;

		/// <summary>
		/// Selected via drag/click, we can't use normal widget Selectable without interfering with dragging
		/// </summary>
		public bool UserSelected
		{
			get => _userSelected;
			set
			{
				_userSelected = value;

				// Enable/disable tangent controls if we're showing handles on selection only
				if ( _tangentViewMode == TangentViewMode.Selected )
					UpdateTangentVisiblity();
			}
		}
		private bool _userSelected = false;

		/// <summary>
		/// True while this keyframe is in an invalid state (shares a time with another keyframe)
		/// The user is expected to fix this, or lose the key when they close the editor.
		/// </summary>
		public bool InvalidKeyframe { get; set; } = false;

		/// <summary>
		/// Tangent view mode controlling the conditions for drawing tangent handles
		/// </summary>
		public TangentViewMode TangentMode
		{
			get => _tangentViewMode;
			set
			{
				_tangentViewMode = value;
				UpdateTangentVisiblity();
			}
		}
		private TangentViewMode _tangentViewMode;

		/// <summary>
		/// Transform for converting curve/widget space
		/// </summary>
		public CurveWidgetTransform Transform
		{
			get => _transform;
			set
			{
				_transform = value;
				Position = _transform.CurveToWidgetPosition( new( Keyframe.Time, Keyframe.Value ) );
				TangentIn.Transform = value;
				TangentOut.Transform = value;
				Update();
			}
		}
		private CurveWidgetTransform _transform;

		/// <summary>
		/// Should we draw the tangents for this keyframe, based on interpolation type + user settings
		/// </summary>
		public bool TangentsVisible =>  _keyframe.Interpolation == Interpolation.Cubic && (TangentMode == TangentViewMode.All || (TangentMode == TangentViewMode.Selected && UserSelected));

		public KeyTangent TangentIn { get; init; }
		public KeyTangent TangentOut { get; init; }

		private bool _isFirst = false; // JMCB TODO: Not accurate for raw curve
		private bool _isLast = false;

		public KeyVisible( int index, int totalKeyframes, CurveWidgetTransform transform, AltCurve.Keyframe keyframe, TangentViewMode tangentViewMode, GraphicsItem parent ) : base( parent )
		{
			Index = index;
			_isFirst = index == 0;
			_isLast = index == totalKeyframes - 1;
			_keyframe = keyframe;
			_tangentViewMode = tangentViewMode;

			HandlePosition = 0.5f;
			Movable = false;
			HoverEvents = false;
			Clip = false;
			Selectable = false;

			Size = new( 10.0f );
			_transform = transform;
			Position = _transform.CurveToWidgetPosition( new( Keyframe.Time, Keyframe.Value ) );

			TangentIn = new KeyTangent( this, transform, keyframe.TangentIn, true );
			TangentOut = new KeyTangent( this, transform, keyframe.TangentOut, false );
			UpdateTangentVisiblity();
		}

		private void UpdateTangentVisiblity()
		{
			TangentIn.Hidden = !TangentsVisible/* || _isFirst*/; // jmcb todo
			TangentOut.Hidden = !TangentsVisible/* || _isLast*/;
			Update();
		}

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

			if ( InvalidKeyframe )
			{
				Paint.SetBrush( Theme.Red );
				Paint.SetDefaultFont();
				Paint.DrawTextBox( LocalRect, "X", Color.White, new( 2.0f, 0.0f, 1.0f, 0.0f ), 1.0f, Sandbox.TextFlag.CenterHorizontally );
				return;
			}

			if ( !TangentIn.Hidden )
			{
				float inAngle = MathF.Atan( _keyframe.TangentIn * _transform.WidgetCurveAspectRatio );
				var inOffset = new Vector2( -MathF.Cos( inAngle ), MathF.Sin( inAngle ) ) * KeyTangent.TANGENT_RADIUS;

				Paint.SetPen( Color.White.WithAlpha( 0.4f ), style: PenStyle.Dash );
				Paint.DrawLine( Size * 0.5f, inOffset + (TangentIn.Size * 0.5f) );

				Paint.SetPen( Color.White.WithAlpha( 0.8f ) );
				Paint.SetBrush( TangentIn.Hovered ? Theme.Red : Theme.Red.Darken( 0.3f ) );
				Paint.DrawCircle( inOffset + (TangentIn.Size * 0.5f), TangentIn.Size - 3.0f );
			}

			if ( !TangentOut.Hidden )
			{
				float outAngle = MathF.Atan( _keyframe.TangentOut * _transform.WidgetCurveAspectRatio );
				var outOffset = new Vector2( MathF.Cos( outAngle ), -MathF.Sin( outAngle ) ) * KeyTangent.TANGENT_RADIUS;

				Paint.SetPen( Color.White.WithAlpha( 0.4f ), style: PenStyle.Dash );
				Paint.DrawLine( Size * 0.5f, outOffset + (TangentOut.Size * 0.5f) );

				Paint.SetPen( Color.White.WithAlpha( 0.8f ) );
				Paint.SetBrush( TangentOut.Hovered ? Theme.Blue : Theme.Blue.Darken( 0.3f ) );
				Paint.DrawCircle( outOffset + (TangentOut.Size * 0.5f), TangentOut.Size - 3.0f );
			}

			// Per-interpolation icons
			Paint.SetPen( Color.Black );
			Paint.SetBrush( UserSelected ? Color.White : Color.White.Darken( 0.3f ) );
			switch ( _keyframe.Interpolation )
			{
				case Interpolation.Linear:
					// Triangle
					Paint.DrawPolygon( new Vector2[] {
						new(0, Size.y),
						new(Size.x * 0.5f, 0.0f),
						new(Size.x, Size.y)
					} );
					break;
				case Interpolation.Constant:
					// Box
					Paint.DrawRect( LocalRect.Shrink( 1f ) );
					break;
				case Interpolation.Cubic:
					// Diamond
					// Tint slightly green if auto
					if (_keyframe.TangentMode == AltCurve.TangentMode.Automatic)
						Paint.SetBrush( UserSelected ? Theme.Green.Desaturate( 0.5f ) : Theme.Green.Desaturate( 0.5f ).Darken( 0.3f )  );
					// Or blue if broken
					else if ( _keyframe.TangentMode == AltCurve.TangentMode.Split)
						Paint.SetBrush( UserSelected ? Theme.Blue.Desaturate( 0.4f ) : Theme.Blue.Desaturate( 0.4f ).Darken( 0.4f ) );

					Paint.DrawPolygon( new Vector2[] {
						new(Size.x * 0.5f, 0.0f),
						new(0, Size.y * 0.5f),
						new(Size.x * 0.5f, Size.y),
						new(Size.x, Size.y * 0.5f)
					} );
					break;
			}
		}
	}
}