XGUI/XGUIIconSystem.cs
using Sandbox;
using Sandbox.UI;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace XGUI;

/// <summary>
/// Look up icons respective to the current theme, you can look up icons by name to use in buttons and panels, or icons for file types to use in a file browser.
/// </summary>
public static class XGUIIconSystem
{
	private const string DefaultThemeName = "Computer95";
	private static string _currentThemeName = DefaultThemeName;

	// Cache for icon paths - theme/type/name/size/variant -> path
	private static Dictionary<string, string> IconPathCache = new Dictionary<string, string>();

	/// <summary>
	/// Supported icon types
	/// </summary>
	public enum IconType
	{
		/// <summary>
		/// Standard UI icons (menus, buttons, controls)
		/// </summary>
		UI,

		/// <summary>
		/// Icons for file types
		/// </summary>
		FileType,

		/// <summary>
		/// Icons for folders
		/// </summary>
		Folder,

		/// <summary>
		/// Miscellaneous icons (other types)
		/// </summary>
		Misc
	}

	/// <summary>
	/// Get the current theme name
	/// </summary>
	public static string CurrentTheme
	{
		get => _currentThemeName;
		set
		{
			if ( _currentThemeName != value )
			{
				_currentThemeName = value;
				IconPathCache.Clear(); // Invalidate cache when theme changes
			}
		}
	}

	/// <summary>
	/// Get the base directory for the current theme's icons
	/// </summary>
	private static string GetThemeIconBaseDirectory( string themeName )
	{
		return $"XGUI/Resources/{themeName}/Icons";
	}

	/// <summary>
	/// Get the type-specific sub-directory for icons
	/// </summary>
	private static string GetIconTypeDirectory( IconType iconType )
	{
		return iconType switch
		{
			IconType.UI => "UI",
			IconType.FileType => "FileTypes",
			IconType.Folder => "Folders",
			IconType.Misc => "Misc",
			_ => string.Empty
		};
	}

	/// <summary>
	/// Look up an icon by name and size
	/// </summary>
	/// <param name="iconName">The name of the icon</param>
	/// <param name="iconType">The type of icon</param>
	/// <param name="size">The desired size of the icon (16, 24, 32, 48, etc.)</param>
	/// <param name="variant">Optional variant of the icon (e.g., "hover", "active", "disabled")</param>
	/// <returns>The path to the icon file, or null if not found</returns>
	public static string GetIcon( string iconName, IconType iconType = IconType.UI, int size = 16, string variant = null )
	{
		// Standardize icon name
		if ( string.IsNullOrEmpty( iconName ) )
			return null;

		iconName = iconName.ToLowerInvariant();

		// Standardize variant if provided
		if ( !string.IsNullOrEmpty( variant ) )
		{
			variant = variant.ToLowerInvariant();
		}

		// Check cache first
		string cacheKey = $"{_currentThemeName}/{iconType}/{iconName}/{size}/{variant}";
		if ( IconPathCache.TryGetValue( cacheKey, out string cachedPath ) )
			return cachedPath;

		// First, try with variant if specified
		string iconPath = null;
		if ( !string.IsNullOrEmpty( variant ) )
		{
			iconPath = FindIconInTheme( _currentThemeName, iconName, iconType, size, variant );

			// If not found in current theme, try default theme with variant
			if ( string.IsNullOrEmpty( iconPath ) && _currentThemeName != DefaultThemeName )
			{
				iconPath = FindIconInTheme( DefaultThemeName, iconName, iconType, size, variant );
			}

			// If still not found with variant, fall back to standard icon (without variant)
			if ( string.IsNullOrEmpty( iconPath ) )
			{
				// Try again without the variant
				return GetIcon( iconName, iconType, size );
			}
		}
		else
		{
			// Standard lookup without variant
			iconPath = FindIconInTheme( _currentThemeName, iconName, iconType, size );

			// If not found in current theme, try default theme
			if ( string.IsNullOrEmpty( iconPath ) && _currentThemeName != DefaultThemeName )
			{
				iconPath = FindIconInTheme( DefaultThemeName, iconName, iconType, size );
			}

			// If still not found, try Material Icons as fallback
			if ( string.IsNullOrEmpty( iconPath ) )
			{
				// For UI icons, try to use Material Icons
				if ( iconType == IconType.UI && IsMaterialIcon( iconName ) )
				{
					// Return the icon name with special prefix to indicate it's a material icon
					iconPath = $"material:{iconName}";
				}
				else
				{
					// For file types, use a generic file icon
					if ( iconType == IconType.FileType )
					{
						iconPath = FindIconInTheme( _currentThemeName, "file", iconType, size ) ??
								   FindIconInTheme( DefaultThemeName, "file", iconType, size );
					}
					// For folders, use a generic folder icon
					else if ( iconType == IconType.Folder )
					{
						iconPath = FindIconInTheme( _currentThemeName, "folder", iconType, size ) ??
								   FindIconInTheme( DefaultThemeName, "folder", iconType, size );
					}
				}
			}
		}

		// Cache the result for faster lookups
		if ( !string.IsNullOrEmpty( iconPath ) )
		{
			IconPathCache[cacheKey] = iconPath;
		}

		return iconPath;
	}

