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

namespace SboxMcpServer;

/// <summary>
/// Camera MCP tools: create_camera, configure_camera.
/// </summary>
internal static class CameraToolHandlers
{
	private static readonly JsonSerializerOptions _json = new()
	{
		PropertyNamingPolicy   = JsonNamingPolicy.CamelCase,
		DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
	};

	// ── create_camera ──────────────────────────────────────────────────────

	internal static object CreateCamera( 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", 100f );
		float  z     = OzmiumSceneHelpers.Get( args, "z", 0f );
		float  pitch = OzmiumSceneHelpers.Get( args, "pitch", -90f );
		float  yaw   = OzmiumSceneHelpers.Get( args, "yaw", 0f );
		float  roll  = OzmiumSceneHelpers.Get( args, "roll", 0f );
		string name  = OzmiumSceneHelpers.Get( args, "name", "Camera" );

		try
		{
			var go = scene.CreateObject();
			go.Name = name;
			go.WorldPosition = new Vector3( x, y, z );
			go.WorldRotation = Rotation.From( pitch, yaw, roll );

			var cam = go.Components.Create<CameraComponent>();
			cam.FieldOfView = OzmiumSceneHelpers.Get( args, "fov", 60f );
			cam.ZNear = OzmiumSceneHelpers.Get( args, "zNear", 10f );
			cam.ZFar = OzmiumSceneHelpers.Get( args, "zFar", 10000f );
			cam.IsMainCamera = OzmiumSceneHelpers.Get( args, "isMainCamera", true );

			if ( args.TryGetProperty( "orthographic", out var orthoEl ) )
				cam.Orthographic = orthoEl.GetBoolean();
			if ( args.TryGetProperty( "orthographicHeight", out var ohEl ) )
				cam.OrthographicHeight = ohEl.GetSingle();

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

	// ── configure_camera ────────────────────────────────────────────────────

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

		string id   = OzmiumSceneHelpers.Get( args, "id",   (string)null );
		string name = OzmiumSceneHelpers.Get( args, "name", (string)null );

		var go = OzmiumSceneHelpers.FindGoWithComponent<CameraComponent>( scene, id, name );
		if ( go == null ) return OzmiumSceneHelpers.Txt( $"No object with CameraComponent found (id={id ?? "null"}, name={name ?? "null"})." );

		var cam = go.Components.Get<CameraComponent>();

		try
		{
			if ( args.TryGetProperty( "fov", out var fovEl ) )
				cam.FieldOfView = fovEl.GetSingle();
			if ( args.TryGetProperty( "zNear", out var znEl ) )
				cam.ZNear = znEl.GetSingle();
			if ( args.TryGetProperty( "zFar", out var zfEl ) )
				cam.ZFar = zfEl.GetSingle();
			if ( args.TryGetProperty( "isMainCamera", out var mcEl ) )
				cam.IsMainCamera = mcEl.GetBoolean();
			if ( args.TryGetProperty( "orthographic", out var orthoEl ) )
				cam.Orthographic = orthoEl.GetBoolean();
			if ( args.TryGetProperty( "orthographicHeight", out var ohEl ) )
				cam.OrthographicHeight = ohEl.GetSingle();
			if ( args.TryGetProperty( "backgroundColor", out var bgEl ) && bgEl.ValueKind == JsonValueKind.String )
			{
				try { cam.BackgroundColor = Color.Parse( bgEl.GetString() ) ?? default; } catch { }
			}
			if ( args.TryGetProperty( "priority", out var prEl ) )
				cam.Priority = prEl.GetInt32();

			return OzmiumSceneHelpers.Txt( $"Configured CameraComponent on '{go.Name}'." );
		}
		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> SchemaCreateCamera => S( "create_camera",
		"Creates a GO with a CameraComponent.",
		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." },
			["pitch"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Pitch rotation in degrees." },
			["yaw"]              = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Yaw rotation in degrees." },
			["roll"]             = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Roll rotation in degrees." },
			["name"]             = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Name for the GO." },
			["fov"]              = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Field of view in degrees." },
			["zNear"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Near clip plane distance." },
			["zFar"]             = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Far clip plane distance." },
			["isMainCamera"]      = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Whether this is the main camera (default true)." },
			["orthographic"]     = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Use orthographic projection." },
			["orthographicHeight"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Orthographic height." }
		} );

	internal static Dictionary<string, object> SchemaConfigureCamera => S( "configure_camera",
		"Configures an existing CameraComponent.",
		new Dictionary<string, object>
	{
			["id"]               = new Dictionary<string, object> { ["type"] = "string", ["description"] = "GUID." },
			["name"]             = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Exact name." },
			["fov"]              = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Field of view." },
			["zNear"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Near clip plane." },
			["zFar"]             = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Far clip plane." },
			["isMainCamera"]      = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Main camera flag." },
			["orthographic"]     = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Orthographic projection." },
			["orthographicHeight"] = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Orthographic height." },
			["backgroundColor"]   = new Dictionary<string, object> { ["type"] = "string", ["description"] = "Background color hex." },
			["priority"]         = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Camera priority (higher renders on top)." }
		} );
}