Editor/Converters/FabMaterialConverter.cs
using System;
using System.IO;
using System.Text;

namespace FabBridge.Converters;

/// <summary>
/// Generates s&box VMAT materials from Fab PBR texture data
/// </summary>
public static class FabMaterialConverter
{
	/// <summary>
	/// Result of a material generation
	/// </summary>
	public class ConversionResult
	{
		public bool Success { get; set; }
		public string VmatPath { get; set; }
		public string RelativePath { get; set; }
		public string Error { get; set; }
		public Asset Asset { get; set; }
	}

	/// <summary>
	/// Texture paths for material generation
	/// </summary>
	public class MaterialTextures
	{
		public string Color { get; set; }
		public string Normal { get; set; }
		public string Roughness { get; set; }
		public string Metalness { get; set; }
		public string AmbientOcclusion { get; set; }
		public string Displacement { get; set; }
		public string Translucency { get; set; }  // RGB translucency map (preferred for foliage)
		public string Opacity { get; set; }       // Grayscale opacity/alpha map (fallback)
		public string Emissive { get; set; }
	}

	/// <summary>
	/// Generate a VMAT material from converted textures
	/// </summary>
	/// <param name="materialName">Name for the material</param>
	/// <param name="textures">Texture paths (relative to project)</param>
	/// <param name="destinationFolder">Absolute path to destination folder</param>
	/// <returns>Conversion result</returns>
	public static ConversionResult Generate( string materialName, MaterialTextures textures, string destinationFolder )
	{
		var result = new ConversionResult();

		try
		{
			// Ensure destination folder exists
			Directory.CreateDirectory( destinationFolder );

			// Build the VMAT file path
			var vmatPath = Path.Combine( destinationFolder, $"{materialName}.vmat" );
			result.VmatPath = vmatPath;

			// Generate the VMAT content
			var vmatContent = GenerateVmatContent( textures );

			// Write the VMAT file
			File.WriteAllText( vmatPath, vmatContent );

			// Delete stale .vmat_c so s&box recompiles from the new source.
			// Why: if the compiled output is newer than the source, s&box may skip
			// recompiling and keep serving the old (possibly broken) material.
			var vmatCompiledPath = vmatPath + "_c";
			if ( File.Exists( vmatCompiledPath ) )
			{
				try { File.Delete( vmatCompiledPath ); }
				catch ( Exception delEx ) { Log.Warning( $"FabBridge: Couldn't delete stale {vmatCompiledPath}: {delEx.Message}" ); }
			}

			Log.Info( $"FabBridge: Created VMAT at {vmatPath}" );

			// Register the asset
			var asset = AssetSystem.RegisterFile( vmatPath );
			if ( asset != null )
			{
				result.Asset = asset;
				result.RelativePath = asset.Path;
				result.Success = true;

				Log.Info( $"FabBridge: Registered material asset at {asset.Path}" );
			}
			else
			{
				result.Error = "Failed to register VMAT asset";
			}
		}
		catch ( Exception ex )
		{
			result.Error = ex.Message;
			Log.Error( $"FabBridge: Material generation failed: {ex.Message}" );
		}

		return result;
	}

