//! The editor's wire format: page to worker messages, worker to page state
//! mirrors, and the external agent request and response types.

#[cfg(feature = "editor-protocol-agent")]
mod agent;
#[cfg(feature = "editor-protocol-agent")]
pub use agent::*;

use crate::ecs::audio::components::AudioSource;
use crate::ecs::camera::components::Camera;
use crate::ecs::decal::components::Decal;
use crate::ecs::gizmos::GizmoMode;
use crate::ecs::light::components::Light;
use crate::ecs::material::components::Material;
use crate::ecs::particles::components::ParticleEmitter;
use crate::ecs::scene::{SceneChunkConfig, SceneLayerConfig};
use crate::ecs::text::components::TextProperties;
pub use crate::prelude::{
    Atmosphere, CharacterControllerComponent, ColliderComponent, NavMeshAgent, RigidBodyComponent,
};
use serde::{Deserialize, Serialize};

pub use crate::ecs::audio::components::AudioSource as AudioSourceComponent;
pub use crate::ecs::camera::components::{
    Camera as CameraComponent, OrthographicCamera, PerspectiveCamera, Projection,
};
pub use crate::ecs::decal::components::Decal as DecalComponent;
pub use crate::ecs::gizmos::GizmoMode as GizmoModeKind;
pub use crate::ecs::light::components::{AreaLightShape, Light as LightComponent, LightType};
pub use crate::ecs::material::components::{AlphaMode, Material as MaterialComponent};
pub use crate::ecs::particles::components::ParticleEmitter as ParticleEmitterComponent;
pub use crate::ecs::physics::components::ColliderShape;
pub use crate::ecs::physics::types::RigidBodyType;
pub use crate::ecs::primitives::{CameraCullingMask, CullingMask, RenderLayer};
pub use crate::ecs::text::components::TextProperties as TextPropertiesComponent;
pub use crate::prelude::{Atmosphere as AtmosphereKind, Vec2, Vec3, Vec4};

/// Envelope field carrying the serialized message in every `postMessage`.
pub const MESSAGE_KEY: &str = "message";
/// Envelope field carrying the transferred `OffscreenCanvas` (on `Init` only).
pub const CANVAS_KEY: &str = "canvas";
/// Envelope field carrying transferred binary bytes (file drops, map saves).
pub const BYTES_KEY: &str = "bytes";
/// Envelope field carrying the transferred glTF bytes of a multi-file bundle.
pub const GLTF_KEY: &str = "gltf";
/// Envelope field carrying a `{ name: Uint8Array }` object of bundle resources.
pub const RESOURCES_KEY: &str = "resources";

/// Lifecycle phase of a forwarded touch contact.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum TouchPhase {
    Started,
    Moved,
    Ended,
    Cancelled,
}

/// A parametric primitive shape used for spawning and block placement.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
    feature = "editor-protocol-agent",
    derive(enum2schema::Schema),
    schema(string_enum)
)]
pub enum ShapeKind {
    Cube,
    Plane,
    Cylinder,
    Cone,
    Sphere,
    Torus,
}

/// A light kind for the Add menu.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
    feature = "editor-protocol-agent",
    derive(enum2schema::Schema),
    schema(string_enum)
)]
pub enum LightKind {
    Point,
    Spot,
    Directional,
}

/// A procedural greybox generator.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
    feature = "editor-protocol-agent",
    derive(enum2schema::Schema),
    schema(string_enum)
)]
pub enum GeneratorKind {
    Building,
    Room,
    Tower,
    Stairs,
    Columns,
    Perimeter,
    CityBlock,
    Courtyard,
    Wfc,
    WfcCity,
}

/// Asset browser category for Polyhaven.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(
    feature = "editor-protocol-agent",
    derive(enum2schema::Schema),
    schema(string_enum)
)]
pub enum PolyhavenCategory {
    Hdris,
    Models,
}

/// Which optional component a message adds, removes, or replaces.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum ComponentKind {
    ParticleEmitter,
    Decal,
    AudioSource,
    RigidBody,
    Collider,
    CharacterController,
    NavmeshAgent,
    Camera,
    Text,
    AnimationPlayer,
    Visibility,
    CastsShadow,
    Light,
    RenderLayer,
    CullingMask,
    CameraCullingMask,
    IgnoreParentScale,
    AudioListener,
    CollisionListener,
    PhysicsInterpolation,
    MorphWeights,
    MaterialVariants,
    PanOrbitCamera,
    ThirdPersonCamera,
    CameraEnvironment,
    CameraPostProcess,
    ConstrainedAspect,
    ViewportUpdateMode,
}