	private static string GetDefaultIconName( IconType iconType )
	{
		return iconType switch
		{
			IconType.FileType => "file",
			IconType.Folder => "folder",
			IconType.UI => "default",
			IconType.Misc => "default",
			_ => "default"
		};
	}

	/// <summary>
	/// Get an icon for a specific file extension with optional variant
	/// </summary>
	/// <param name="extension">The file extension (with or without the dot)</param>
	/// <param name="size">The desired icon size</param>
	/// <param name="variant">Optional variant of the icon (e.g., "hover", "active", "disabled")</param>
	/// <returns>The path to the icon file</returns>
	public static string GetFileIcon( string extension, int size = 16, string variant = null )
	{
		if ( string.IsNullOrEmpty( extension ) )
			return GetIcon( "file", IconType.FileType, size, variant );

		// Normalize extension
		if ( extension.StartsWith( "." ) )
			extension = extension.Substring( 1 );

		extension = extension.ToLowerInvariant();

		// Try to find an icon for this specific extension
		return GetIcon( extension, IconType.FileType, size, variant );
	}

	/// <summary>
	/// Get a folder icon with optional variant
	/// </summary>
	/// <param name="folderType">The type of folder (optional)</param>
	/// <param name="size">The desired icon size</param>
	/// <param name="variant">Optional variant of the icon (e.g., "hover", "active", "disabled")</param>
	/// <returns>The path to the icon file</returns>
	public static string GetFolderIcon( string folderType = "folder", int size = 16, string variant = null )
	{
		return GetIcon( folderType, IconType.Folder, size, variant );
	}