	/// <summary>
	/// Generate a material from a Fab asset and its converted textures
	/// </summary>
	public static ConversionResult GenerateFromFabAsset( FabAsset fabAsset, List<FabTextureConverter.ConversionResult> textureResults, string destinationFolder )
	{
		var materialName = fabAsset.GetSafeFileName();
		var textures = new MaterialTextures();

		// Log project path for debugging
		var project = Project.Current;
		var assetsPath = project?.GetAssetsPath() ?? "(no project)";
		Log.Info( $"FabBridge: Project assets path: {assetsPath}" );
		Log.Info( $"FabBridge: Destination folder: {destinationFolder}" );

		// Map converted textures to material slots
		foreach ( var texResult in textureResults )
		{
			if ( !texResult.Success || string.IsNullOrEmpty( texResult.DestinationPath ) )
				continue;

			// Verify the file actually exists before referencing it
			if ( !File.Exists( texResult.DestinationPath ) )
			{
				Log.Warning( $"FabBridge: Texture file missing, skipping: {texResult.DestinationPath}" );
				continue;
			}

			// Use the relative path to the SOURCE image file (not .vtex)
			// Materials can reference .jpg/.png directly and s&box will compile them
			var path = FabTextureConverter.GetRelativePath( texResult.DestinationPath );

			// Log detailed path info for debugging
			Log.Info( $"FabBridge: Texture paths:" );
			Log.Info( $"  Absolute: {texResult.DestinationPath}" );
			Log.Info( $"  Relative: {path}" );
			Log.Info( $"  File exists: {File.Exists( texResult.DestinationPath )}" );

			// Determine texture type from the destination path suffix
			var fileName = Path.GetFileNameWithoutExtension( texResult.DestinationPath )?.ToLowerInvariant() ?? "";

			if ( fileName.EndsWith( "_color" ) || fileName.EndsWith( "_albedo" ) || fileName.EndsWith( "_basecolor" ) )
				textures.Color = path;
			else if ( fileName.EndsWith( "_normal" ) )
				textures.Normal = path;
			else if ( fileName.EndsWith( "_rough" ) || fileName.EndsWith( "_roughness" ) )
				textures.Roughness = path;
			else if ( fileName.EndsWith( "_metal" ) || fileName.EndsWith( "_metalness" ) || fileName.EndsWith( "_metallic" ) )
				textures.Metalness = path;
			else if ( fileName.EndsWith( "_ao" ) || fileName.EndsWith( "_occlusion" ) || fileName.EndsWith( "_ambientocclusion" ) )
				textures.AmbientOcclusion = path;
			else if ( fileName.EndsWith( "_height" ) || fileName.EndsWith( "_displacement" ) )
				textures.Displacement = path;
			else if ( fileName.EndsWith( "_translucency" ) )
				textures.Translucency = path;  // RGB translucency (preferred)
			else if ( fileName.EndsWith( "_opacity" ) || fileName.EndsWith( "_alpha" ) || fileName.EndsWith( "_trans" ) )
				textures.Opacity = path;  // Grayscale opacity (fallback)
			else if ( fileName.EndsWith( "_selfillum" ) || fileName.EndsWith( "_emissive" ) )
				textures.Emissive = path;
		}

		return Generate( materialName, textures, destinationFolder );
	}