/// A whole-component value applied to the selected entity. These are the
/// engine's own component types; the worker applies them with the same
/// setters the retained inspector used.
#[derive(Clone, Serialize, Deserialize)]
pub enum ComponentPayload {
    Light(Light),
    Visibility(bool),
    CastsShadow(bool),
    Camera(Camera),
    RigidBody(RigidBodyComponent),
    Collider(ColliderComponent),
    CharacterController(CharacterControllerComponent),
    NavmeshAgent(NavMeshAgent),
    ParticleEmitter(Box<ParticleEmitter>),
    Decal(Decal),
    AudioSource(AudioSource),
    RenderLayer(RenderLayer),
    CullingMask(CullingMask),
    CameraCullingMask(CameraCullingMask),
    IgnoreParentScale(bool),
    Text {
        content: String,
        properties: TextProperties,
    },
}

impl ComponentPayload {
    pub fn kind(&self) -> ComponentKind {
        match self {
            Self::Light(_) => ComponentKind::Light,
            Self::Visibility(_) => ComponentKind::Visibility,
            Self::CastsShadow(_) => ComponentKind::CastsShadow,
            Self::Camera(_) => ComponentKind::Camera,
            Self::RigidBody(_) => ComponentKind::RigidBody,
            Self::Collider(_) => ComponentKind::Collider,
            Self::CharacterController(_) => ComponentKind::CharacterController,
            Self::NavmeshAgent(_) => ComponentKind::NavmeshAgent,
            Self::ParticleEmitter(_) => ComponentKind::ParticleEmitter,
            Self::Decal(_) => ComponentKind::Decal,
            Self::AudioSource(_) => ComponentKind::AudioSource,
            Self::RenderLayer(_) => ComponentKind::RenderLayer,
            Self::CullingMask(_) => ComponentKind::CullingMask,
            Self::CameraCullingMask(_) => ComponentKind::CameraCullingMask,
            Self::IgnoreParentScale(_) => ComponentKind::IgnoreParentScale,
            Self::Text { .. } => ComponentKind::Text,
        }
    }
}

/// Animation playback control for the selected entity's player.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum AnimationCommand {
    Play { index: u32 },
    PlayAll,
    Pause,
    Resume,
    Stop,
    Seek { time: f32 },
    SetSpeed { speed: f32 },
    SetLooping { looping: bool },
}

/// Procedural generation settings, mirrored from the worker's resource so the
/// panel edits round-trip.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "editor-protocol-agent", derive(enum2schema::Schema))]
pub struct GenerationSettings {
    pub footprint_x: f32,
    pub footprint_z: f32,
    pub stories: u32,
    pub story_height: f32,
    pub wall_thickness: f32,
    pub floor_thickness: f32,
    pub window_spacing: f32,
    pub seed: u32,
    pub variation: f32,
}

/// Every world-level rendering setting the inspector's world sections edit,
/// synced both ways as one block.
#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct RenderSettingsState {
    pub ambient_light: [f32; 4],
    pub bloom_enabled: bool,
    pub bloom_intensity: f32,
    pub bloom_threshold: f32,
    pub bloom_knee: f32,
    pub bloom_filter_radius: f32,
    pub ssao_enabled: bool,
    pub ssao_radius: f32,
    pub ssao_intensity: f32,
    pub ssao_bias: f32,
    pub ssao_sample_count: u32,
    pub ssao_visualization: bool,
    pub ssgi_enabled: bool,
    pub ssgi_radius: f32,
    pub ssgi_intensity: f32,
    pub ssgi_max_steps: u32,
    pub ssr_enabled: bool,
    pub ssr_max_steps: u32,
    pub ssr_thickness: f32,
    pub ssr_max_distance: f32,
    pub ssr_stride: f32,
    pub ssr_fade_start: f32,
    pub ssr_fade_end: f32,
    pub ssr_intensity: f32,
    pub fog_enabled: bool,
    pub fog_color: [f32; 3],
    pub fog_start: f32,
    pub fog_end: f32,
    pub dof_enabled: bool,
    pub dof_focus_distance: f32,
    pub dof_focus_range: f32,
    pub dof_max_blur_radius: f32,
    pub dof_bokeh_threshold: f32,
    pub dof_bokeh_intensity: f32,
    pub dof_quality: u32,
    pub fxaa_enabled: bool,
    pub render_scale: f32,
    pub ibl_blend_factor: f32,
    pub pbr_debug_index: u32,
    pub unlit_mode: bool,
    pub selection_outline_enabled: bool,
    pub selection_outline_color: [f32; 4],
    pub exposure: f32,
    pub saturation: f32,
    pub contrast: f32,
    pub brightness: f32,
    pub gamma: f32,
    pub show_grid: bool,
    pub show_sky: bool,
    pub show_normals: bool,
    pub navmesh_debug: bool,
    pub atmosphere: Atmosphere,
}

