//! Model Context Protocol (MCP) server for AI-driven scene manipulation.
//!
//! When the `mcp` feature is enabled, the engine starts an HTTP-based MCP server
//! (default `http://127.0.0.1:3333/mcp`) during initialization. Any MCP-compatible client
//! (Claude Code, Claude Desktop, or custom tooling) can connect and manipulate the
//! running scene through structured tool calls.
//!
//! The server exposes 50+ tools organized by category:
//!
//! | Category | Tools |
//! |----------|-------|
//! | Entities | `list_entities`, `query_entity`, `spawn_entity`, `despawn_entity`, `clear_scene` |
//! | Transforms | `set_position`, `set_rotation`, `set_scale`, `set_parent`, `set_visibility` |
//! | Materials | `set_material_color`, `set_emissive`, `set_material`, `set_casts_shadow` |
//! | Lighting | `spawn_light`, `set_light` |
//! | Camera | `set_camera` |
//! | Assets | `load_asset`, `spawn_prefab`, `list_loaded_assets` |
//! | Environment | `set_atmosphere`, `load_hdr`, `set_graphics` |
//! | Effects | `spawn_water`, `spawn_particles`, `set_particles`, `spawn_decal` |
//! | Text | `spawn_hud_text`, `set_hud_text`, `spawn_3d_text` |
//! | Physics | `add_rigid_body`, `add_collider`, `apply_impulse`, `apply_force`, `set_velocity` |
//! | Animation | `play_animation`, `stop_animation`, `list_animations` |
//! | Scripting | `set_script`, `remove_script`, `set_game_state`, `get_game_state` |
//! | Debug | `add_line`, `add_lines`, `clear_lines`, `get_input`, `get_time` |
//! | Batch | `batch`, `run` |
//!
//! # Connecting Claude Code
//!
//! ```bash
//! claude mcp add --transport http nightshade http://127.0.0.1:3333/mcp
//! ```
//!
//! # Intercepting Commands
//!
//! Applications can intercept commands before the engine processes them via
//! [`State::handle_mcp_command`](crate::run::State::handle_mcp_command), and react
//! to results via [`State::after_mcp_command`](crate::run::State::after_mcp_command).
//!
//! # Architecture
//!
//! The server runs on a background thread using tokio and axum. Commands are pushed
//! into a `Mutex<Vec<PendingCommand>>` shared between the server and engine threads,
//! and each command carries a oneshot channel for its response. Commands are drained
//! and processed once per frame, after `run_systems()` and before rendering.

pub const MCP_DEFAULT_HOST: &str = "127.0.0.1";
pub const MCP_DEFAULT_PORT: u16 = 3333;

