TilesetDoc.cs
namespace PixelPusher;

public enum TileFlag
{
	None = 0,
	Solid = 1,
	OneWay = 2,
	Hazard = 4,
	Spring = 8,
	Climb = 16,
	Ice = 32,
	Breakable = 64
}

public sealed class TilesetDoc
{
	public string Name { get; set; } = "tileset";
	public int TileSize { get; set; } = 16;
	public int Columns { get; set; } = 8;
	public PixelDoc Sheet { get; set; }
	public TileFlag[] Flags { get; set; }
	public int FlagVersion { get; private set; }
	public int Version => (Sheet?.Version ?? 0) + FlagVersion;

	public int TileCount => Flags?.Length ?? 0;
	public int Rows => Sheet is null || TileSize <= 0 ? 1 : Math.Max( 1, Sheet.Height / TileSize );

	public TilesetDoc( PixelDoc sheet, int tileSize = 16 )
	{
		Sheet = sheet ?? new PixelDoc( 128, 64 );
		TileSize = Math.Clamp( tileSize, 8, 64 );
		Columns = Math.Max( 1, Sheet.Width / TileSize );
		var count = Math.Max( 1, Columns * Math.Max( 1, Sheet.Height / TileSize ) );
		Flags = new TileFlag[count];
		Name = sheet?.Name ?? "tileset";
	}

	public int TileIndex( int tx, int ty ) => ty * Columns + tx;

	public void TileOrigin( int id, out int ox, out int oy )
	{
		if ( id <= 0 || Columns <= 0 )
		{
			ox = 0;
			oy = 0;
			return;
		}
		var i = id;
		ox = (i % Columns) * TileSize;
		oy = (i / Columns) * TileSize;
	}

	public TileFlag Flag( int id )
	{
		if ( Flags is null || (uint)id >= (uint)Flags.Length )
			return TileFlag.None;
		return Flags[id];
	}

	public bool IsSolid( int id ) => (Flag( id ) & TileFlag.Solid) != 0;
	public bool IsOneWay( int id ) => (Flag( id ) & TileFlag.OneWay) != 0;
	public bool IsHazard( int id ) => (Flag( id ) & TileFlag.Hazard) != 0;
	public bool IsSpring( int id ) => (Flag( id ) & TileFlag.Spring) != 0;

	public void SetFlag( int id, TileFlag flag )
	{
		if ( Flags is null || (uint)id >= (uint)Flags.Length )
			return;
		if ( Flags[id] == flag )
			return;
		Flags[id] = flag;
		FlagVersion++;
	}

	public static TileFlag FlagFromRole( StampRole role ) => role switch
	{
		StampRole.Solid => TileFlag.Solid,
		StampRole.OneWay => TileFlag.OneWay,
		StampRole.Hazard => TileFlag.Hazard,
		StampRole.Spring => TileFlag.Spring,
		_ => TileFlag.None
	};

	public static TileFlag CycleFlag( TileFlag flag ) => flag switch
	{
		TileFlag.None => TileFlag.Solid,
		TileFlag.Solid => TileFlag.OneWay,
		TileFlag.OneWay => TileFlag.Hazard,
		TileFlag.Hazard => TileFlag.Spring,
		_ => TileFlag.None
	};

	public static string FlagName( TileFlag flag ) => flag switch
	{
		TileFlag.Solid => "SOLID",
		TileFlag.OneWay => "ONE-WAY",
		TileFlag.Hazard => "HAZARD",
		TileFlag.Spring => "SPRING",
		_ => "DECOR"
	};

	public string PreviewHex( int id )
	{
		if ( Sheet is null || id <= 0 )
			return "#1a2450";
		TileOrigin( id, out var ox, out var oy );
		var r = 0f;
		var g = 0f;
		var b = 0f;
		var n = 0;
		var ts = TileSize;
		for ( var y = 0; y < ts; y += 2 )
		for ( var x = 0; x < ts; x += 2 )
		{
			Color c = Sheet.ColorAt( ox + x, oy + y );
			if ( c.a < 0.04f )
				continue;
			r += c.r;
			g += c.g;
			b += c.b;
			n++;
		}
		if ( n == 0 )
			return "#1a2450";
		return $"#{(int)MathF.Round( r / n * 255f ):x2}{(int)MathF.Round( g / n * 255f ):x2}{(int)MathF.Round( b / n * 255f ):x2}";
	}

	public void StampFromSprite( PixelDoc sprite, int tileId )
	{
		if ( sprite is null || Sheet is null || tileId <= 0 )
			return;
		TileOrigin( tileId, out var ox, out var oy );
		var ts = TileSize;
		Sheet.BeginStroke();
		for ( var y = 0; y < ts; y++ )
		for ( var x = 0; x < ts; x++ )
		{
			var sx = sprite.Width == ts ? x : x * sprite.Width / ts;
			var sy = sprite.Height == ts ? y : y * sprite.Height / ts;
			Sheet.SetIndex( ox + x, oy + y, (byte)sprite.IndexAt( sx, sy ) );
		}
	}

	public TilesetSave ToSave()
	{
		var flags = new int[Flags.Length];
		for ( var i = 0; i < Flags.Length; i++ )
			flags[i] = (int)Flags[i];
		return new TilesetSave
		{
			Name = Name,
			TileSize = TileSize,
			Columns = Columns,
			Sheet = Sheet?.ToSave(),
			Flags = flags
		};
	}

	public static TilesetDoc FromSave( TilesetSave save )
	{
		if ( save is null )
			return StarterPack.CornerStore();
		var sheet = PixelDoc.FromSave( save.Sheet );
		var doc = new TilesetDoc( sheet, save.TileSize > 0 ? save.TileSize : 16 )
		{
			Name = save.Name ?? "tileset",
			Columns = save.Columns > 0 ? save.Columns : Math.Max( 1, sheet.Width / 16 )
		};
		if ( save.Flags is { Length: > 0 } )
		{
			doc.Flags = new TileFlag[Math.Max( doc.Flags.Length, save.Flags.Length )];
			for ( var i = 0; i < save.Flags.Length && i < doc.Flags.Length; i++ )
				doc.Flags[i] = (TileFlag)save.Flags[i];
		}
		return doc;
	}
}

public sealed class TilesetSave
{
	public string Name { get; set; }
	public int TileSize { get; set; }
	public int Columns { get; set; }
	public PixelSave Sheet { get; set; }
	public int[] Flags { get; set; }
}