/// Editor-side state the page mirrors for toolbar and panel highlights.
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct EditorFlags {
    pub gizmo_mode: GizmoMode,
    pub snap_enabled: bool,
    pub snap_translation_step: f32,
    pub snap_rotation_step_degrees: f32,
    pub snap_scale_step: f32,
    pub greybox_enabled: bool,
    pub greybox_show_final: bool,
    pub placement_active: bool,
    pub placement_shape: ShapeKind,
    pub placement_size: [f32; 3],
    pub placement_orient: bool,
    pub placement_status: String,
    pub push_pull_active: bool,
    pub play_active: bool,
    pub fly_mode: bool,
    pub viewer_mode: bool,
    pub frame_on_load: bool,
    pub day_night_hour: f32,
    pub day_night_auto: bool,
    pub skeleton_view: bool,
    pub rotation_speed: f32,
    pub active_palette: usize,
    pub edit_mode_target: Option<u32>,
    pub generation: GenerationSettings,
}

/// One row of the flattened scene tree, in depth-first order.
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct TreeRow {
    pub id: u32,
    pub name: String,
    pub depth: u32,
    pub has_children: bool,
    pub camera: bool,
    pub light: bool,
    pub mesh: bool,
}

/// A greybox material palette swatch.
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct PaletteEntry {
    pub label: String,
    pub color: [f32; 3],
}

/// Prefab link shown in the inspector for instanced prefab roots.
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct PrefabLink {
    pub name: String,
    pub source_path: Option<String>,
}

/// Animation state of the selected entity, for the inspector's player section.
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct AnimationInfo {
    pub clips: Vec<String>,
    pub current: Option<u32>,
    pub playing: bool,
    pub time: f32,
    pub duration: f32,
    pub speed: f32,
    pub looping: bool,
}

/// The selected entity's full editable detail. Rotation is Euler degrees.
#[derive(Clone, Serialize, Deserialize)]
pub struct SelectedEntity {
    pub id: u32,
    /// Bumped whenever a committed edit or undo changed this entity, so the
    /// page re-seeds its local editor copies exactly then.
    pub revision: u32,
    pub name: String,
    pub translation: [f32; 3],
    pub rotation: [f32; 3],
    pub scale: [f32; 3],
    pub mesh: Option<String>,
    pub material_name: Option<String>,
    pub tags: Vec<String>,
    pub layer: Option<u32>,
    pub chunk: Option<u32>,
    pub prefab: Option<PrefabLink>,
    pub animation: Option<AnimationInfo>,
    pub selected_count: u32,
    pub visibility: Option<bool>,
    pub casts_shadow: bool,
    pub light: Option<Light>,
    pub camera: Option<Camera>,
    pub is_active_camera: bool,
    pub rigid_body: Option<RigidBodyComponent>,
    pub collider: Option<ColliderComponent>,
    pub character_controller: Option<CharacterControllerComponent>,
    pub navmesh_agent: Option<NavMeshAgent>,
    pub particle_emitter: Option<Box<ParticleEmitter>>,
    pub decal: Option<Decal>,
    pub audio_source: Option<AudioSource>,
    pub render_layer: Option<RenderLayer>,
    pub culling_mask: Option<CullingMask>,
    pub camera_culling_mask: Option<CameraCullingMask>,
    pub ignore_parent_scale: bool,
    pub text: Option<(String, TextProperties)>,
    pub present: Vec<ComponentKind>,
    pub addable: Vec<ComponentKind>,
}

/// A material registry entry for the materials panel.
#[derive(Clone, Serialize, Deserialize)]
pub struct MaterialEntry {
    pub name: String,
    pub material: Material,
}

/// A Khronos sample-asset entry for the browser grid.
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct KhronosEntry {
    pub label: String,
    pub thumbnail: Option<String>,
}

/// A Polyhaven entry for the browser grid.
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct PolyhavenEntry {
    pub slug: String,
    pub name: String,
    pub thumbnail: String,
}

/// Scene layer and chunk authoring data.
#[derive(Clone, PartialEq, Serialize, Deserialize)]
pub struct SceneStructure {
    pub layers: Vec<SceneLayerConfig>,
    pub chunks: Vec<SceneChunkConfig>,
}

