Authoring/GooModernUiValues.cs
using System;
using Sandbox;
using Sandbox.UI;

namespace Goo.Authoring;

public struct GooGradientStop : IEquatable<GooGradientStop>
{
	public Color Color { get; set; }
	public GooLength? Position { get; set; }

	public static implicit operator GradientStop( GooGradientStop stop )
		=> new( stop.Color, stop.Position.HasValue ? (Length)stop.Position.Value : null );

	public static implicit operator GooGradientStop( GradientStop stop )
		=> new()
		{
			Color = stop.Color,
			Position = stop.Position.HasValue ? (GooLength)stop.Position.Value : null,
		};

	public readonly bool Equals( GooGradientStop other )
		=> Color == other.Color && Nullable.Equals( Position, other.Position );

	public readonly override bool Equals( object? obj ) => obj is GooGradientStop other && Equals( other );
	public readonly override int GetHashCode() => HashCode.Combine( Color, Position );
}

public struct GooGradient : IEquatable<GooGradient>
{
	public GradientKind Kind { get; set; }
	[ShowIf( nameof( Kind ), GradientKind.Linear )]
	public LinearGradientDirection LinearDirection { get; set; }
	[HideIf( nameof( Kind ), GradientKind.Radial )]
	public float AngleDegrees { get; set; }
	[ShowIf( nameof( Kind ), GradientKind.Radial )]
	public RadialGradientShape RadialShape { get; set; }
	[ShowIf( nameof( Kind ), GradientKind.Radial )]
	public RadialGradientExtent RadialExtent { get; set; }
	[HideIf( nameof( Kind ), GradientKind.Linear )]
	public GooLength CenterX { get; set; }
	[HideIf( nameof( Kind ), GradientKind.Linear )]
	public GooLength CenterY { get; set; }
	public GooGradientStop[] Stops { get; set; }

	public GooGradient()
	{
		Kind = GradientKind.Linear;
		LinearDirection = LinearGradientDirection.Angle;
		AngleDegrees = 180f;
		RadialShape = RadialGradientShape.Ellipse;
		RadialExtent = RadialGradientExtent.FarthestCorner;
		CenterX = Percent( 50f );
		CenterY = Percent( 50f );
		Stops =
		[
			new GooGradientStop { Color = Color.White },
			new GooGradientStop { Color = Color.Black },
		];
	}

	public static implicit operator Gradient( GooGradient value )
	{
		var source = value.Stops ?? [];
		var stops = new GradientStop[source.Length];
		for ( int i = 0; i < source.Length; i++ ) stops[i] = source[i];

		return value.Kind switch
		{
			GradientKind.Linear => value.LinearDirection == LinearGradientDirection.Angle
				? Gradient.Linear( value.AngleDegrees, stops )
				: Gradient.LinearTo( value.LinearDirection, stops ),
			GradientKind.Radial => (value.RadialShape == RadialGradientShape.Circle
				? Gradient.RadialCircle( stops )
				: Gradient.Radial( stops ))
				.WithExtent( value.RadialExtent )
				.At( Center( value.CenterX ), Center( value.CenterY ) ),
			GradientKind.Conic => Gradient.Conic( value.AngleDegrees, stops )
				.At( Center( value.CenterX ), Center( value.CenterY ) ),
			_ => throw new ArgumentOutOfRangeException( nameof( value.Kind ) ),
		};
	}

	public static implicit operator GooGradient( Gradient value )
	{
		var stops = new GooGradientStop[value.Count];
		for ( int i = 0; i < value.Count; i++ ) stops[i] = value[i];
		return new GooGradient
		{
			Kind = value.Kind,
			LinearDirection = value.LinearDirection,
			AngleDegrees = value.AngleDegrees,
			RadialShape = value.RadialShape,
			RadialExtent = value.RadialExtent,
			CenterX = value.IsEmpty ? Percent( 50f ) : (GooLength)value.CenterX,
			CenterY = value.IsEmpty ? Percent( 50f ) : (GooLength)value.CenterY,
			Stops = stops,
		};
	}

	static GooLength Percent( float value ) => new() { Value = value, Unit = LengthUnit.Percentage };

	static Length Center( GooLength value )
	{
		var center = (Length)value;
		return center.Unit is LengthUnit.Pixels or LengthUnit.Percentage && float.IsFinite( center.Value )
			? center
			: new Length { Value = 50f, Unit = LengthUnit.Percentage };
	}

	public readonly bool Equals( GooGradient other )
	{
		if ( Kind != other.Kind || LinearDirection != other.LinearDirection || AngleDegrees != other.AngleDegrees
			|| RadialShape != other.RadialShape || RadialExtent != other.RadialExtent
			|| !CenterX.Equals( other.CenterX ) || !CenterY.Equals( other.CenterY ) )
			return false;
		var left = Stops ?? [];
		var right = other.Stops ?? [];
		var count = left.Length;
		if ( count != right.Length ) return false;
		for ( int i = 0; i < count; i++ )
			if ( !left[i].Equals( right[i] ) ) return false;
		return true;
	}

	public readonly override bool Equals( object? obj ) => obj is GooGradient other && Equals( other );

