SpriteData.cs
namespace Sandbox;

/// <summary>
/// A single coloured pixel within a sprite frame.
/// Stores its position relative to the sprite origin (bottom-left) and its colour.
/// </summary>
public struct PixelData
{
	public PixelPoint Position;
	public Color32 Color;

	public PixelData( PixelPoint position, Color32 color )
	{
		Position = position;
		Color = color;
	}
}

/// <summary>
/// One frame of a sprite animation.
/// Contains the pixel list and the duration (in seconds) before advancing to the next frame.
/// </summary>
public class FrameData
{
	// Sparse pixel list — only non-transparent pixels are stored.
	// Fully transparent pixels are stripped at load time (see SpriteManager.ParseFramePixels)
	// so Draw loops only touch pixels that will actually write something.
	public List<PixelData> Pixels { get; set; }
	public float AnimTime { get; set; }

	/// <summary>
	/// Optional per-frame hitbox override. When null, the animation-level
	/// <see cref="AnimationData.Hitbox"/> is used instead.
	/// </summary>
	public PixelRect? Hitbox { get; set; }

	// Lazily-computed flipped variants. Pixel data is immutable after load,
	// so these are computed once and reused on every subsequent Draw call.
	private List<PixelData> _cachedFlipX;
	private List<PixelData> _cachedFlipY;
	private List<PixelData> _cachedFlipXY;

	public FrameData() { }

	public FrameData( List<PixelData> pixels, float animTime = 0f, PixelRect? hitbox = null )
	{
		Pixels = pixels;
		AnimTime = animTime;
		Hitbox = hitbox;
	}

	/// <summary>
	/// Return a copy of the pixel list with X coordinates mirrored within the given width.
	/// Result is cached after the first call.
	/// </summary>
	public List<PixelData> GetPixelsFlippedX( int width )
	{
		if ( _cachedFlipX is not null )
			return _cachedFlipX;

		_cachedFlipX = new List<PixelData>( Pixels.Count );
		foreach ( PixelData p in Pixels )
			_cachedFlipX.Add( new PixelData( new PixelPoint( width - 1 - p.Position.X, p.Position.Y ), p.Color ) );
		return _cachedFlipX;
	}

	/// <summary>
	/// Return a copy of the pixel list with Y coordinates mirrored within the given height.
	/// Result is cached after the first call.
	/// </summary>
	public List<PixelData> GetPixelsFlippedY( int height )
	{
		if ( _cachedFlipY is not null )
			return _cachedFlipY;

		_cachedFlipY = new List<PixelData>( Pixels.Count );
		foreach ( PixelData p in Pixels )
			_cachedFlipY.Add( new PixelData( new PixelPoint( p.Position.X, height - 1 - p.Position.Y ), p.Color ) );
		return _cachedFlipY;
	}

	/// <summary>
	/// Return a copy of the pixel list mirrored on both axes.
	/// Result is cached after the first call.
	/// </summary>
	public List<PixelData> GetPixelsFlippedXY( int width, int height )
	{
		if ( _cachedFlipXY is not null )
			return _cachedFlipXY;

		_cachedFlipXY = new List<PixelData>( Pixels.Count );
		foreach ( PixelData p in Pixels )
			_cachedFlipXY.Add( new PixelData( new PixelPoint( width - 1 - p.Position.X, height - 1 - p.Position.Y ), p.Color ) );
		return _cachedFlipXY;
	}
}

/// <summary>
/// Defines a single named animation for a sprite, including all frames,
/// the bounding size, optional hitbox, and loop mode.
/// </summary>
public class AnimationData
{
	public string Name { get; set; }
	public List<FrameData> Frames { get; set; }
	public PixelPoint AnimSize { get; set; }
	public PixelRect Hitbox { get; set; }
	public LoopMode LoopMode { get; set; }

	public AnimationData() { }

	public AnimationData( string name, List<FrameData> frames, PixelPoint animSize, PixelRect hitbox, LoopMode loopMode )
	{
		Name = name;
		Frames = frames;
		AnimSize = animSize;
		Hitbox = hitbox;
		LoopMode = loopMode;
	}
}

/// <summary>
/// How an animation plays back after reaching the last frame.
/// </summary>
public enum LoopMode
{
	/// <summary>Restart from frame 0 after the last frame.</summary>
	Loops,
	/// <summary>Play once then stop on the last frame.</summary>
	PlayOnce,
	/// <summary>Alternate forward and backward through the frames.</summary>
	PingPong,
	/// <summary>Jump to a random frame each cycle.</summary>
	RandomFrame
}