/// Editor commands that need no payload beyond what they carry. Entity
/// references are raw entity ids resolved by the worker.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "editor-protocol-agent", derive(enum2schema::Schema))]
pub enum EditorAction {
    ToggleGroundGrid,
    ToggleSky,
    ToggleLitUnlit,
    ToggleViewerMode,
    SetViewerMode(bool),
    FrameScene,
    ToggleFrameOnLoad,
    ClearScene,
    SnapSelectionToFloor,
    BakeNavmesh,
    ToggleNavmeshDebug,
    SetAtmosphere(Atmosphere),
    SetGizmoMode(GizmoMode),
    RandomizeBoth,
    SelectEntity(u32),
    ToggleSelectEntity(u32),
    ClearSelection,
    AddEntityChild(u32),
    ViewCamera(u32),
    ToggleSnap,
    ToggleSkeletonView,
    ToggleCameraGizmos,
    ToggleShowNormals,
    ToggleDayNightCycle,
    SetDayNightHour(f32),
    LoadKhronosByIndex(usize),
    LoadPolyhavenByIndex(PolyhavenCategory, usize),
    SetPolyhavenResolution(u32),
    RefreshBrowsers,
    DeepLink(String),
    SpawnLines,
    SpawnMeshes,
    Spawn3DText,
    SpawnTextLattice,
    ClothSponza,
    NewProject,
    SaveProject,
    ExportGlb,
    SaveSelectedAsPrefab,
    RefreshSelectedPrefab,
    BreakSelectedPrefabLink,
    SpawnPrefab(String),
    Undo,
    Redo,
    DeleteEntity(u32),
    DeleteSelectedEntity,
    DuplicateEntity(u32),
    DuplicateSelectedEntity,
    AddShape(ShapeKind),
    AddInstancedShape(ShapeKind),
    ConvertSimilarToInstanced,
    AddEmpty,
    AddCamera,
    AddPlayerSpawn,
    AddSavePoint,
    AddPatrolPoint,
    AddTriggerVolume,
    StampDecalAtCursor,
    AddLight(LightKind),
    AddTagToSelected(String),
    RemoveTagFromSelected(String),
    ToggleGreyboxMode,
    ApplyGreyboxToSelection,
    SnapSelectionToGrid,
    MarkSelectionAsBrush,
    ToggleShowFinal,
    TogglePlay,
    TogglePushPull,
    SetPlacementActive(bool),
    SetPlacementShape(ShapeKind),
    SetPlacementSize([f32; 3]),
    SetOrientToNormal(bool),
    RunGenerator(GeneratorKind),
    SetGenerationSettings(GenerationSettings),
    SetPaletteIndex(usize),
    SetSnapEnabled(bool),
    SetSnapSteps {
        translation: f32,
        rotation_degrees: f32,
        scale: f32,
    },
    SetRotationSpeed(f32),
    SetEntityLayer {
        id: u32,
        layer: Option<u32>,
    },
    SetEntityChunk {
        id: u32,
        chunk: Option<u32>,
    },
    AddSceneLayer,
    RemoveSceneLayer(u32),
    UpdateSceneLayer(SceneLayerConfig),
    AddSceneChunk,
    RemoveSceneChunk(u32),
    UpdateSceneChunk(SceneChunkConfig),
}

/// A camera entity available for multi-view tiling.
#[derive(Clone, Serialize, Deserialize)]
pub struct CameraEntry {
    pub id: u32,
    pub name: String,
}

/// One camera's tile in the multi-view layout, in physical canvas pixels with
/// the origin at the canvas top-left.
#[derive(Clone, Copy, Serialize, Deserialize)]
pub struct ViewportTile {
    pub camera: u32,
    pub x: f32,
    pub y: f32,
    pub width: f32,
    pub height: f32,
}