use rmcp::{
    ServerHandler,
    handler::server::{router::tool::ToolRouter, tool::Parameters},
    model::*,
    tool, tool_handler, tool_router,
    transport::streamable_http_server::{
        StreamableHttpService, session::local::LocalSessionManager,
    },
};
use std::sync::{Arc, Mutex};

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct ListEntitiesRequest {}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct QueryEntityRequest {
    #[schemars(description = "Name of the entity to query")]
    pub name: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SpawnEntityRequest {
    #[schemars(description = "Unique name for the entity")]
    pub name: String,
    #[schemars(description = "Mesh type: Cube, Sphere, Cylinder, Cone, Plane, Torus")]
    pub mesh: String,
    #[schemars(description = "Position as [x, y, z]")]
    pub position: [f32; 3],
    #[schemars(description = "Scale as [x, y, z], defaults to [1, 1, 1]")]
    pub scale: Option<[f32; 3]>,
    #[schemars(description = "Base color as [r, g, b, a] where each component is 0.0-1.0")]
    pub color: Option<[f32; 4]>,
    #[schemars(
        description = "Emissive color as [r, g, b]. Values > 1.0 create HDR bloom/glow effects"
    )]
    pub emissive: Option<[f32; 3]>,
    #[schemars(
        description = "Parent entity name. The spawned entity will be a child of this entity"
    )]
    pub parent: Option<String>,
    #[schemars(description = "Alpha mode: opaque (default), mask, or blend (for transparency)")]
    pub alpha: Option<String>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct DespawnEntityRequest {
    #[schemars(description = "Name of the entity to despawn")]
    pub name: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetPositionRequest {
    #[schemars(description = "Name of the entity")]
    pub name: String,
    #[schemars(description = "New position as [x, y, z]")]
    pub position: [f32; 3],
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetRotationRequest {
    #[schemars(description = "Name of the entity")]
    pub name: String,
    #[schemars(description = "Rotation as euler angles [pitch, yaw, roll] in radians")]
    pub rotation: [f32; 3],
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetScaleRequest {
    #[schemars(description = "Name of the entity")]
    pub name: String,
    #[schemars(description = "New scale as [x, y, z]")]
    pub scale: [f32; 3],
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetMaterialColorRequest {
    #[schemars(description = "Name of the entity")]
    pub name: String,
    #[schemars(description = "Base color as [r, g, b, a] where each component is 0.0-1.0")]
    pub color: [f32; 4],
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetEmissiveRequest {
    #[schemars(description = "Name of the entity")]
    pub name: String,
    #[schemars(
        description = "Emissive color as [r, g, b]. Values > 1.0 create HDR bloom/glow effects"
    )]
    pub emissive: [f32; 3],
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetParentRequest {
    #[schemars(description = "Name of the child entity")]
    pub name: String,
    #[schemars(description = "Name of the parent entity, or null/empty to unparent")]
    pub parent: Option<String>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct LoadAssetRequest {
    #[schemars(description = "Absolute file path to .glb, .gltf, or .fbx file")]
    pub path: String,
    #[schemars(description = "Unique name to reference this asset")]
    pub asset_name: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SpawnPrefabRequest {
    #[schemars(description = "Name of a previously loaded asset")]
    pub asset_name: String,
    #[schemars(description = "Unique name for the spawned entity")]
    pub entity_name: String,
    #[schemars(description = "Position as [x, y, z]")]
    pub position: [f32; 3],
    #[schemars(description = "Scale as [x, y, z], defaults to [1, 1, 1]")]
    pub scale: Option<[f32; 3]>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct ListLoadedAssetsRequest {}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct ClearSceneRequest {}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetAtmosphereRequest {
    #[schemars(description = "Atmosphere type: none, sky, cloudy_sky, space, nebula, sunset, hdr")]
    pub atmosphere: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct LoadHdrRequest {
    #[schemars(description = "Absolute file path to .hdr file")]
    pub path: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SpawnLightRequest {
    #[schemars(description = "Unique name for the light")]
    pub name: String,
    #[schemars(description = "Light type: point, spot, or directional")]
    pub light_type: String,
    #[schemars(description = "Position as [x, y, z]")]
    pub position: [f32; 3],
    #[schemars(description = "Light color as [r, g, b] (linear RGB, 0.0-1.0+)")]
    pub color: Option<[f32; 3]>,
    #[schemars(description = "Light intensity (lumens for point/spot, lux for directional)")]
    pub intensity: Option<f32>,
    #[schemars(description = "Maximum range for point/spot lights")]
    pub range: Option<f32>,
    #[schemars(description = "Inner cone angle in degrees for spot lights")]
    pub inner_cone_angle: Option<f32>,
    #[schemars(description = "Outer cone angle in degrees for spot lights")]
    pub outer_cone_angle: Option<f32>,
    #[schemars(description = "Whether this light casts shadows")]
    pub cast_shadows: Option<bool>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct AddLineRequest {
    #[schemars(description = "Start point as [x, y, z]")]
    pub start: [f32; 3],
    #[schemars(description = "End point as [x, y, z]")]
    pub end: [f32; 3],
    #[schemars(description = "Line color as [r, g, b, a]")]
    pub color: Option<[f32; 4]>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct AddLinesRequest {
    #[schemars(description = "Array of lines, each with start, end, and optional color")]
    pub lines: Vec<AddLineRequest>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct ClearLinesRequest {}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetGraphicsRequest {
    #[schemars(description = "Enable bloom post-processing")]
    pub bloom_enabled: Option<bool>,
    #[schemars(description = "Bloom intensity (0.0-1.0+)")]
    pub bloom_intensity: Option<f32>,
    #[schemars(description = "Enable SSAO (screen-space ambient occlusion)")]
    pub ssao_enabled: Option<bool>,
    #[schemars(description = "SSAO radius in world units")]
    pub ssao_radius: Option<f32>,
    #[schemars(description = "SSAO intensity")]
    pub ssao_intensity: Option<f32>,
    #[schemars(description = "SSAO bias to reduce self-occlusion")]
    pub ssao_bias: Option<f32>,
    #[schemars(description = "Enable fog")]
    pub fog_enabled: Option<bool>,
    #[schemars(description = "Fog color as [r, g, b]")]
    pub fog_color: Option<[f32; 3]>,
    #[schemars(description = "Fog start distance")]
    pub fog_start: Option<f32>,
    #[schemars(description = "Fog end distance")]
    pub fog_end: Option<f32>,
    #[schemars(
        description = "Tonemapping algorithm: aces, reinhard, reinhard_extended, uncharted2, agx, neutral, none"
    )]
    pub tonemap: Option<String>,
    #[schemars(description = "Exposure/gamma (typically 2.2)")]
    pub gamma: Option<f32>,
    #[schemars(description = "Color saturation (1.0 = neutral)")]
    pub saturation: Option<f32>,
    #[schemars(description = "Brightness offset (-1 to 1)")]
    pub brightness: Option<f32>,
    #[schemars(description = "Contrast multiplier (1.0 = neutral)")]
    pub contrast: Option<f32>,
    #[schemars(description = "Show debug grid")]
    pub show_grid: Option<bool>,
    #[schemars(description = "Background clear color as [r, g, b, a]")]
    pub clear_color: Option<[f32; 4]>,
    #[schemars(description = "Enable depth of field")]
    pub dof_enabled: Option<bool>,
    #[schemars(description = "Depth of field focus distance")]
    pub dof_focus_distance: Option<f32>,
    #[schemars(description = "Depth of field focus range (sharp region)")]
    pub dof_focus_range: Option<f32>,
    #[schemars(description = "Depth of field max blur radius")]
    pub dof_max_blur_radius: Option<f32>,
    #[schemars(description = "Enable unlit mode (disable all lighting)")]
    pub unlit_mode: Option<bool>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SpawnWaterRequest {
    #[schemars(description = "Unique name for the water plane")]
    pub name: String,
    #[schemars(description = "Position as [x, y, z]")]
    pub position: [f32; 3],
    #[schemars(description = "Scale as [x, y, z]")]
    pub scale: Option<[f32; 3]>,
    #[schemars(description = "Base color (deep water) as [r, g, b, a]")]
    pub base_color: Option<[f32; 4]>,
    #[schemars(description = "Water color (surface) as [r, g, b, a]")]
    pub water_color: Option<[f32; 4]>,
    #[schemars(description = "Wave height")]
    pub wave_height: Option<f32>,
    #[schemars(description = "Choppiness of waves")]
    pub choppy: Option<f32>,
    #[schemars(description = "Wave animation speed")]
    pub speed: Option<f32>,
    #[schemars(description = "Wave frequency")]
    pub frequency: Option<f32>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetLightRequest {
    #[schemars(description = "Name of the light entity to modify")]
    pub name: String,
    #[schemars(description = "Light color as [r, g, b] (linear RGB, 0.0-1.0+)")]
    pub color: Option<[f32; 3]>,
    #[schemars(description = "Light intensity (lumens for point/spot, lux for directional)")]
    pub intensity: Option<f32>,
    #[schemars(description = "Maximum range for point/spot lights")]
    pub range: Option<f32>,
    #[schemars(description = "Inner cone angle in degrees for spot lights")]
    pub inner_cone_angle: Option<f32>,
    #[schemars(description = "Outer cone angle in degrees for spot lights")]
    pub outer_cone_angle: Option<f32>,
    #[schemars(description = "Whether this light casts shadows")]
    pub cast_shadows: Option<bool>,
    #[schemars(description = "Shadow depth bias")]
    pub shadow_bias: Option<f32>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SpawnHudTextRequest {
    #[schemars(description = "Unique name for the HUD text element")]
    pub name: String,
    #[schemars(description = "The text content to display")]
    pub text: String,
    #[schemars(description = "Screen position as [x, y] in pixels from anchor")]
    pub position: Option<[f32; 2]>,
    #[schemars(
        description = "Anchor point: top_left, top_center, top_right, center_left, center, center_right, bottom_left, bottom_center, bottom_right"
    )]
    pub anchor: Option<String>,
    #[schemars(description = "Font size in pixels")]
    pub font_size: Option<f32>,
    #[schemars(description = "Text color as [r, g, b, a]")]
    pub color: Option<[f32; 4]>,
    #[schemars(description = "Text alignment: left, center, right")]
    pub alignment: Option<String>,
    #[schemars(description = "Outline width (0 = no outline)")]
    pub outline_width: Option<f32>,
    #[schemars(description = "Outline color as [r, g, b, a]")]
    pub outline_color: Option<[f32; 4]>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetHudTextRequest {
    #[schemars(description = "Name of the HUD text to modify")]
    pub name: String,
    #[schemars(description = "New text content")]
    pub text: Option<String>,
    #[schemars(description = "Screen position as [x, y]")]
    pub position: Option<[f32; 2]>,
    #[schemars(description = "Anchor point")]
    pub anchor: Option<String>,
    #[schemars(description = "Font size in pixels")]
    pub font_size: Option<f32>,
    #[schemars(description = "Text color as [r, g, b, a]")]
    pub color: Option<[f32; 4]>,
    #[schemars(description = "Text alignment: left, center, right")]
    pub alignment: Option<String>,
    #[schemars(description = "Outline width")]
    pub outline_width: Option<f32>,
    #[schemars(description = "Outline color as [r, g, b, a]")]
    pub outline_color: Option<[f32; 4]>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetCameraRequest {
    #[schemars(description = "Camera position as [x, y, z]")]
    pub position: Option<[f32; 3]>,
    #[schemars(description = "Camera target/look-at point as [x, y, z]")]
    pub target: Option<[f32; 3]>,
    #[schemars(description = "Field of view in degrees (for perspective cameras)")]
    pub fov: Option<f32>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetScriptRequest {
    #[schemars(description = "Name of the entity to add/update script on")]
    pub name: String,
    #[schemars(
        description = "Rhai script source code. Variables: pos_x/y/z, rot_x/y/z, scale_x/y/z (own transform), dt/time, mouse_x/y, pressed_keys/just_pressed_keys, state (shared map), entities (map: name->{x,y,z,scale_x,scale_y,scale_z}), entity_names (array of all names for iteration), do_despawn (despawn self), despawn_names (push names to despawn other entities), do_spawn_cube/sphere + spawn_cube/sphere_x/y/z"
    )]
    pub source: String,
    #[schemars(description = "Whether the script is enabled (default: true)")]
    pub enabled: Option<bool>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct RemoveScriptRequest {
    #[schemars(description = "Name of the entity to remove script from")]
    pub name: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetGameStateRequest {
    #[schemars(
        description = "Key-value pairs to set in the shared game state (scripts access via state[\"key\"])"
    )]
    pub values: std::collections::HashMap<String, f64>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct GetGameStateRequest {
    #[schemars(description = "Keys to retrieve from game state (empty = get all)")]
    pub keys: Option<Vec<String>>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetVisibilityRequest {
    #[schemars(description = "Name of the entity")]
    pub name: String,
    #[schemars(description = "Whether the entity is visible")]
    pub visible: bool,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct PlayAnimationRequest {
    #[schemars(description = "Name of the entity with AnimationPlayer")]
    pub name: String,
    #[schemars(description = "Animation clip name or index")]
    pub clip: String,
    #[schemars(description = "Whether to loop the animation (default: true)")]
    pub looping: Option<bool>,
    #[schemars(description = "Playback speed multiplier (default: 1.0)")]
    pub speed: Option<f32>,
    #[schemars(description = "Blend duration in seconds for crossfade (0 = instant switch)")]
    pub blend_duration: Option<f32>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct StopAnimationRequest {
    #[schemars(description = "Name of the entity")]
    pub name: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct ListAnimationsRequest {
    #[schemars(description = "Name of the entity with AnimationPlayer")]
    pub name: String,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SpawnParticlesRequest {
    #[schemars(description = "Unique name for the particle emitter")]
    pub name: String,
    #[schemars(description = "Position as [x, y, z]")]
    pub position: [f32; 3],
    #[schemars(
        description = "Preset type: fire, smoke, sparks, firework_explosion, firework_willow, firework_chrysanthemum"
    )]
    pub preset: String,
    #[schemars(description = "Color as [r, g, b] for applicable presets")]
    pub color: Option<[f32; 3]>,
    #[schemars(description = "Particle count for burst effects")]
    pub particle_count: Option<u32>,
    #[schemars(description = "Whether emitter is enabled (default: true)")]
    pub enabled: Option<bool>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetParticlesRequest {
    #[schemars(description = "Name of the particle emitter")]
    pub name: String,
    #[schemars(description = "Whether emitter is enabled")]
    pub enabled: Option<bool>,
    #[schemars(description = "Spawn rate (particles per second)")]
    pub spawn_rate: Option<f32>,
    #[schemars(description = "Emissive strength for glow")]
    pub emissive_strength: Option<f32>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct AddRigidBodyRequest {
    #[schemars(description = "Name of the entity")]
    pub name: String,
    #[schemars(description = "Body type: dynamic, kinematic, or static")]
    pub body_type: String,
    #[schemars(description = "Mass in kg (for dynamic bodies)")]
    pub mass: Option<f32>,
    #[schemars(description = "Initial linear velocity as [x, y, z]")]
    pub linear_velocity: Option<[f32; 3]>,
    #[schemars(description = "Initial angular velocity as [x, y, z]")]
    pub angular_velocity: Option<[f32; 3]>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct AddColliderRequest {
    #[schemars(description = "Name of the entity")]
    pub name: String,
    #[schemars(description = "Collider shape: ball, cuboid, capsule, cylinder")]
    pub shape: String,
    #[schemars(
        description = "Size params: [radius] for ball, [hx,hy,hz] for cuboid, [half_height,radius] for capsule/cylinder"
    )]
    pub size: Vec<f32>,
    #[schemars(description = "Friction coefficient (0.0-1.0)")]
    pub friction: Option<f32>,
    #[schemars(description = "Restitution/bounciness (0.0-1.0)")]
    pub restitution: Option<f32>,
    #[schemars(description = "Whether this is a sensor/trigger (no collision response)")]
    pub is_sensor: Option<bool>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct ApplyImpulseRequest {
    #[schemars(description = "Name of the entity with rigid body")]
    pub name: String,
    #[schemars(description = "Impulse vector as [x, y, z]")]
    pub impulse: [f32; 3],
    #[schemars(description = "Whether this is a torque impulse (angular) instead of linear")]
    pub torque: Option<bool>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct ApplyForceRequest {
    #[schemars(description = "Name of the entity with rigid body")]
    pub name: String,
    #[schemars(description = "Force vector as [x, y, z]")]
    pub force: [f32; 3],
    #[schemars(description = "Whether this is a torque (angular) instead of linear force")]
    pub torque: Option<bool>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetVelocityRequest {
    #[schemars(description = "Name of the entity with rigid body")]
    pub name: String,
    #[schemars(description = "Linear velocity as [x, y, z]")]
    pub linear: Option<[f32; 3]>,
    #[schemars(description = "Angular velocity as [x, y, z]")]
    pub angular: Option<[f32; 3]>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct Spawn3dTextRequest {
    #[schemars(description = "Unique name for the text")]
    pub name: String,
    #[schemars(description = "Text content")]
    pub text: String,
    #[schemars(description = "Position as [x, y, z]")]
    pub position: [f32; 3],
    #[schemars(description = "Font size")]
    pub font_size: Option<f32>,
    #[schemars(description = "Text color as [r, g, b, a]")]
    pub color: Option<[f32; 4]>,
    #[schemars(description = "Whether text always faces camera (billboard)")]
    pub billboard: Option<bool>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SpawnDecalRequest {
    #[schemars(description = "Unique name for the decal")]
    pub name: String,
    #[schemars(description = "Path to decal texture")]
    pub texture: String,
    #[schemars(description = "Position as [x, y, z]")]
    pub position: [f32; 3],
    #[schemars(description = "Size as [width, height, depth]")]
    pub size: Option<[f32; 3]>,
    #[schemars(description = "Rotation as euler angles [pitch, yaw, roll]")]
    pub rotation: Option<[f32; 3]>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetMaterialRequest {
    #[schemars(description = "Name of the entity")]
    pub name: String,
    #[schemars(description = "Base color as [r, g, b, a]")]
    pub base_color: Option<[f32; 4]>,
    #[schemars(description = "Emissive color as [r, g, b]")]
    pub emissive: Option<[f32; 3]>,
    #[schemars(description = "Roughness (0.0 = mirror, 1.0 = diffuse)")]
    pub roughness: Option<f32>,
    #[schemars(description = "Metallic factor (0.0 = dielectric, 1.0 = metal)")]
    pub metallic: Option<f32>,
    #[schemars(description = "Alpha mode: opaque, mask, blend")]
    pub alpha_mode: Option<String>,
    #[schemars(description = "Alpha cutoff for mask mode")]
    pub alpha_cutoff: Option<f32>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct SetCastsShadowRequest {
    #[schemars(description = "Name of the entity")]
    pub name: String,
    #[schemars(description = "Whether the entity casts shadows")]
    pub casts_shadow: bool,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct GetInputRequest {}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct GetTimeRequest {}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct BatchOperation {
    #[schemars(
        description = "Operation type: spawn_entity, despawn_entity, set_position, set_rotation, set_scale, set_material_color, set_emissive, set_parent, query_entity, list_entities, clear_scene, set_atmosphere"
    )]
    pub operation: String,
    #[schemars(description = "Entity name for the operation")]
    pub name: Option<String>,
    #[schemars(
        description = "Mesh type for spawn_entity: Cube, Sphere, Cylinder, Cone, Plane, Torus"
    )]
    pub mesh: Option<String>,
    #[schemars(description = "Position as [x, y, z]")]
    pub position: Option<[f32; 3]>,
    #[schemars(description = "Scale as [x, y, z]")]
    pub scale: Option<[f32; 3]>,
    #[schemars(description = "Rotation as euler angles [pitch, yaw, roll] in radians")]
    pub rotation: Option<[f32; 3]>,
    #[schemars(description = "Color as [r, g, b, a] where each component is 0.0-1.0")]
    pub color: Option<[f32; 4]>,
    #[schemars(
        description = "Emissive color as [r, g, b]. Values > 1.0 create HDR bloom/glow effects"
    )]
    pub emissive: Option<[f32; 3]>,
    #[schemars(description = "Parent entity name for spawn_entity or set_parent")]
    pub parent: Option<String>,
    #[schemars(description = "Alpha mode: opaque (default), mask, or blend (for transparency)")]
    pub alpha: Option<String>,
    #[schemars(
        description = "Atmosphere type for set_atmosphere: none, sky, cloudy_sky, space, nebula, sunset, hdr"
    )]
    pub atmosphere: Option<String>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct BatchRequest {
    #[schemars(description = "Array of operations to execute atomically in a single frame")]
    pub operations: Vec<BatchOperation>,
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
pub struct RunCommandsRequest {
    #[schemars(description = "Newline-separated commands. Format:\n\
        spawn <name> <mesh> <x>,<y>,<z> [scale:<s>] [color:<r>,<g>,<b>,<a>] [emissive:<r>,<g>,<b>] [parent:<name>] [alpha:<mode>]\n\
        despawn <name>\n\
        pos <name> <x>,<y>,<z>\n\
        rot <name> <pitch>,<yaw>,<roll>\n\
        scale <name> <x>,<y>,<z>\n\
        color <name> <r>,<g>,<b>,<a>\n\
        emissive <name> <r>,<g>,<b>\n\
        parent <child> <parent>\n\
        atmosphere <type> (none, sky, cloudy_sky, space, nebula, sunset, hdr)\n\
        hdr <path> (load .hdr file)\n\
        clear\n\
        Mesh types: Cube, Sphere, Cylinder, Cone, Plane, Torus\n\
        Alpha modes: opaque (default), mask, blend")]
    pub commands: String,
}

/// A command received from an MCP client to manipulate the engine scene.
///
/// Commands are deserialized from MCP tool calls and queued for processing on
/// the next frame. Implements [`Display`](std::fmt::Display) for human-readable
/// descriptions (e.g., `"Spawning Cube 'MyCube'"`).
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum McpCommand {
    ListEntities(ListEntitiesRequest),
    QueryEntity(QueryEntityRequest),
    SpawnEntity(SpawnEntityRequest),
    DespawnEntity(DespawnEntityRequest),
    SetPosition(SetPositionRequest),
    SetRotation(SetRotationRequest),
    SetScale(SetScaleRequest),
    SetMaterialColor(SetMaterialColorRequest),
    SetEmissive(SetEmissiveRequest),
    SetParent(SetParentRequest),
    LoadAsset(LoadAssetRequest),
    SpawnPrefab(SpawnPrefabRequest),
    ListLoadedAssets(ListLoadedAssetsRequest),
    ClearScene(ClearSceneRequest),
    SetAtmosphere(SetAtmosphereRequest),
    LoadHdr(LoadHdrRequest),
    SpawnLight(SpawnLightRequest),
    SetLight(SetLightRequest),
    AddLine(AddLineRequest),
    AddLines(AddLinesRequest),
    ClearLines(ClearLinesRequest),
    SetGraphics(SetGraphicsRequest),
    SpawnWater(SpawnWaterRequest),
    SpawnHudText(SpawnHudTextRequest),
    SetHudText(SetHudTextRequest),
    SetCamera(SetCameraRequest),
    SetScript(SetScriptRequest),
    RemoveScript(RemoveScriptRequest),
    SetGameState(SetGameStateRequest),
    GetGameState(GetGameStateRequest),
    SetVisibility(SetVisibilityRequest),
    PlayAnimation(PlayAnimationRequest),
    StopAnimation(StopAnimationRequest),
    ListAnimations(ListAnimationsRequest),
    SpawnParticles(SpawnParticlesRequest),
    SetParticles(SetParticlesRequest),
    AddRigidBody(AddRigidBodyRequest),
    AddCollider(AddColliderRequest),
    ApplyImpulse(ApplyImpulseRequest),
    ApplyForce(ApplyForceRequest),
    SetVelocity(SetVelocityRequest),
    Spawn3dText(Spawn3dTextRequest),
    SpawnDecal(SpawnDecalRequest),
    SetMaterial(SetMaterialRequest),
    SetCastsShadow(SetCastsShadowRequest),
    GetInput(GetInputRequest),
    GetTime(GetTimeRequest),
    Batch(BatchRequest),
    RunCommands(RunCommandsRequest),
}

impl std::fmt::Display for McpCommand {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            McpCommand::ListEntities(_) => write!(formatter, "Listing entities"),
            McpCommand::QueryEntity(request) => write!(formatter, "Querying '{}'", request.name),
            McpCommand::SpawnEntity(request) => {
                write!(formatter, "Spawning {} '{}'", request.mesh, request.name)
            }
            McpCommand::DespawnEntity(request) => write!(formatter, "Removing '{}'", request.name),
            McpCommand::SetPosition(request) => {
                write!(
                    formatter,
                    "Moving '{}' to ({:.1}, {:.1}, {:.1})",
                    request.name, request.position[0], request.position[1], request.position[2]
                )
            }
            McpCommand::SetRotation(request) => write!(formatter, "Rotating '{}'", request.name),
            McpCommand::SetScale(request) => write!(formatter, "Scaling '{}'", request.name),
            McpCommand::SetMaterialColor(request) => {
                write!(formatter, "Changing color of '{}'", request.name)
            }
            McpCommand::SetEmissive(request) => {
                write!(formatter, "Setting emissive on '{}'", request.name)
            }
            McpCommand::SetMaterial(request) => {
                write!(formatter, "Setting material on '{}'", request.name)
            }
            McpCommand::SetParent(request) => write!(formatter, "Reparenting '{}'", request.name),
            McpCommand::SetVisibility(request) => {
                let action = if request.visible { "Showing" } else { "Hiding" };
                write!(formatter, "{action} '{}'", request.name)
            }
            McpCommand::LoadAsset(request) => write!(formatter, "Loading asset '{}'", request.path),
            McpCommand::SpawnPrefab(request) => {
                write!(formatter, "Spawning prefab '{}'", request.entity_name)
            }
            McpCommand::ListLoadedAssets(_) => write!(formatter, "Listing loaded assets"),
            McpCommand::ClearScene(_) => write!(formatter, "Clearing scene"),
            McpCommand::SetAtmosphere(request) => {
                write!(formatter, "Setting atmosphere to '{}'", request.atmosphere)
            }
            McpCommand::LoadHdr(request) => write!(formatter, "Loading HDR '{}'", request.path),
            McpCommand::SpawnLight(request) => {
                write!(
                    formatter,
                    "Spawning {} light '{}'",
                    request.light_type, request.name
                )
            }
            McpCommand::SetLight(request) => {
                write!(formatter, "Adjusting light '{}'", request.name)
            }
            McpCommand::SetCamera(_) => write!(formatter, "Adjusting camera"),
            McpCommand::SpawnWater(request) => {
                write!(formatter, "Spawning water '{}'", request.name)
            }
            McpCommand::SpawnHudText(request) => {
                write!(formatter, "Spawning HUD text '{}'", request.name)
            }
            McpCommand::SetHudText(request) => {
                write!(formatter, "Updating HUD text '{}'", request.name)
            }
            McpCommand::Spawn3dText(request) => {
                write!(formatter, "Spawning 3D text '{}'", request.name)
            }
            McpCommand::SpawnParticles(request) => {
                write!(
                    formatter,
                    "Spawning {} particles '{}'",
                    request.preset, request.name
                )
            }
            McpCommand::SetParticles(request) => {
                write!(formatter, "Updating particles '{}'", request.name)
            }
            McpCommand::SpawnDecal(request) => {
                write!(formatter, "Spawning decal '{}'", request.name)
            }
            McpCommand::SetGraphics(_) => write!(formatter, "Updating graphics settings"),
            McpCommand::AddLine(_) => write!(formatter, "Drawing line"),
            McpCommand::AddLines(_) => write!(formatter, "Drawing lines"),
            McpCommand::ClearLines(_) => write!(formatter, "Clearing lines"),
            McpCommand::AddRigidBody(request) => {
                write!(formatter, "Adding rigid body to '{}'", request.name)
            }
            McpCommand::AddCollider(request) => {
                write!(formatter, "Adding collider to '{}'", request.name)
            }
            McpCommand::ApplyImpulse(request) => {
                write!(formatter, "Applying impulse to '{}'", request.name)
            }
            McpCommand::ApplyForce(request) => {
                write!(formatter, "Applying force to '{}'", request.name)
            }
            McpCommand::SetVelocity(request) => {
                write!(formatter, "Setting velocity on '{}'", request.name)
            }
            McpCommand::SetScript(request) => {
                write!(formatter, "Setting script '{}'", request.name)
            }
            McpCommand::RemoveScript(request) => {
                write!(formatter, "Removing script '{}'", request.name)
            }
            McpCommand::SetGameState(_) => write!(formatter, "Setting game state"),
            McpCommand::GetGameState(_) => write!(formatter, "Getting game state"),
            McpCommand::PlayAnimation(request) => {
                write!(
                    formatter,
                    "Playing animation '{}' on '{}'",
                    request.clip, request.name
                )
            }
            McpCommand::StopAnimation(request) => {
                write!(formatter, "Stopping animation on '{}'", request.name)
            }
            McpCommand::ListAnimations(request) => {
                write!(formatter, "Listing animations for '{}'", request.name)
            }
            McpCommand::SetCastsShadow(request) => {
                write!(formatter, "Setting shadow casting on '{}'", request.name)
            }
            McpCommand::GetInput(_) => write!(formatter, "Getting input state"),
            McpCommand::GetTime(_) => write!(formatter, "Getting time info"),
            McpCommand::Batch(_) => write!(formatter, "Executing batch commands"),
            McpCommand::RunCommands(_) => write!(formatter, "Running text commands"),
        }
    }
}

/// Response returned after processing an [`McpCommand`].
///
/// `Success(message)` indicates the command completed, `Error(message)` indicates failure.
/// Query commands return structured variants like `EntityList`, `EntityInfo`, etc.
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub enum McpResponse {
    EntityList(Vec<String>),
    EntityInfo {
        name: String,
        position: [f32; 3],
        rotation: [f32; 4],
        scale: [f32; 3],
    },
    AssetList(Vec<String>),
    AnimationList(Vec<String>),
    GameState(std::collections::HashMap<String, f64>),
    InputState {
        pressed_keys: Vec<String>,
        mouse_position: [f32; 2],
        mouse_buttons: [bool; 3],
    },
    TimeInfo {
        delta_time: f32,
        elapsed_time: f32,
    },
    Success(String),
    Error(String),
    BatchResult(Vec<McpResponse>),
}

pub type PendingCommand = (McpCommand, tokio::sync::oneshot::Sender<McpResponse>);

pub type CommandQueue = Arc<Mutex<Vec<PendingCommand>>>;

#[derive(Clone)]
pub struct McpServer {
    tool_router: ToolRouter<Self>,
    command_queue: CommandQueue,
}

#[tool_router]
impl McpServer {
    pub fn new(command_queue: CommandQueue) -> Self {
        Self {
            tool_router: Self::tool_router(),
            command_queue,
        }
    }

    async fn send_command_and_wait(&self, command: McpCommand) -> String {
        let (sender, receiver) = tokio::sync::oneshot::channel();
        {
            let mut queue = self.command_queue.lock().unwrap();
            queue.push((command, sender));
        }
        match tokio::time::timeout(std::time::Duration::from_secs(5), receiver).await {
            Ok(Ok(response)) => serde_json::to_string_pretty(&response)
                .unwrap_or_else(|_| format!("{:?}", response)),
            Ok(Err(_)) => "Command processing cancelled".to_string(),
            Err(_) => "Timeout waiting for response".to_string(),
        }
    }

    #[tool(description = "List all named entities in the scene")]
    async fn list_entities(&self, Parameters(request): Parameters<ListEntitiesRequest>) -> String {
        self.send_command_and_wait(McpCommand::ListEntities(request))
            .await
    }

    #[tool(description = "Query information about a specific entity by name")]
    async fn query_entity(&self, Parameters(request): Parameters<QueryEntityRequest>) -> String {
        self.send_command_and_wait(McpCommand::QueryEntity(request))
            .await
    }

    #[tool(
        description = "Spawn a new entity with a mesh at a position, optionally with color, emissive, parent, and alpha mode"
    )]
    async fn spawn_entity(&self, Parameters(request): Parameters<SpawnEntityRequest>) -> String {
        self.send_command_and_wait(McpCommand::SpawnEntity(request))
            .await
    }

    #[tool(description = "Despawn an entity by name")]
    async fn despawn_entity(
        &self,
        Parameters(request): Parameters<DespawnEntityRequest>,
    ) -> String {
        self.send_command_and_wait(McpCommand::DespawnEntity(request))
            .await
    }

    #[tool(description = "Set the position of an entity")]
    async fn set_position(&self, Parameters(request): Parameters<SetPositionRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetPosition(request))
            .await
    }

    #[tool(description = "Set the rotation of an entity using euler angles in radians")]
    async fn set_rotation(&self, Parameters(request): Parameters<SetRotationRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetRotation(request))
            .await
    }

    #[tool(description = "Set the scale of an entity")]
    async fn set_scale(&self, Parameters(request): Parameters<SetScaleRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetScale(request))
            .await
    }

    #[tool(description = "Set the material base color of an entity")]
    async fn set_material_color(
        &self,
        Parameters(request): Parameters<SetMaterialColorRequest>,
    ) -> String {
        self.send_command_and_wait(McpCommand::SetMaterialColor(request))
            .await
    }

    #[tool(
        description = "Set the emissive color of an entity. Values > 1.0 create HDR bloom/glow effects"
    )]
    async fn set_emissive(&self, Parameters(request): Parameters<SetEmissiveRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetEmissive(request))
            .await
    }

    #[tool(
        description = "Set or clear the parent of an entity. Pass null/empty parent to unparent"
    )]
    async fn set_parent(&self, Parameters(request): Parameters<SetParentRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetParent(request))
            .await
    }

    #[tool(description = "Load a 3D asset from file (.glb, .gltf, or .fbx)")]
    async fn load_asset(&self, Parameters(request): Parameters<LoadAssetRequest>) -> String {
        self.send_command_and_wait(McpCommand::LoadAsset(request))
            .await
    }

    #[tool(description = "Spawn a prefab from a previously loaded asset")]
    async fn spawn_prefab(&self, Parameters(request): Parameters<SpawnPrefabRequest>) -> String {
        self.send_command_and_wait(McpCommand::SpawnPrefab(request))
            .await
    }

    #[tool(description = "List all loaded assets available for spawning")]
    async fn list_loaded_assets(
        &self,
        Parameters(request): Parameters<ListLoadedAssetsRequest>,
    ) -> String {
        self.send_command_and_wait(McpCommand::ListLoadedAssets(request))
            .await
    }

    #[tool(description = "Clear all named entities from the scene")]
    async fn clear_scene(&self, Parameters(request): Parameters<ClearSceneRequest>) -> String {
        self.send_command_and_wait(McpCommand::ClearScene(request))
            .await
    }

    #[tool(
        description = "Set the atmosphere/skybox type: none, sky, cloudy_sky, space, nebula, sunset, hdr"
    )]
    async fn set_atmosphere(
        &self,
        Parameters(request): Parameters<SetAtmosphereRequest>,
    ) -> String {
        self.send_command_and_wait(McpCommand::SetAtmosphere(request))
            .await
    }

    #[tool(
        description = "Load an HDR skybox from file path. Sets atmosphere to 'hdr' automatically"
    )]
    async fn load_hdr(&self, Parameters(request): Parameters<LoadHdrRequest>) -> String {
        self.send_command_and_wait(McpCommand::LoadHdr(request))
            .await
    }

    #[tool(
        description = "Execute multiple operations atomically in a single frame. Supported operations: spawn_entity, despawn_entity, set_position, set_rotation, set_scale, set_material_color, set_emissive, set_parent, query_entity, list_entities, clear_scene, set_atmosphere. Each operation requires 'name' (except list_entities and clear_scene) and operation-specific fields."
    )]
    async fn batch(&self, Parameters(request): Parameters<BatchRequest>) -> String {
        self.send_command_and_wait(McpCommand::Batch(request)).await
    }

    #[tool(
        description = "Execute concise text commands. Much shorter than batch for common operations. Example:\nspawn sun Sphere 0,0,0 scale:2 emissive:5,4,0\nspawn planet Sphere 5,0,0 scale:0.5 parent:sun\nparent child_name parent_name\nclear"
    )]
    async fn run(&self, Parameters(request): Parameters<RunCommandsRequest>) -> String {
        self.send_command_and_wait(McpCommand::RunCommands(request))
            .await
    }

    #[tool(description = "Spawn a light (point, spot, or directional)")]
    async fn spawn_light(&self, Parameters(request): Parameters<SpawnLightRequest>) -> String {
        self.send_command_and_wait(McpCommand::SpawnLight(request))
            .await
    }

    #[tool(description = "Modify an existing light's properties")]
    async fn set_light(&self, Parameters(request): Parameters<SetLightRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetLight(request))
            .await
    }

    #[tool(description = "Add a debug line")]
    async fn add_line(&self, Parameters(request): Parameters<AddLineRequest>) -> String {
        self.send_command_and_wait(McpCommand::AddLine(request))
            .await
    }

    #[tool(description = "Add multiple debug lines")]
    async fn add_lines(&self, Parameters(request): Parameters<AddLinesRequest>) -> String {
        self.send_command_and_wait(McpCommand::AddLines(request))
            .await
    }

    #[tool(description = "Clear all debug lines")]
    async fn clear_lines(&self, Parameters(request): Parameters<ClearLinesRequest>) -> String {
        self.send_command_and_wait(McpCommand::ClearLines(request))
            .await
    }

    #[tool(description = "Configure graphics settings: bloom, SSAO, fog, tonemapping, DOF, etc.")]
    async fn set_graphics(&self, Parameters(request): Parameters<SetGraphicsRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetGraphics(request))
            .await
    }

    #[tool(description = "Spawn a water plane with wave effects")]
    async fn spawn_water(&self, Parameters(request): Parameters<SpawnWaterRequest>) -> String {
        self.send_command_and_wait(McpCommand::SpawnWater(request))
            .await
    }

    #[tool(description = "Spawn a HUD text element")]
    async fn spawn_hud_text(&self, Parameters(request): Parameters<SpawnHudTextRequest>) -> String {
        self.send_command_and_wait(McpCommand::SpawnHudText(request))
            .await
    }

    #[tool(description = "Modify a HUD text element")]
    async fn set_hud_text(&self, Parameters(request): Parameters<SetHudTextRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetHudText(request))
            .await
    }

    #[tool(description = "Set the main camera's position, target, and field of view")]
    async fn set_camera(&self, Parameters(request): Parameters<SetCameraRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetCamera(request))
            .await
    }

    #[tool(
        description = "Add or update a Rhai script on an entity. Scripts run each frame. Modify pos_x/y/z, rot_x/y/z, scale_x/y/z for transforms. Use state[\"key\"] for shared game state. Query entities via entities[\"name\"].x/y/z. Iterate with entity_names array (use starts_with to filter by prefix). Despawn others via despawn_names.push(\"name\"). Check input via pressed_keys.contains(\"W\"). Set do_despawn=true to remove self."
    )]
    async fn set_script(&self, Parameters(request): Parameters<SetScriptRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetScript(request))
            .await
    }

    #[tool(description = "Remove script from an entity")]
    async fn remove_script(&self, Parameters(request): Parameters<RemoveScriptRequest>) -> String {
        self.send_command_and_wait(McpCommand::RemoveScript(request))
            .await
    }

    #[tool(
        description = "Set values in the shared game state that scripts can read/write via state[\"key\"]"
    )]
    async fn set_game_state(&self, Parameters(request): Parameters<SetGameStateRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetGameState(request))
            .await
    }

    #[tool(
        description = "Get values from the shared game state. Pass specific keys or omit to get all."
    )]
    async fn get_game_state(&self, Parameters(request): Parameters<GetGameStateRequest>) -> String {
        self.send_command_and_wait(McpCommand::GetGameState(request))
            .await
    }

    #[tool(description = "Set visibility of an entity (show/hide)")]
    async fn set_visibility(
        &self,
        Parameters(request): Parameters<SetVisibilityRequest>,
    ) -> String {
        self.send_command_and_wait(McpCommand::SetVisibility(request))
            .await
    }

    #[tool(description = "Play an animation on an entity. Clip can be name or index.")]
    async fn play_animation(
        &self,
        Parameters(request): Parameters<PlayAnimationRequest>,
    ) -> String {
        self.send_command_and_wait(McpCommand::PlayAnimation(request))
            .await
    }

    #[tool(description = "Stop animation playback on an entity")]
    async fn stop_animation(
        &self,
        Parameters(request): Parameters<StopAnimationRequest>,
    ) -> String {
        self.send_command_and_wait(McpCommand::StopAnimation(request))
            .await
    }

    #[tool(description = "List available animation clips on an entity")]
    async fn list_animations(
        &self,
        Parameters(request): Parameters<ListAnimationsRequest>,
    ) -> String {
        self.send_command_and_wait(McpCommand::ListAnimations(request))
            .await
    }

    #[tool(
        description = "Spawn a particle emitter with a preset (fire, smoke, sparks, firework_explosion, firework_willow, firework_chrysanthemum)"
    )]
    async fn spawn_particles(
        &self,
        Parameters(request): Parameters<SpawnParticlesRequest>,
    ) -> String {
        self.send_command_and_wait(McpCommand::SpawnParticles(request))
            .await
    }

    #[tool(description = "Modify particle emitter settings")]
    async fn set_particles(&self, Parameters(request): Parameters<SetParticlesRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetParticles(request))
            .await
    }

    #[tool(description = "Add a rigid body for physics simulation (dynamic, kinematic, or static)")]
    async fn add_rigid_body(&self, Parameters(request): Parameters<AddRigidBodyRequest>) -> String {
        self.send_command_and_wait(McpCommand::AddRigidBody(request))
            .await
    }

    #[tool(
        description = "Add a collider shape for collision detection (ball, cuboid, capsule, cylinder)"
    )]
    async fn add_collider(&self, Parameters(request): Parameters<AddColliderRequest>) -> String {
        self.send_command_and_wait(McpCommand::AddCollider(request))
            .await
    }

    #[tool(description = "Apply an instant impulse to a rigid body")]
    async fn apply_impulse(&self, Parameters(request): Parameters<ApplyImpulseRequest>) -> String {
        self.send_command_and_wait(McpCommand::ApplyImpulse(request))
            .await
    }

    #[tool(description = "Apply a continuous force to a rigid body")]
    async fn apply_force(&self, Parameters(request): Parameters<ApplyForceRequest>) -> String {
        self.send_command_and_wait(McpCommand::ApplyForce(request))
            .await
    }

    #[tool(description = "Set linear and/or angular velocity of a rigid body")]
    async fn set_velocity(&self, Parameters(request): Parameters<SetVelocityRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetVelocity(request))
            .await
    }

    #[tool(description = "Spawn 3D text in world space")]
    async fn spawn_3d_text(&self, Parameters(request): Parameters<Spawn3dTextRequest>) -> String {
        self.send_command_and_wait(McpCommand::Spawn3dText(request))
            .await
    }

    #[tool(description = "Spawn a decal (projected texture onto surfaces)")]
    async fn spawn_decal(&self, Parameters(request): Parameters<SpawnDecalRequest>) -> String {
        self.send_command_and_wait(McpCommand::SpawnDecal(request))
            .await
    }

    #[tool(description = "Set material properties (roughness, metallic, colors, alpha)")]
    async fn set_material(&self, Parameters(request): Parameters<SetMaterialRequest>) -> String {
        self.send_command_and_wait(McpCommand::SetMaterial(request))
            .await
    }

    #[tool(description = "Set whether an entity casts shadows")]
    async fn set_casts_shadow(
        &self,
        Parameters(request): Parameters<SetCastsShadowRequest>,
    ) -> String {
        self.send_command_and_wait(McpCommand::SetCastsShadow(request))
            .await
    }

    #[tool(description = "Get current input state (pressed keys, mouse position)")]
    async fn get_input(&self, Parameters(request): Parameters<GetInputRequest>) -> String {
        self.send_command_and_wait(McpCommand::GetInput(request))
            .await
    }

    #[tool(description = "Get time information (delta time, elapsed time)")]
    async fn get_time(&self, Parameters(request): Parameters<GetTimeRequest>) -> String {
        self.send_command_and_wait(McpCommand::GetTime(request))
            .await
    }
}