	static bool findingDefaultIconPreventRecursion = false;
	/// <summary>
	/// Find an icon in a specific theme
	/// </summary>
	private static string FindIconInTheme( string themeName, string iconName, IconType iconType, int size, string variant = null )
	{
		string baseDir = GetThemeIconBaseDirectory( themeName );
		string typeDir = GetIconTypeDirectory( iconType );

		// Build file name pattern based on whether variant is specified
		string fileNamePattern = !string.IsNullOrEmpty( variant ) ?
			$"{iconName}_{size}_{variant}.png" : $"{iconName}_{size}.png";

		// Try exact size and variant/non-variant first
		string exactPath = $"{baseDir}/{typeDir}/{fileNamePattern}";
		if ( FileSystem.Mounted.FileExists( exactPath ) )
			return exactPath;

		// If not found, try to find the closest size with the same variant (if specified)
		List<(int Size, string Variant, string Path)> availableIcons = new List<(int, string, string)>();

		// Try to find all available sizes for this icon
		foreach ( var file in FileSystem.Mounted.FindFile( $"{baseDir}/{typeDir}" ) )
		{
			string fileName = Path.GetFileNameWithoutExtension( file );

			// Skip files that don't start with our icon name
			if ( !fileName.StartsWith( iconName + "_" ) )
				continue;

			// Parse out size and variant from filename (format: name_size.png or name_size_variant.png)
			string[] parts = fileName.Substring( iconName.Length + 1 ).Split( '_' );

			if ( parts.Length >= 1 && int.TryParse( parts[0], out int fileSize ) )
			{
				string fileVariant = parts.Length > 1 ? parts[1] : null;
				availableIcons.Add( (fileSize, fileVariant, $"{baseDir}/{typeDir}/{fileName}.png") );
			}
		}

		// If no sizes found, return null
		if ( availableIcons.Count == 0 )
		{
			Log.Warning( $"Icon not found: {iconName} in theme {themeName} of type {iconType}" );
			Log.Info( $"Add to path: {baseDir}/{typeDir}/{fileNamePattern}" );

			// Return a generic file icon if this is a file type
			if ( iconType == IconType.FileType && !findingDefaultIconPreventRecursion )
			{
				findingDefaultIconPreventRecursion = true;
				return null;
			}
			findingDefaultIconPreventRecursion = false;

			return null;
		}

		// If variant is specified, try to find an exact match for the variant first
		if ( !string.IsNullOrEmpty( variant ) )
		{
			// Filter for the requested variant
			var variantMatches = availableIcons.Where( i => i.Variant == variant ).ToList();

			if ( variantMatches.Count > 0 )
			{
				// Sort by size
				variantMatches.Sort( ( a, b ) => a.Size.CompareTo( b.Size ) );

				// Find closest size (prefer larger)
				var closest = FindClosestSize( variantMatches, size );
				return closest.Path;
			}
		}

		// Otherwise, filter for icons with no variant
		var standardIcons = availableIcons.Where( i => i.Variant == null ).ToList();
		if ( standardIcons.Count > 0 )
		{
			// Sort by size
			standardIcons.Sort( ( a, b ) => a.Size.CompareTo( b.Size ) );

			// Find closest size (prefer larger)
			var closest = FindClosestSize( standardIcons, size );
			return closest.Path;
		}

		// If we get here, we have icons but none with the requested variant or no variant
		// Just return the first one as a last resort
		return availableIcons[0].Path;
	}

	/// <summary>
	/// Find the icon with the closest size to the requested size, preferring larger sizes
	/// </summary>
	private static (int Size, string Variant, string Path) FindClosestSize( List<(int Size, string Variant, string Path)> icons, int requestedSize )
	{
		// Find the first icon that's at least as large as the requested size
		foreach ( var icon in icons )
		{
			if ( icon.Size >= requestedSize )
			{
				return icon;
			}
		}

		// If no icons are large enough, return the largest available
		return icons[icons.Count - 1];
	}

	/// <summary>
	/// Check if an icon name is a valid Material Icons name
	/// </summary>
	private static bool IsMaterialIcon( string iconName )
	{
		// This is a simplification - in a real implementation you'd check against a list of valid Material Icons
		// For now, we'll just return true to use Material Icons as a fallback
		return true;
	}

	/// <summary>
	/// Clear the icon cache
	/// </summary>
	public static void ClearCache()
	{
		IconPathCache.Clear();
	}

	[ConCmd( "xgui_icon_reset_cache" )]
	public static void ResetIconCache()
	{
		ClearCache();
		Log.Info( "XGUI Icon cache cleared." );
	}
}

/// <summary>
/// Icon panel that uses the XGUIIconSystem to look up icons based on the current theme
/// </summary>
public class XGUIIconPanel : Panel
{
	private string _iconName;
	private XGUIIconSystem.IconType _iconType = XGUIIconSystem.IconType.UI;
	private int _iconSize = 16;
	private string _variant;
	private Image _iconImage;
	private Label _materialIconLabel;

