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

namespace SboxMcpServer;

/// <summary>
/// Navigation MCP tools: create_nav_mesh_agent, create_nav_mesh_link, create_nav_mesh_area.
/// </summary>
internal static class NavigationToolHandlers
{
	private static readonly JsonSerializerOptions _json = new()
	{
		PropertyNamingPolicy   = JsonNamingPolicy.CamelCase,
		DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
	};

	// ── create_nav_mesh_agent ──────────────────────────────────────────────

	internal static object CreateNavMeshAgent( 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", "NavMesh Agent" );

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

			var agent = go.Components.Create<NavMeshAgent>();
			agent.Height = OzmiumSceneHelpers.Get( args, "height", agent.Height );
			agent.Radius = OzmiumSceneHelpers.Get( args, "radius", agent.Radius );
			agent.MaxSpeed = OzmiumSceneHelpers.Get( args, "maxSpeed", agent.MaxSpeed );
			agent.Acceleration = OzmiumSceneHelpers.Get( args, "acceleration", agent.Acceleration );
			agent.UpdatePosition = OzmiumSceneHelpers.Get( args, "updatePosition", agent.UpdatePosition );
			agent.UpdateRotation = OzmiumSceneHelpers.Get( args, "updateRotation", agent.UpdateRotation );
			agent.AutoTraverseLinks = OzmiumSceneHelpers.Get( args, "autoTraverseLinks", agent.AutoTraverseLinks );

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

	// ── create_nav_mesh_link ──────────────────────────────────────────────

	internal static object CreateNavMeshLink( 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", "NavMesh Link" );

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

			var link = go.Components.Create<NavMeshLink>();
			link.IsBiDirectional = OzmiumSceneHelpers.Get( args, "isBiDirectional", link.IsBiDirectional );
			link.ConnectionRadius = OzmiumSceneHelpers.Get( args, "connectionRadius", link.ConnectionRadius );

			if ( args.TryGetProperty( "localStartPosition", out var startPosEl ) && startPosEl.ValueKind == JsonValueKind.Object )
			{
				link.LocalStartPosition = new Vector3(
					OzmiumSceneHelpers.Get( startPosEl, "x", 0f ),
					OzmiumSceneHelpers.Get( startPosEl, "y", 0f ),
					OzmiumSceneHelpers.Get( startPosEl, "z", 0f ) );
			}
			else
			{
				link.LocalStartPosition = OzmiumSceneHelpers.Get( args, "startX", 0f ) * Vector3.Right
					+ OzmiumSceneHelpers.Get( args, "startY", 0f ) * Vector3.Up
					+ OzmiumSceneHelpers.Get( args, "startZ", 0f ) * Vector3.Forward;
			}

			if ( args.TryGetProperty( "localEndPosition", out var endPosEl ) && endPosEl.ValueKind == JsonValueKind.Object )
			{
				link.LocalEndPosition = new Vector3(
					OzmiumSceneHelpers.Get( endPosEl, "x", 0f ),
					OzmiumSceneHelpers.Get( endPosEl, "y", 0f ),
					OzmiumSceneHelpers.Get( endPosEl, "z", 0f ) );
			}
			else
			{
				link.LocalEndPosition = OzmiumSceneHelpers.Get( args, "endX", 0f ) * Vector3.Right
					+ OzmiumSceneHelpers.Get( args, "endY", 0f ) * Vector3.Up
					+ OzmiumSceneHelpers.Get( args, "endZ", 0f ) * Vector3.Forward;
			}

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

	// ── create_nav_mesh_area ──────────────────────────────────────────────

	internal static object CreateNavMeshArea( 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", "NavMesh Area" );

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

			var area = go.Components.Create<NavMeshArea>();
			area.IsBlocker = OzmiumSceneHelpers.Get( args, "isBlocker", true );

				return OzmiumSceneHelpers.Txt( JsonSerializer.Serialize( new
			{
				message = $"Created NavMeshArea '{go.Name}'.",
				id       = go.Id.ToString(),
				position = OzmiumSceneHelpers.V3( go.WorldPosition ),
				isBlocker = area.IsBlocker
			}, _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> SchemaCreateNavMeshAgent => S( "create_nav_mesh_agent",
		"Create a GO with a NavMeshAgent component for AI navigation.",
		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." },
			["height"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Agent height." },
			["radius"]            = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Agent radius." },
			["maxSpeed"]          = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Maximum movement speed." },
			["acceleration"]      = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Movement acceleration." },
			["updatePosition"]    = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Update GO position each frame." },
			["updateRotation"]    = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Face movement direction." },
			["autoTraverseLinks"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Auto-traverse nav links." }
		} );

	internal static Dictionary<string, object> SchemaCreateNavMeshLink => S( "create_nav_mesh_link",
		"Create a NavMeshLink for connecting navigation mesh polygons (ladders, jumps, teleports).",
		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." },
			["localStartPosition"] = new Dictionary<string, object> { ["type"] = "object", ["description"] = "Start position relative to GO {x,y,z}." },
			["localEndPosition"]   = new Dictionary<string, object> { ["type"] = "object", ["description"] = "End position relative to GO {x,y,z}." },
			["isBiDirectional"]    = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Link is bidirectional (default true)." },
			["connectionRadius"]  = new Dictionary<string, object> { ["type"] = "number", ["description"] = "Connection search radius." }
		} );

	internal static Dictionary<string, object> SchemaCreateNavMeshArea => S( "create_nav_mesh_area",
		"Create a NavMeshArea volume that blocks or modifies navmesh generation.",
		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." },
			["isBlocker"] = new Dictionary<string, object> { ["type"] = "boolean", ["description"] = "Block navmesh generation in this area (default true)." }
		} );
}