	public readonly override int GetHashCode()
	{
		var hash = new HashCode();
		hash.Add( Kind );
		hash.Add( LinearDirection );
		hash.Add( AngleDegrees );
		hash.Add( RadialShape );
		hash.Add( RadialExtent );
		hash.Add( CenterX );
		hash.Add( CenterY );
		if ( Stops is not null )
			foreach ( var stop in Stops ) hash.Add( stop );
		return hash.ToHashCode();
	}
}

public struct GooPanelShape : IEquatable<GooPanelShape>
{
	public PanelShapeKind Kind { get; set; }
	[ShowIf( nameof( Kind ), PanelShapeKind.Circle )]
	public GooLength? CircleRadius { get; set; }
	[ShowIf( nameof( Kind ), PanelShapeKind.Circle )]
	public GooLength CircleCenterX { get; set; }
	[ShowIf( nameof( Kind ), PanelShapeKind.Circle )]
	public GooLength CircleCenterY { get; set; }
	[ShowIf( nameof( Kind ), PanelShapeKind.Polygon )]
	public GooPanelShapePoint[] Points { get; set; }

	public GooPanelShape()
	{
		Kind = PanelShapeKind.Circle;
		CircleRadius = null;
		CircleCenterX = Percent( 50f );
		CircleCenterY = Percent( 50f );
		Points =
		[
			GooPanelShapePoint.Percent( 50f, 0f ),
			GooPanelShapePoint.Percent( 100f, 100f ),
			GooPanelShapePoint.Percent( 0f, 100f ),
		];
	}

	public static implicit operator PanelShape( GooPanelShape value )
		=> value.Kind switch
		{
			PanelShapeKind.None => PanelShape.None,
			PanelShapeKind.Circle => PanelShape.Circle(
				value.CircleRadius.HasValue ? (Length)value.CircleRadius.Value : null,
				Center( value.CircleCenterX ),
				Center( value.CircleCenterY ) ),
			PanelShapeKind.Polygon => Polygon( value.Points ),
			_ => throw new ArgumentOutOfRangeException( nameof( value.Kind ) ),
		};

	public static implicit operator GooPanelShape( PanelShape value )
	{
		var points = new GooPanelShapePoint[value.Count];
		for ( int i = 0; i < value.Count; i++ )
			points[i] = value[i];

		return new GooPanelShape
		{
			Kind = value.Kind,
			CircleRadius = value.CircleRadius.HasValue ? (GooLength)value.CircleRadius.Value : null,
			CircleCenterX = value.Kind == PanelShapeKind.Circle ? (GooLength)value.CircleCenterX : Percent( 50f ),
			CircleCenterY = value.Kind == PanelShapeKind.Circle ? (GooLength)value.CircleCenterY : Percent( 50f ),
			Points = points,
		};
	}

	static GooLength Percent( float value ) => new() { Value = value, Unit = LengthUnit.Percentage };

	static Length Center( GooLength value )
	{
		var center = (Length)value;
		return center.Unit is LengthUnit.Pixels or LengthUnit.Percentage && float.IsFinite( center.Value )
			? center
			: new Length { Value = 50f, Unit = LengthUnit.Percentage };
	}

	static PanelShape Polygon( GooPanelShapePoint[] points )
	{
		var source = points ?? [];
		var typed = new PanelShapePoint[source.Length];
		for ( int i = 0; i < source.Length; i++ ) typed[i] = source[i];
		return PanelShape.Polygon( typed );
	}

	public readonly bool Equals( GooPanelShape other )
	{
		if ( Kind != other.Kind || !Nullable.Equals( CircleRadius, other.CircleRadius )
			|| !CircleCenterX.Equals( other.CircleCenterX ) || !CircleCenterY.Equals( other.CircleCenterY ) )
			return false;
		var left = Points ?? [];
		var right = other.Points ?? [];
		var count = left.Length;
		if ( count != right.Length ) return false;
		for ( int i = 0; i < count; i++ )
			if ( !left[i].Equals( right[i] ) ) return false;
		return true;
	}

	public readonly override bool Equals( object? obj ) => obj is GooPanelShape other && Equals( other );

	public readonly override int GetHashCode()
	{
		var hash = new HashCode();
		hash.Add( Kind );
		hash.Add( CircleRadius );
		hash.Add( CircleCenterX );
		hash.Add( CircleCenterY );
		if ( Points is not null )
			foreach ( var point in Points ) hash.Add( point );
		return hash.ToHashCode();
	}
}

public struct GooPanelShapePoint : IEquatable<GooPanelShapePoint>
{
	public GooLength X { get; set; }
	public GooLength Y { get; set; }

	public static GooPanelShapePoint Percent( float x, float y )
		=> new()
		{
			X = new GooLength { Value = x, Unit = LengthUnit.Percentage },
			Y = new GooLength { Value = y, Unit = LengthUnit.Percentage },
		};

	public static implicit operator PanelShapePoint( GooPanelShapePoint point )
		=> new( point.X, point.Y );

	public static implicit operator GooPanelShapePoint( PanelShapePoint point )
		=> new() { X = point.X, Y = point.Y };

	public readonly bool Equals( GooPanelShapePoint other )
		=> X.Equals( other.X ) && Y.Equals( other.Y );

	public readonly override bool Equals( object? obj ) => obj is GooPanelShapePoint other && Equals( other );
	public readonly override int GetHashCode() => HashCode.Combine( X, Y );
}