	/// <summary>
	/// Generate the VMAT file content
	/// </summary>
	private static string GenerateVmatContent( MaterialTextures textures )
	{
		var sb = new StringBuilder();
		sb.AppendLine( "// Generated by FabBridge" );
		sb.AppendLine();
		sb.AppendLine( "Layer0" );
		sb.AppendLine( "{" );
		sb.AppendLine( "\tshader \"shaders/complex.shader\"" );
		sb.AppendLine();

		// Color/Albedo texture
		if ( !string.IsNullOrEmpty( textures.Color ) )
		{
			sb.AppendLine( "\t//---- Color ----" );
			sb.AppendLine( $"\tTextureColor \"{textures.Color}\"" );
			sb.AppendLine( "\tg_flModelTintAmount \"1.000\"" );
			sb.AppendLine( "\tg_vColorTint \"[1.000000 1.000000 1.000000 0.000000]\"" );
			sb.AppendLine();
		}
		else
		{
			sb.AppendLine( "\t//---- Color ----" );
			sb.AppendLine( "\tTextureColor \"materials/default/default_color.tga\"" );
			sb.AppendLine();
		}

		// Normal texture
		if ( !string.IsNullOrEmpty( textures.Normal ) )
		{
			sb.AppendLine( "\t//---- Normal ----" );
			sb.AppendLine( $"\tTextureNormal \"{textures.Normal}\"" );
			sb.AppendLine();
		}
		else
		{
			sb.AppendLine( "\t//---- Normal ----" );
			sb.AppendLine( "\tTextureNormal \"materials/default/default_normal.tga\"" );
			sb.AppendLine();
		}

		// Roughness texture
		if ( !string.IsNullOrEmpty( textures.Roughness ) )
		{
			sb.AppendLine( "\t//---- Roughness ----" );
			sb.AppendLine( $"\tTextureRoughness \"{textures.Roughness}\"" );
			sb.AppendLine();
		}
		else
		{
			sb.AppendLine( "\t//---- Roughness ----" );
			sb.AppendLine( "\tTextureRoughness \"materials/default/default_rough.tga\"" );
			sb.AppendLine();
		}

		// Ambient Occlusion texture
		if ( !string.IsNullOrEmpty( textures.AmbientOcclusion ) )
		{
			sb.AppendLine( "\t//---- Ambient Occlusion ----" );
			sb.AppendLine( $"\tTextureAmbientOcclusion \"{textures.AmbientOcclusion}\"" );
			sb.AppendLine( "\tg_flAmbientOcclusionDirectDiffuse \"0.000\"" );
			sb.AppendLine( "\tg_flAmbientOcclusionDirectSpecular \"0.000\"" );
			sb.AppendLine();
		}
		else
		{
			sb.AppendLine( "\t//---- Ambient Occlusion ----" );
			sb.AppendLine( "\tTextureAmbientOcclusion \"materials/default/default_ao.tga\"" );
			sb.AppendLine();
		}

		// Metalness texture (requires feature flag)
		if ( !string.IsNullOrEmpty( textures.Metalness ) )
		{
			sb.AppendLine( "\t//---- Metalness ----" );
			sb.AppendLine( "\tF_METALNESS_TEXTURE 1" );
			sb.AppendLine( "\tF_SPECULAR 1" );
			sb.AppendLine( $"\tTextureMetalness \"{textures.Metalness}\"" );
			sb.AppendLine();
		}
		else
		{
			sb.AppendLine( "\t//---- Metalness ----" );
			sb.AppendLine( "\tg_flMetalness \"0.000\"" );
			sb.AppendLine();
		}

		// Self-illumination/Emissive (requires feature flag)
		if ( !string.IsNullOrEmpty( textures.Emissive ) )
		{
			sb.AppendLine( "\t//---- Self Illum ----" );
			sb.AppendLine( "\tF_SELF_ILLUM 1" );
			sb.AppendLine( $"\tTextureSelfIllumMask \"{textures.Emissive}\"" );
			sb.AppendLine();
		}

		// Translucency (RGB map - preferred for foliage, glass-like transparency)
		if ( !string.IsNullOrEmpty( textures.Translucency ) )
		{
			sb.AppendLine( "\t//---- Translucent ----" );
			sb.AppendLine( "\tF_TRANSLUCENT 1" );
			sb.AppendLine( "\tF_RENDER_BACKFACES 1" );
			sb.AppendLine( $"\tTextureTranslucency \"{textures.Translucency}\"" );
			sb.AppendLine();
		}
		// Alpha Test/Opacity (grayscale map - fallback for cutout materials)
		else if ( !string.IsNullOrEmpty( textures.Opacity ) )
		{
			sb.AppendLine( "\t//---- Alpha Test ----" );
			sb.AppendLine( "\tF_ALPHA_TEST 1" );
			sb.AppendLine( "\tF_RENDER_BACKFACES 1" );
			sb.AppendLine( "\tg_flAlphaTestReference \"0.5\"" );
			sb.AppendLine( "\tg_flAntiAliasedEdgeStrength \"1.000\"" );
			sb.AppendLine( $"\tTextureTranslucency \"{textures.Opacity}\"" );
			sb.AppendLine();
		}

		// Standard settings
		sb.AppendLine( "\t//---- Fog ----" );
		sb.AppendLine( "\tg_bFogEnabled \"1\"" );
		sb.AppendLine();

		sb.AppendLine( "\t//---- Texture Coordinates ----" );
		sb.AppendLine( "\tg_nScaleTexCoordUByModelScaleAxis \"0\"" );
		sb.AppendLine( "\tg_nScaleTexCoordVByModelScaleAxis \"0\"" );
		sb.AppendLine( "\tg_vTexCoordOffset \"[0.000 0.000]\"" );
		sb.AppendLine( "\tg_vTexCoordScale \"[1.000 1.000]\"" );
		sb.AppendLine( "\tg_vTexCoordScrollSpeed \"[0.000 0.000]\"" );

		sb.AppendLine( "}" );

		return sb.ToString();
	}
}