Editor/RenderingToolHandlers.cs
using System;
using System.Collections.Generic;
using System.Text.Json;
using Sandbox;

namespace SboxMcpServer;

/// <summary>
/// Rendering MCP tools: create_text_renderer, create_line_renderer, create_sprite_renderer, create_trail_renderer.
/// </summary>
internal static class RenderingToolHandlers
{
	private static readonly JsonSerializerOptions _json = new()
	{
		PropertyNamingPolicy   = JsonNamingPolicy.CamelCase,
		DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
	};

	// ── create_text_renderer ───────────────────────────────────────────────

	internal static object CreateTextRenderer( JsonElement args )
	{
		var scene = OzmiumSceneHelpers.ResolveScene();
		if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );

		float  x    = OzmiumSceneHelpers.Get( args, "x", 0f );
		float  y    = OzmiumSceneHelpers.Get( args, "y", 0f );
		float  z    = OzmiumSceneHelpers.Get( args, "z", 0f );
		string name  = OzmiumSceneHelpers.Get( args, "name", "Text Renderer" );

		try
		{
			var go = scene.CreateObject();
			go.Name = name;
			go.WorldPosition = new Vector3( x, y, z );

			var tr = go.Components.Create<TextRenderer>();
			tr.Text = OzmiumSceneHelpers.Get( args, "text", tr.Text );
			tr.FontSize = OzmiumSceneHelpers.Get( args, "fontSize", tr.FontSize );
			tr.Scale = OzmiumSceneHelpers.Get( args, "scale", tr.Scale );

			if ( args.TryGetProperty( "color", out var colEl ) && colEl.ValueKind == JsonValueKind.String )
			{
				try { tr.Color = Color.Parse( colEl.GetString() ) ?? default; } catch { }
			}

			if ( args.TryGetProperty( "horizontalAlignment", out var hEl ) && hEl.ValueKind == JsonValueKind.String )
			{
				if ( Enum.TryParse<TextRenderer.HAlignment>( hEl.GetString(), true, out var h ) )
					tr.HorizontalAlignment = h;
			}

			if ( args.TryGetProperty( "verticalAlignment", out var vEl ) && vEl.ValueKind == JsonValueKind.String )
			{
				if ( Enum.TryParse<TextRenderer.VAlignment>( vEl.GetString(), true, out var v ) )
					tr.VerticalAlignment = v;
			}

			return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
			{
				message = $"Created TextRenderer '{go.Name}'.",
				id       = go.Id.ToString(),
				position = OzmiumSceneHelpers.V3( go.WorldPosition )
			}, _json ) );
		}
		catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
	}

	// ── create_line_renderer ───────────────────────────────────────────────

	internal static object CreateLineRenderer( JsonElement args )
	{
		var scene = OzmiumSceneHelpers.ResolveScene();
		if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );

		float  x    = OzmiumSceneHelpers.Get( args, "x", 0f );
		float  y    = OzmiumSceneHelpers.Get( args, "y", 0f );
		float  z    = OzmiumSceneHelpers.Get( args, "z", 0f );
		string name  = OzmiumSceneHelpers.Get( args, "name", "Line Renderer" );

		try
		{
			var go = scene.CreateObject();
			go.Name = name;
			go.WorldPosition = new Vector3( x, y, z );

			var lr = go.Components.Create<LineRenderer>();
			lr.UseVectorPoints = true;
			lr.VectorPoints = new List<Vector3>();

			if ( args.TryGetProperty( "points", out var ptsEl ) && ptsEl.ValueKind == JsonValueKind.Array )
			{
				foreach ( var ptEl in ptsEl.EnumerateArray() )
				{
					if ( ptEl.ValueKind == JsonValueKind.Object )
					{
						lr.VectorPoints.Add( new Vector3(
							OzmiumSceneHelpers.Get( ptEl, "x", 0f ),
							OzmiumSceneHelpers.Get( ptEl, "y", 0f ),
							OzmiumSceneHelpers.Get( ptEl, "z", 0f ) ) );
					}
				}
			}

			if ( args.TryGetProperty( "color", out var colEl ) && colEl.ValueKind == JsonValueKind.String )
			{
				try { lr.Color = Color.Parse( colEl.GetString() ) ?? default; } catch { }
			}

			if ( args.TryGetProperty( "width", out var wEl ) )
				lr.Width = 5; // Width is a Curve, but we set a default via property

			return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
			{
				message = $"Created LineRenderer '{go.Name}'.",
				id       = go.Id.ToString(),
				position = OzmiumSceneHelpers.V3( go.WorldPosition ),
				pointCount = lr.VectorPoints?.Count ?? 0
			}, _json ) );
		}
		catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
	}

	// ── create_sprite_renderer ──────────────────────────────────────────────

	internal static object CreateSpriteRenderer( JsonElement args )
	{
		var scene = OzmiumSceneHelpers.ResolveScene();
		if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );

		float  x    = OzmiumSceneHelpers.Get( args, "x", 0f );
		float  y    = OzmiumSceneHelpers.Get( args, "y", 0f );
		float  z    = OzmiumSceneHelpers.Get( args, "z", 0f );
		string name  = OzmiumSceneHelpers.Get( args, "name", "Sprite Renderer" );

		try
		{
			var go = scene.CreateObject();
			go.Name = name;
			go.WorldPosition = new Vector3( x, y, z );

			// SpriteRenderer — look up via TypeLibrary (may not exist in all versions)
			var spriteTd = OzmiumWriteHandlers.FindComponentTypeDescription( "SpriteRenderer" );
			if ( spriteTd == null )
				return OzmiumSceneHelpers.Txt( "SpriteRenderer is not available in this S&box version." );

			var comp = go.Components.Create( spriteTd );

			if ( args.TryGetProperty( "color", out var colEl ) && colEl.ValueKind == JsonValueKind.String )
			{
				try
				{
					var prop = spriteTd.TargetType.GetProperty( "Color" );
					if ( prop != null ) prop.SetValue( comp, Color.Parse( colEl.GetString() ) ?? default );
				}
				catch { }
			}

			return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
			{
				message = $"Created SpriteRenderer '{go.Name}'.",
				id       = go.Id.ToString(),
				position = OzmiumSceneHelpers.V3( go.WorldPosition )
			}, _json ) );
		}
		catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
	}

	// ── create_trail_renderer ───────────────────────────────────────────────

	internal static object CreateTrailRenderer( JsonElement args )
	{
		var scene = OzmiumSceneHelpers.ResolveScene();
		if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );

		float  x    = OzmiumSceneHelpers.Get( args, "x", 0f );
		float  y    = OzmiumSceneHelpers.Get( args, "y", 0f );
		float  z    = OzmiumSceneHelpers.Get( args, "z", 0f );
		string name  = OzmiumSceneHelpers.Get( args, "name", "Trail Renderer" );

		try
		{
			var go = scene.CreateObject();
			go.Name = name;
			go.WorldPosition = new Vector3( x, y, z );

			var trail = go.Components.Create<TrailRenderer>();
			trail.MaxPoints = OzmiumSceneHelpers.Get( args, "maxPoints", trail.MaxPoints );
			trail.PointDistance = OzmiumSceneHelpers.Get( args, "pointDistance", trail.PointDistance );
			trail.LifeTime = OzmiumSceneHelpers.Get( args, "lifetime", trail.LifeTime );
			trail.Emitting = OzmiumSceneHelpers.Get( args, "emitting", trail.Emitting );

			if ( args.TryGetProperty( "color", out var colEl ) && colEl.ValueKind == JsonValueKind.String )
			{
				try { trail.Color = Color.Parse( colEl.GetString() ) ?? default; } catch { }
			}

			return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
			{
				message = $"Created TrailRenderer '{go.Name}'.",
				id       = go.Id.ToString(),
				position = OzmiumSceneHelpers.V3( go.WorldPosition )
			}, _json ) );
		}
		catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
	}

	// ── create_model_renderer ──────────────────────────────────────────────

	internal static object CreateModelRenderer( JsonElement args )
	{
		var scene = OzmiumSceneHelpers.ResolveScene();
		if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );

		float  x    = OzmiumSceneHelpers.Get( args, "x", 0f );
		float  y    = OzmiumSceneHelpers.Get( args, "y", 0f );
		float  z    = OzmiumSceneHelpers.Get( args, "z", 0f );
		string name  = OzmiumSceneHelpers.Get( args, "name", "Model Renderer" );

		try
		{
			var go = scene.CreateObject();
			go.Name = name;
			go.WorldPosition = new Vector3( x, y, z );

			var mr = go.Components.Create<ModelRenderer>();

			if ( args.TryGetProperty( "modelPath", out var mpEl ) && mpEl.ValueKind == JsonValueKind.String )
			{
				var model = Model.Load( mpEl.GetString() );
				if ( model != null ) mr.Model = model;
			}

			if ( args.TryGetProperty( "tint", out var tintEl ) && tintEl.ValueKind == JsonValueKind.String )
			{
				try { mr.Tint = Color.Parse( tintEl.GetString() ) ?? default; } catch { }
			}

			mr.RenderType = OzmiumSceneHelpers.Get( args, "castsShadows", true ) 
				? ModelRenderer.ShadowRenderType.On 
				: ModelRenderer.ShadowRenderType.Off;

			if ( args.TryGetProperty( "bodyGroups", out var bgEl ) && bgEl.ValueKind == JsonValueKind.String )
			{
				if ( ulong.TryParse( bgEl.GetString(), out var bg ) ) mr.BodyGroups = bg;
			}

			return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
			{
				message = $"Created ModelRenderer '{go.Name}'.",
				id       = go.Id.ToString(),
				position = OzmiumSceneHelpers.V3( go.WorldPosition ),
				model    = mr.Model?.ResourcePath ?? "null"
			}, _json ) );
		}
		catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
	}

	// ── create_skinned_model ──────────────────────────────────────────────

	internal static object CreateSkinnedModel( JsonElement args )
	{
		var scene = OzmiumSceneHelpers.ResolveScene();
		if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );

		float  x    = OzmiumSceneHelpers.Get( args, "x", 0f );
		float  y    = OzmiumSceneHelpers.Get( args, "y", 0f );
		float  z    = OzmiumSceneHelpers.Get( args, "z", 0f );
		string name  = OzmiumSceneHelpers.Get( args, "name", "Skinned Model" );

		try
		{
			var go = scene.CreateObject();
			go.Name = name;
			go.WorldPosition = new Vector3( x, y, z );

			var sk = go.Components.Create<SkinnedModelRenderer>();

			if ( args.TryGetProperty( "modelPath", out var mpEl ) && mpEl.ValueKind == JsonValueKind.String )
			{
				var model = Model.Load( mpEl.GetString() );
				if ( model != null ) sk.Model = model;
			}

			if ( args.TryGetProperty( "tint", out var tintEl ) && tintEl.ValueKind == JsonValueKind.String )
			{
				try { sk.Tint = Color.Parse( tintEl.GetString() ) ?? default; } catch { }
			}

			sk.UseAnimGraph = OzmiumSceneHelpers.Get( args, "useAnimGraph", true );
			sk.CreateBoneObjects = OzmiumSceneHelpers.Get( args, "createBoneObjects", false );

			return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
			{
				message = $"Created SkinnedModelRenderer '{go.Name}'.",
				id       = go.Id.ToString(),
				position = OzmiumSceneHelpers.V3( go.WorldPosition ),
				model    = sk.Model?.ResourcePath ?? "null",
				useAnimGraph = sk.UseAnimGraph
			}, _json ) );
		}
		catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
	}

	// ── create_screen_panel ──────────────────────────────────────────────

	internal static object CreateScreenPanel( JsonElement args )
	{
		var scene = OzmiumSceneHelpers.ResolveScene();
		if ( scene == null ) return OzmiumSceneHelpers.Txt( "No active scene." );

		float  x    = OzmiumSceneHelpers.Get( args, "x", 0f );
		float  y    = OzmiumSceneHelpers.Get( args, "y", 0f );
		float  z    = OzmiumSceneHelpers.Get( args, "z", 0f );
		string name  = OzmiumSceneHelpers.Get( args, "name", "Screen Panel" );

		try
		{
			var go = scene.CreateObject();
			go.Name = name;
			go.WorldPosition = new Vector3( x, y, z );

			var sp = go.Components.Create<ScreenPanel>();
			sp.Opacity = OzmiumSceneHelpers.Get( args, "opacity", 1f );
			sp.Scale = OzmiumSceneHelpers.Get( args, "scale", 1f );
			sp.ZIndex = OzmiumSceneHelpers.Get( args, "zIndex", 100 );
			sp.AutoScreenScale = OzmiumSceneHelpers.Get( args, "autoScreenScale", true );

			return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
			{
				message = $"Created ScreenPanel '{go.Name}'.",
				id       = go.Id.ToString(),
				position = OzmiumSceneHelpers.V3( go.WorldPosition ),
				opacity  = sp.Opacity,
				scale    = sp.Scale,
				zIndex   = sp.ZIndex
			}, _json ) );
		}
		catch ( Exception ex ) { return OzmiumSceneHelpers.Txt( $"Error: {ex.Message}" ); }
	}

	// ── Schemas ─────────────────────────────────────────────────────────────

	private static Dictionary<string, object> S( string name, string desc, Dictionary<string, object> props, string[] req = null )
	{
		var schema = new Dictionary<string, object> { ["type"] = "object", ["properties"] = props };
		if ( req != null ) schema["required"] = req;
		return new Dictionary<string, object> { ["name"] = name, ["description"] = desc, ["inputSchema"] = schema };
	}

	internal static Dictionary<string, object> SchemaCreateTextRenderer => S( "create_text_renderer",
		"Create a GO with a TextRenderer component for 3D world text.",
		new Dictionary<string, object>
		{
			["x"]                 = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
			["y"]                 = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
			["z"]                 = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
			["name"]              = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
			["text"]              = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Text to display." },
			["fontSize"]          = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Font size." },
			["color"]             = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Text color hex." },
			["scale"]             = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World size scale." },
			["horizontalAlignment"] = new Dictionary<string, object>
			{
				["type"] = "string", ["description"] = "Horizontal alignment.",
				["enum"] = new[] { "Left", "Center", "Right" }
			},
			["verticalAlignment"]   = new Dictionary<string, object>
			{
				["type"] = "string", ["description"] = "Vertical alignment.",
				["enum"] = new[] { "Top", "Center", "Bottom" }
			}
		} );

	internal static Dictionary<string, object> SchemaCreateLineRenderer => S( "create_line_renderer",
		"Create a GO with a LineRenderer component for lines/paths.",
		new Dictionary<string, object>
		{
			["x"]      = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
			["y"]      = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
			["z"]      = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
			["name"]   = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
			["points"] = new Dictionary<string, object>
			{
				["type"] = "array", ["description"] = "Array of Vector3 points {x,y,z}.",
				["items"] = new Dictionary<string, object> { ["type"] = "object",
					["properties"] = new Dictionary<string, object>
					{
						["x"] = new Dictionary<string, object> { ["type"] = "number" },
						["y"] = new Dictionary<string, object> { ["type"] = "number" },
						["z"] = new Dictionary<string, object> { ["type"] = "number" }
					}
				}
			},
			["color"]  = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Line color hex." }
		} );

	internal static Dictionary<string, object> SchemaCreateSpriteRenderer => S( "create_sprite_renderer",
		"Create a GO with a SpriteRenderer for 2D billboards in 3D.",
		new Dictionary<string, object>
		{
			["x"]     = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
			["y"]     = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
			["z"]     = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
			["name"]  = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
			["color"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Sprite color hex." }
		} );

	internal static Dictionary<string, object> SchemaCreateTrailRenderer => S( "create_trail_renderer",
		"Create a GO with a TrailRenderer for object trails.",
		new Dictionary<string, object>
		{
			["x"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
			["y"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
			["z"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
			["name"]          = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
			["maxPoints"]     = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Maximum trail points." },
			["pointDistance"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Distance between trail points." },
			["lifetime"]      = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Trail point lifetime in seconds." },
			["emitting"]      = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Whether the trail emits points." },
			["color"]         = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Trail color hex." }
		} );

	internal static Dictionary<string, object> SchemaCreateModelRenderer => S( "create_model_renderer",
		"Create a GO with a ModelRenderer (static model display). Unlike Prop, this is purely visual — no physics, no breakable behavior. Essential for decorative models.",
		new Dictionary<string, object>
		{
			["x"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
			["y"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
			["z"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
			["name"]          = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
			["modelPath"]     = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Model asset path (e.g. 'models/citizen.vmdl')." },
			["tint"]          = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Color tint hex (default '#FFFFFF')." },
			["castsShadows"]  = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Cast shadows (default true)." },
			["bodyGroups"]    = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Body group mask (ulong)." }
		} );

	internal static Dictionary<string, object> SchemaCreateSkinnedModel => S( "create_skinned_model",
		"Create a GO with a SkinnedModelRenderer for animated models (NPCs, characters). THE component for animated characters — lets AI set up NPCs and animated props.",
		new Dictionary<string, object>
		{
			["x"]                = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
			["y"]                = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
			["z"]                = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
			["name"]              = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
			["modelPath"]         = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Model asset path (e.g. 'models/citizen.vmdl')." },
			["tint"]              = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Color tint hex." },
			["useAnimGraph"]      = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Use animation graph (default true)." },
			["createBoneObjects"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Create bone objects (default false)." }
		} );

	internal static Dictionary<string, object> SchemaCreateScreenPanel => S( "create_screen_panel",
		"Create a GO with a ScreenPanel for 2D HUD-style UI (health bars, score counters, debug overlays). Renders flat to screen, unlike WorldPanel which is 3D.",
		new Dictionary<string, object>
		{
			["x"]               = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
			["y"]               = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
			["z"]               = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
			["name"]             = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
			["opacity"]          = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Panel opacity (default 1)." },
			["scale"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Panel scale (default 1)." },
			["zIndex"]           = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Z-order index (default 100)." },
			["autoScreenScale"]  = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Auto-scale with screen resolution (default true)." }
		} );

	// ── create_render_entity (Omnibus) ──────────────────────────────────────

	internal static object CreateRenderEntity( JsonElement args )
	{
		string renderType = OzmiumSceneHelpers.Get( args, "renderType", "" );
		return renderType switch
		{
			"TextRenderer" => CreateTextRenderer( args ),
			"LineRenderer" => CreateLineRenderer( args ),
			"SpriteRenderer" => CreateSpriteRenderer( args ),
			"TrailRenderer" => CreateTrailRenderer( args ),
			"ModelRenderer" => CreateModelRenderer( args ),
			"SkinnedModelRenderer" => CreateSkinnedModel( args ),
			"ScreenPanel" => CreateScreenPanel( args ),
			_ => OzmiumSceneHelpers.Txt( $"Unknown renderType: {renderType}" )
		};
	}

	internal static Dictionary<string, object> SchemaCreateRenderEntity => S( "create_render_entity",
		"Create a rendering entity (TextRenderer, LineRenderer, SpriteRenderer, TrailRenderer, ModelRenderer, SkinnedModelRenderer, ScreenPanel).",
		new Dictionary<string, object>
		{
			["renderType"]          = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Type of renderer.", ["enum"] = new[] { "TextRenderer", "LineRenderer", "SpriteRenderer", "TrailRenderer", "ModelRenderer", "SkinnedModelRenderer", "ScreenPanel" } },
			["x"]                   = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World X position." },
			["y"]                   = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Y position." },
			["z"]                   = new Dictionary<string, object> { ["type"] = "number", ["description"] = "World Z position." },
			["name"]                = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
			["text"]                = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Text to display." },
			["fontSize"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Font size." },
			["color"]               = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Color hex." },
			["tint"]                = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Color tint hex." },
			["scale"]               = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Size scale." },
			["horizontalAlignment"] = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Horizontal alignment." },
			["verticalAlignment"]   = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Vertical alignment." },
			["points"]              = new Dictionary<string, object> { ["type"] = "array",  ["description"] = "Array of Vector3 points {x,y,z}.", ["items"] = new Dictionary<string, object> { ["type"] = "object", ["properties"] = new Dictionary<string, object> { ["x"] = new Dictionary<string, object> { ["type"] = "number" }, ["y"] = new Dictionary<string, object> { ["type"] = "number" }, ["z"] = new Dictionary<string, object> { ["type"] = "number" } } } },
			["maxPoints"]           = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Maximum trail points." },
			["pointDistance"]       = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Distance between trail points." },
			["lifetime"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Trail point lifetime in seconds." },
			["emitting"]            = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Whether the trail emits points." },
			["modelPath"]           = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Model asset path." },
			["castsShadows"]        = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Cast shadows." },
			["bodyGroups"]          = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Body group mask (ulong)." },
			["useAnimGraph"]        = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Use animation graph." },
			["createBoneObjects"]   = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Create bone objects." },
			["opacity"]             = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Panel opacity." },
			["zIndex"]              = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Z-order index." },
			["autoScreenScale"]     = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Auto-scale with screen resolution." }
		},
		new[] { "renderType" } );
}