Code/IconCache.cs
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Sandbox;

namespace Iconify;

/// <summary>
/// Fetches icons from the Iconify API and caches them as textures.
/// Handles disk cache and in-memory cache with proper null safety.
/// </summary>
public static class IconCache
{
	private static readonly Dictionary<string, Texture> MemoryCache = new();
	private static BaseFileSystem? _diskCache;
	private static bool _diskCacheReady;

	private static BaseFileSystem DiskCache
	{
		get
		{
			if ( !_diskCacheReady )
			{
				_diskCacheReady = true;
				try
				{
					if ( FileSystem.Data is not null )
					{
						FileSystem.Data.CreateDirectory( "iconify_cache" );
						_diskCache = FileSystem.Data.CreateSubSystem( "iconify_cache" );
					}
				}
				catch ( Exception e )
				{
					Log.Warning( $"[Iconify] Could not create disk cache: {e.Message}" );
					_diskCache = null;
				}
			}
			return _diskCache;
		}
	}

	/// <summary>
	/// Get an icon texture from cache or fetch from the Iconify API.
	/// </summary>
	public static async Task<Texture?> GetOrFetch( string prefix, string name, string color, int size )
	{
		var cacheKey = $"{prefix}_{name}_{color}_{size}";

		// Memory cache
		if ( MemoryCache.TryGetValue( cacheKey, out var cached ) && cached.IsValid() )
			return cached;

		// Disk cache
		var diskPath = $"{cacheKey}.svg";
		if ( DiskCache is not null && DiskCache.FileExists( diskPath ) )
		{
			try
			{
				var svgString = DiskCache.ReadAllText( diskPath );
				var tex = Texture.CreateFromSvgSource( svgString, size, size, null );
				if ( tex is not null && tex.IsValid() )
				{
					MemoryCache[cacheKey] = tex;
					return tex;
				}
			}
			catch { /* disk cache corrupted, re-fetch */ }
		}

		// Fetch from API
		var texture = await FetchFromApi( prefix, name, color, size, cacheKey );
		if ( texture is not null )
		{
			MemoryCache[cacheKey] = texture;
		}

		return texture;
	}

	private static async Task<Texture?> FetchFromApi( string prefix, string name, string color, int size, string cacheKey )
	{
		var encodedColor = Uri.EscapeDataString( color ?? "white" );
		var url = $"https://api.iconify.design/{prefix}/{name}.svg?color={encodedColor}&width={size}&height={size}";

		try
		{
			var svgString = await Http.RequestStringAsync( url );

			if ( string.IsNullOrEmpty( svgString ) )
			{
				Log.Warning( $"[Iconify] Empty response for {prefix}:{name}" );
				return null;
			}

			// Create texture directly from SVG string with size
			var texture = Texture.CreateFromSvgSource( svgString, size, size, null );

			if ( texture is not null && texture.IsValid() )
			{
				SaveToDiskCache( $"{cacheKey}.svg", System.Text.Encoding.UTF8.GetBytes( svgString ) );
			}

			return texture;
		}
		catch ( Exception e )
		{
			Log.Warning( $"[Iconify] API fetch failed for {prefix}:{name}: {e.Message}" );
			return null;
		}
	}

	private static void SaveToDiskCache( string path, byte[] data )
	{
		try
		{
			DiskCache?.WriteAllBytes( path, data );
		}
		catch { /* non-critical, just won't be cached */ }
	}
}