	/// <summary>
	/// The name of the icon
	/// </summary>
	public string IconName
	{
		get => _iconName;
		set
		{
			if ( _iconName != value )
			{
				_iconName = value;
				UpdateIcon();
			}
		}
	}

	/// <summary>
	/// The type of icon
	/// </summary>
	public XGUIIconSystem.IconType IconType
	{
		get => _iconType;
		set
		{
			if ( _iconType != value )
			{
				_iconType = value;
				UpdateIcon();
			}
		}
	}

	/// <summary>
	/// The desired size of the icon
	/// </summary>
	public int IconSize
	{
		get => _iconSize;
		set
		{
			if ( _iconSize != value )
			{
				_iconSize = value;
				UpdateIcon();
			}
		}
	}

	/// <summary>
	/// The variant of the icon (e.g., "hover", "active", "disabled")
	/// </summary>
	public string Variant
	{
		get => _variant;
		set
		{
			if ( _variant != value )
			{
				_variant = value;
				UpdateIcon();
			}
		}
	}

	public XGUIIconPanel()
	{
		AddClass( "xgui-icon-panel" );

		// Create the icon image
		_iconImage = AddChild<Image>();
		_iconImage.AddClass( "icon-image" );

		// Create the material icon label
		_materialIconLabel = AddChild<Label>();
		_materialIconLabel.AddClass( "material-icon" );

		// Hide both by default
		_iconImage.Style.Display = DisplayMode.None;
		_materialIconLabel.Style.Display = DisplayMode.None;

		// Set up event handlers for hover state
		//AddEventListener( "onmouseover", OnMouseEnter );
		//AddEventListener( "onmouseout", OnMouseLeave );
		//AddEventListener( "onmousedown", OnMouseDown );
		//AddEventListener( "onmouseup", OnMouseUp );
	}

	public XGUIIconPanel( string iconName, XGUIIconSystem.IconType iconType = XGUIIconSystem.IconType.UI, int iconSize = 16, string variant = null )
		: this()
	{
		_iconName = iconName;
		_iconType = iconType;
		_iconSize = iconSize;
		_variant = variant;
		UpdateIcon();
	}

	private void OnMouseEnter( PanelEvent e )
	{
		// If auto-hover variants are desired, set variant to "hover"
		if ( string.IsNullOrEmpty( _variant ) )
		{
			Variant = "hover";
		}
	}

	private void OnMouseLeave( PanelEvent e )
	{
		// Reset variant when mouse leaves
		if ( _variant == "hover" || _variant == "active" )
		{
			Variant = null;
		}
	}

	private void OnMouseDown( PanelEvent e )
	{
		// Set to active when mouse is pressed
		if ( string.IsNullOrEmpty( _variant ) || _variant == "hover" )
		{
			Variant = "active";
		}
	}

	private void OnMouseUp( PanelEvent e )
	{
		// Return to hover state when mouse is released
		if ( _variant == "active" )
		{
			Variant = HasHovered ? "hover" : null;
		}
	}