/// Page to worker. Pixel quantities are physical surface pixels (CSS pixels
/// times the device pixel ratio), origin at the canvas top-left.
#[derive(Clone, Serialize, Deserialize)]
pub enum ClientMessage {
    /// Sent once with the `OffscreenCanvas` in the transfer list.
    Init {
        width: f32,
        height: f32,
    },
    Resize {
        width: f32,
        height: f32,
    },
    /// Replaces the camera tile layout. An empty list returns to the single
    /// full-canvas view.
    SetViewportTiles(Vec<ViewportTile>),
    PointerMove {
        x: f32,
        y: f32,
    },
    /// Raw pointer motion (movementX/Y), forwarded while pointer lock is held
    /// so fly and play modes get first-person look input.
    PointerMotion {
        dx: f32,
        dy: f32,
    },
    /// A mouse button changed. `button` is 0 left, 1 middle, 2 right.
    PointerButton {
        button: u8,
        pressed: bool,
    },
    Wheel {
        delta: f32,
    },
    Touch {
        id: u64,
        phase: TouchPhase,
        x: f32,
        y: f32,
    },
    /// A keyboard event forwarded while the canvas owns the keyboard. `code`
    /// is the DOM `KeyboardEvent.code` string.
    Key {
        code: String,
        pressed: bool,
        text: Option<String>,
    },
    Action(Box<EditorAction>),
    SetTransform {
        id: u32,
        translation: [f32; 3],
        rotation: [f32; 3],
        scale: [f32; 3],
        committed: bool,
    },
    SetEntityName {
        id: u32,
        name: String,
    },
    SetComponent {
        id: u32,
        payload: Box<ComponentPayload>,
        committed: bool,
    },
    AddComponent {
        id: u32,
        kind: ComponentKind,
    },
    RemoveComponent {
        id: u32,
        kind: ComponentKind,
    },
    Animation {
        id: u32,
        command: AnimationCommand,
    },
    UpdateMaterial {
        name: String,
        material: Box<Material>,
        committed: bool,
    },
    SetRenderSettings(Box<RenderSettingsState>),
    /// A dropped or picked file's bytes are in the `bytes` envelope field.
    /// Routed by extension: .glb/.gltf import, .hdr skybox, .nsmap/.json map,
    /// .nsprefab prefab.
    DropAsset {
        name: String,
    },
    /// A multi-file glTF: `gltf` holds the document bytes, `resources` a
    /// `{ name: Uint8Array }` object of buffers and images.
    LoadGltfBundle {
        name: String,
    },
    /// Replace the selected greybox brush's art with this glTF (bytes envelope).
    UpgradeBrush {
        name: String,
    },
    /// External agent traffic (Claude Code via the MCP bridge), forwarded over
    /// the page's WebSocket relay onto this same postMessage path.
    #[cfg(feature = "editor-protocol-agent")]
    Agent(Box<AgentRequest>),
}

/// Worker to page.
#[derive(Clone, Serialize, Deserialize)]
pub enum WorkerMessage {
    Ready {
        adapter: String,
    },
    Stats {
        fps: f32,
        entity_count: u32,
    },
    /// The active camera's world-space basis, for the page's nav gizmo.
    Camera {
        right: [f32; 3],
        up: [f32; 3],
        forward: [f32; 3],
    },
    Scene {
        rows: Vec<TreeRow>,
    },
    Selected {
        detail: Option<Box<SelectedEntity>>,
    },
    /// Every camera entity in the world, independent of scene tree filtering,
    /// so multi-view tiling sees cameras inside collapsed prefab instances.
    Cameras {
        entries: Vec<CameraEntry>,
    },
    /// The selected entity's animation playhead, streamed while it changes.
    Animation {
        time: f32,
        duration: f32,
        playing: bool,
        clip: Option<u32>,
    },
    RenderSettings(Box<RenderSettingsState>),
    Flags(Box<EditorFlags>),
    Materials {
        entries: Vec<MaterialEntry>,
    },
    Tags {
        entries: Vec<(String, u32)>,
    },
    Structure(SceneStructure),
    Palette {
        entries: Vec<PaletteEntry>,
    },
    KhronosList {
        entries: Vec<KhronosEntry>,
    },
    PolyhavenList {
        category: PolyhavenCategory,
        entries: Vec<PolyhavenEntry>,
    },
    Loading {
        active: bool,
        label: String,
    },
    /// Project status for the title bar.
    Project {
        name: String,
        modified: bool,
        model: Option<String>,
        mode: String,
    },
    /// Serialized file bytes for the page to save (bytes envelope field).
    SaveFile {
        name: String,
    },
    /// Ask the page to acquire or release pointer lock on the canvas.
    PointerLock {
        locked: bool,
    },
    /// Scroll the tree to this entity after a viewport pick.
    FocusEntity {
        id: u32,
    },
    UndoState {
        can_undo: bool,
        can_redo: bool,
    },
    Toast {
        message: String,
        error: bool,
    },
    /// External agent responses and delta batches, relayed back to the MCP
    /// bridge.
    #[cfg(feature = "editor-protocol-agent")]
    Agent(Box<AgentResponse>),
}
