UI/FlagSheet.cs
namespace BlockParty;

/// <summary>
/// Country flags packed into one CSS-sprite sheet (tools/bake.py convert_flag_sheet):
/// sprites/flags_sheet.png + sprites/flags.json (per-flag pixel rects). <see cref="Style"/>
/// reproduces the old loose-png look — `background-size: cover; background-position: center`
/// inside a fixed box — by scaling the whole sheet so the flag covers the box and windowing
/// the box onto it. The cover window is always a sub-rect of the flag, so neighbouring
/// cells can't bleed in.
/// </summary>
public static class FlagSheet
{
	private class Manifest
	{
		public string Sheet { get; set; }
		public int[] SheetSize { get; set; }
		public Dictionary<string, int[]> Flags { get; set; }
	}

	private static Manifest _manifest;
	private static bool _loadTried;

	private static Manifest Load()
	{
		if ( _manifest is null && !_loadTried )
		{
			// Don't latch a failed load: the mounted filesystem can lag at boot (same race
			// Levels/DailyTemplates guard against).
			try
			{
				if ( FileSystem.Mounted.FileExists( "sprites/flags.json" ) )
				{
					_manifest = Json.Deserialize<Manifest>( FileSystem.Mounted.ReadAllText( "sprites/flags.json" ) );
					_loadTried = true;
				}
			}
			catch ( Exception ex )
			{
				Log.Warning( $"[BlockParty] flags.json failed to load: {ex.Message}" );
				_loadTried = true;
			}
		}
		return _manifest;
	}

	public static bool Has( string code )
		=> !string.IsNullOrEmpty( code ) && Load()?.Flags?.ContainsKey( code ) == true;

	/// <summary>English country or organization name for the flag's hover tooltip.</summary>
	public static string CountryName( string code )
	{
		if ( string.IsNullOrWhiteSpace( code ) ) return "";
		code = code.ToUpperInvariant();
		// These bundled flags are not recognized by RegionInfo on every platform.
		if ( code == "EH" ) return "Western Sahara";
		if ( code == "EU" ) return "European Union";
		if ( code == "UN" ) return "United Nations";
		try
		{
			return new System.Globalization.RegionInfo( code ).EnglishName;
		}
		catch ( ArgumentException )
		{
			return code;
		}
	}

	/// <summary>Inline style drawing the flag cover+centered in a boxW x boxH element.
	/// Everything is emitted in PERCENTAGES of the box: the engine multiplies pixel
	/// background-sizes by ScaleToScreen but leaves pixel background-positions unscaled
	/// (ImageRect.Calculate), so px units fall apart under the 1080-reference ScreenPanel
	/// scaling — percentages of the panel box are scale-invariant on both.</summary>
	public static string Style( string code, float boxW, float boxH )
	{
		var manifest = Load();
		if ( manifest is null || !manifest.Flags.TryGetValue( code, out var rect ) )
			return "";

		float x = rect[0], y = rect[1], w = rect[2], h = rect[3];
		float scale = MathF.Max( boxW / w, boxH / h );

		// Window the box onto the scaled sheet so the flag's centre lands on the box centre.
		float posX = boxW * 0.5f - (x + w * 0.5f) * scale;
		float posY = boxH * 0.5f - (y + h * 0.5f) * scale;

		return string.Create( System.Globalization.CultureInfo.InvariantCulture,
			$"background-image: url( {manifest.Sheet} ); " +
			$"background-size: {manifest.SheetSize[0] * scale / boxW * 100f:0.####}% {manifest.SheetSize[1] * scale / boxH * 100f:0.####}% ; " +
			$"background-position: {posX / boxW * 100f:0.####}% {posY / boxH * 100f:0.####}% ;" );
	}
}