#[tool_handler]
impl ServerHandler for McpServer {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            instructions: Some(
                "Nightshade Engine MCP Server - Control entities and scene elements".into(),
            ),
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            ..Default::default()
        }
    }
}

pub fn start_mcp_server(command_queue: CommandQueue) -> tokio::sync::oneshot::Sender<()> {
    let (shutdown_sender, shutdown_receiver) = tokio::sync::oneshot::channel();
    std::thread::spawn(move || {
        let runtime = tokio::runtime::Runtime::new().unwrap();
        runtime.block_on(async {
            let command_queue_clone = command_queue.clone();

            let service = StreamableHttpService::new(
                move || Ok(McpServer::new(command_queue_clone.clone())),
                LocalSessionManager::default().into(),
                Default::default(),
            );

            let bind_address = format!("{}:{}", MCP_DEFAULT_HOST, MCP_DEFAULT_PORT);
            let router = axum::Router::new().nest_service("/mcp", service);
            let tcp_listener = match tokio::net::TcpListener::bind(&bind_address).await {
                Ok(listener) => listener,
                Err(error) => {
                    tracing::error!("Failed to bind MCP server to {}: {}", bind_address, error);
                    return;
                }
            };

            tracing::info!("MCP server listening on http://{}/mcp", bind_address);
            tracing::info!(
                "Add to Claude Code: claude mcp add --transport http nightshade http://{}/mcp",
                bind_address
            );

            axum::serve(tcp_listener, router)
                .with_graceful_shutdown(async {
                    tokio::select! {
                        _ = shutdown_receiver => {}
                        _ = tokio::signal::ctrl_c() => {}
                    }
                })
                .await
                .ok();
        });
    });
    shutdown_sender
}

pub fn create_mcp_queues() -> CommandQueue {
    Arc::new(Mutex::new(Vec::new()))
}