	/// <summary>
	/// Update the icon based on current properties
	/// </summary>
	private void UpdateIcon()
	{
		if ( string.IsNullOrEmpty( _iconName ) )
		{
			_iconImage.Style.Display = DisplayMode.None;
			_materialIconLabel.Style.Display = DisplayMode.None;
			return;
		}

		if ( _iconName.StartsWith( "url:data:" ) )
		{
			var imagePath = IconName.Substring( 9 ); // Remove "url:data:" prefix
			_iconImage.Style.Display = DisplayMode.Flex;
			_materialIconLabel.Style.Display = DisplayMode.None;

			var tex = Texture.LoadFromFileSystem( imagePath, FileSystem.Data );

			_iconImage.Style.SetBackgroundImage( tex );
			_iconImage.Style.Width = Length.Pixels( _iconSize );
			_iconImage.Style.Height = Length.Pixels( _iconSize );
			return;
		}
		if ( _iconName.StartsWith( "url:vtex:" ) )
		{
			var imagePath = IconName.Substring( 9 ); // Remove "url:vtex:" prefix
			_iconImage.Style.Display = DisplayMode.Flex;
			_materialIconLabel.Style.Display = DisplayMode.None;
			var tex = Texture.Find( imagePath );
			_iconImage.Style.SetBackgroundImage( tex );
			_iconImage.Style.Width = Length.Pixels( _iconSize );
			_iconImage.Style.Height = Length.Pixels( _iconSize );
			return;
		}
		if ( _iconName.StartsWith( "url:" ) )
		{
			var imagePath = IconName.Substring( 4 ); // Remove "url:" prefix
			_iconImage.Style.Display = DisplayMode.Flex;
			_materialIconLabel.Style.Display = DisplayMode.None;
			_iconImage.Style.SetBackgroundImage( imagePath );
			_iconImage.Style.Width = Length.Pixels( _iconSize );
			_iconImage.Style.Height = Length.Pixels( _iconSize );
			return;
		}

		string iconPath = XGUIIconSystem.GetIcon( _iconName, _iconType, _iconSize, _variant );

		if ( string.IsNullOrEmpty( iconPath ) )
		{
			_iconImage.Style.Display = DisplayMode.None;
			_materialIconLabel.Style.Display = DisplayMode.None;
		}
		else if ( iconPath.StartsWith( "material:" ) )
		{
			// Show material icon
			_iconImage.Style.Display = DisplayMode.None;
			_materialIconLabel.Style.Display = DisplayMode.Flex;
			_materialIconLabel.Text = iconPath.Substring( 9 ); // Remove "material:" prefix
			_materialIconLabel.Style.FontSize = Length.Pixels( _iconSize );
		}
		else
		{
			// Show image icon
			_iconImage.Style.Display = DisplayMode.Flex;
			_materialIconLabel.Style.Display = DisplayMode.None;
			_iconImage.Style.BackgroundImage = Texture.LoadFromFileSystem( iconPath, FileSystem.Mounted );
			_iconImage.Style.Width = Length.Pixels( _iconSize );
			_iconImage.Style.Height = Length.Pixels( _iconSize );
		}
	}

	/// <summary>
	/// Set the icon by name
	/// </summary>
	public void SetIcon( string iconName, XGUIIconSystem.IconType iconType = XGUIIconSystem.IconType.UI, int iconSize = 16, string variant = null )
	{
		_iconName = iconName;
		_iconType = iconType;
		_iconSize = iconSize;
		_variant = variant;
		UpdateIcon();
	}

	/// <summary>
	/// Set the icon for a file extension
	/// </summary>
	public void SetFileIcon( string extension, int iconSize = 16, string variant = null )
	{
		_iconType = XGUIIconSystem.IconType.FileType;
		_iconSize = iconSize;
		_variant = variant;

		if ( string.IsNullOrEmpty( extension ) )
		{
			_iconName = "file";
		}
		else
		{
			// Normalize extension
			if ( extension.StartsWith( "." ) )
				extension = extension.Substring( 1 );

			_iconName = extension.ToLowerInvariant();
		}

		UpdateIcon();
	}

	/// <summary>
	/// Set the icon for a folder
	/// </summary>
	public void SetFolderIcon( string folderType = "folder", int iconSize = 16, string variant = null )
	{
		_iconType = XGUIIconSystem.IconType.Folder;
		_iconSize = iconSize;
		_iconName = folderType;
		_variant = variant;
		UpdateIcon();
	}

	/// <summary>
	/// Reset to default theme
	/// </summary>
	public void ResetTheme()
	{
		XGUIIconSystem.ClearCache();
		UpdateIcon();
	}
}