use serde::{Deserialize, Serialize};

use crate::component::ComponentValue;
use crate::entity::EntityId;
use crate::math::Transform;
use crate::physics::{ColliderConfig, ColliderShape, PhysicsBodyType};
use crate::scene::{AnimationClipData, AudioData, CameraFollow, CameraSettings, GaussianSplatData, GltfAnimation, HudData, MaterialData, NavigationData, OceanData, ParticleData, PathData, PlayerController, SavedEntity, TerrainData, TextureSlots, TriggerData};

/// Forme géométrique primitive.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PrimitiveShape {
    Cuboid,
    Sphere,
    Plane,
    Capsule,
    Cylinder,
    Cone,
}

/// Commande d'édition atomique.
///
/// Unité de base du système undo/redo (Phase 2).
/// Chaque mutation de la scène passe par un `EditorCommand`.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EditorCommand {
    // --- Entités ---
    /// Crée une nouvelle entité vide.
    SpawnEntity,

    /// Supprime une entité (et ses enfants).
    DespawnEntity(EntityId),

    /// Duplique une entité (copie des composants clés, offset +0.5 X).
    /// L'inverse (undo) est généré automatiquement après exécution.
    DuplicateEntity { entity: EntityId },

    /// Renomme une entité.
    RenameEntity { entity: EntityId, name: String },

    /// Déplace une entité dans la hiérarchie.
    ReparentEntity {
        entity: EntityId,
        new_parent: Option<EntityId>,
    },

    // --- Composants ---
    /// Modifie la valeur d'un champ de composant.
    SetComponent {
        entity: EntityId,
        component: String,
        value: ComponentValue,
    },

    /// Ajoute un composant à une entité.
    AddComponent {
        entity: EntityId,
        component: String,
        value: ComponentValue,
    },

    /// Supprime un composant d'une entité.
    RemoveComponent {
        entity: EntityId,
        component: String,
    },

    // --- Transformation (raccourci fréquent) ---
    /// Modifie le Transform complet d'une entité.
    SetTransform { entity: EntityId, transform: Transform },

    // --- Entités avec mesh ---
    /// Crée une entité portant un mesh GLB/GLTF.
    /// `mesh_path` : chemin relatif à `assets/`, ex `"models/player.glb#Scene0"`
    /// `asset_id`  : ID stable dans l'AssetIndex (pour retrouver le fichier après déplacement).
    SpawnMeshEntity { name: String, mesh_path: String, asset_id: Option<String> },

    // --- Entités spécialisées ---
    /// Crée une entité caméra 3D.
    SpawnCamera { name: String },
    /// Crée une entité lumière directionnelle.
    SpawnDirectionalLight { name: String },
    /// Crée une entité lumière ponctuelle.
    SpawnPointLight { name: String },
    /// Crée une entité lumière spot.
    SpawnSpotLight { name: String },
    /// Crée une entité source audio.
    SpawnAudioSource { name: String },
    /// Crée une entité océan.
    SpawnOcean { name: String },
    /// Crée un élément HUD (interface 2D superposée).
    /// Si `data` est fourni, ses valeurs sont appliquées immédiatement après le spawn
    /// (équivalent à `SpawnHud { name }` suivi de `SetHudData { entity, data }`).
    SpawnHud {
        name: String,
        #[serde(default)]
        data: Option<HudData>,
    },
    /// Crée une zone de déclenchement invisible.
    SpawnTrigger { name: String },
    /// Crée une entité chemin (waypoints suivis par l'entité dans le jeu exporté).
    SpawnPath { name: String },
    /// Crée un émetteur de particules CPU.
    SpawnParticle { name: String },
    /// Crée une entité terrain heightmap.
    SpawnTerrain { name: String },
    /// Crée une entité nuage de points Gaussian Splat.
    SpawnGaussianSplat { name: String, splat_path: Option<String> },
    /// Crée une entité primitive mesh (Cube, Sphère, Plan, …).
    SpawnPrimitive { shape: PrimitiveShape },

    // --- Caméra ---
    /// Modifie les paramètres d'une entité caméra.
    SetCameraSettings { entity: EntityId, settings: CameraSettings },

    /// Applique un skybox sur la caméra viewport (item feedback #53).
    ///
    /// `texture_path` : chemin relatif du cubemap (idéalement KTX2, format natif
    /// Bevy ; les PNG équirectangulaires ne sont pas supportés en l'état et
    /// donneront un rendu incorrect). Si `None`, retire le skybox existant.
    /// `brightness` : luminosité en cd/m² (défaut 1000.0).
    SetSkybox {
        #[serde(default)]
        texture_path: Option<String>,
        #[serde(default)]
        brightness: Option<f32>,
    },

    // --- Audio ---
    /// Modifie les paramètres d'une entité source audio.
    SetAudioData { entity: EntityId, data: AudioData },

    // --- Visibilité ---
    /// Masque ou affiche une entité (`Visibility::Hidden` vs `Visibility::Inherited`).
    SetVisibility { entity: EntityId, hidden: bool },

    // --- Matériau ---
    /// Remplace les paramètres PBR du matériau d'une entité primitive.
    SetMaterialData { entity: EntityId, data: MaterialData },
    /// Remplace les slots texture du matériau d'une entité primitive.
    SetTextureData { entity: EntityId, slots: TextureSlots },

    // --- Animation ---
    /// Remplace tous les clips d'animation d'une entité (undo = remettre les anciens clips).
    SetAnimationData { entity: EntityId, clips: Vec<AnimationClipData> },
    /// Remplace les bindings d'animations GLTF/GLB d'une entité.
    SetGltfAnimations { entity: EntityId, clips: Vec<GltfAnimation> },

    // --- Collider / Physique ---
    /// Ajoute un collider à une entité.
    AddCollider { entity: EntityId, config: ColliderConfig },
    /// Supprime le collider d'une entité.
    RemoveCollider { entity: EntityId },
    /// Modifie la forme du collider.
    SetColliderShape { entity: EntityId, shape: ColliderShape },
    /// Modifie les layers de collision.
    SetColliderLayer { entity: EntityId, memberships: u32, filters: u32 },
    /// Modifie le mode du corps rigide.
    SetPhysicsBody { entity: EntityId, body_type: PhysicsBodyType },
    /// Active/désactive le mode sensor.
    SetSensor { entity: EntityId, is_sensor: bool },
    /// Associe un matériau physique (par nom).
    SetPhysicsMaterial { entity: EntityId, material_name: Option<String> },

    // --- Océan ---
    /// Modifie les paramètres de l'entité océan.
    SetOceanData { entity: EntityId, data: OceanData },

    // --- HUD ---
    /// Modifie les paramètres d'un élément HUD.
    SetHudData { entity: EntityId, data: HudData },

    // --- Trigger Zone ---
    /// Modifie les paramètres d'une zone de déclenchement.
    SetTriggerData { entity: EntityId, data: TriggerData },

    // --- Chemin (Path) ---
    /// Modifie les données d'un chemin waypoints.
    SetPathData { entity: EntityId, data: PathData },

    // --- Particules ---
    /// Modifie les données d'un émetteur de particules.
    SetParticleData { entity: EntityId, data: ParticleData },

    // --- Terrain ---
    /// Modifie les données du terrain (déclenche une régénération du mesh preview).
    SetTerrainData { entity: EntityId, data: TerrainData },

    // --- Gaussian Splat ---
    /// Modifie les données du nuage de points Gaussian Splat.
    SetGaussianSplatData { entity: EntityId, data: GaussianSplatData },

    // --- Navigation ---
    /// Modifie les paramètres de navigation d'un agent (`None` = supprime).
    SetNavigationData { entity: EntityId, data: Option<NavigationData> },

    // --- Gameplay ---
    /// Modifie le contrôleur de personnage (`None` = supprime le composant).
    SetPlayerController { entity: EntityId, data: Option<PlayerController> },
    /// Modifie le suivi de caméra (`None` = supprime le composant).
    SetCameraFollow { entity: EntityId, data: Option<CameraFollow> },

    // --- Tags ---
    /// Ajoute une étiquette libre à une entité (idempotent — pas de doublon).
    /// Les tags servent à retrouver une entité entre sessions, même si son
    /// `EntityId` Bevy change après un reload de scène.
    AddTag { entity: EntityId, tag: String },
    /// Supprime une étiquette d'une entité (no-op si absente).
    RemoveTag { entity: EntityId, tag: String },

    // --- Préfabs ---
    /// Sauvegarde l'entité comme préfab dans `assets/prefabs/<nom>.prefab.ron`.
    /// Non undo-able (opération fichier). Nécessite un projet ouvert.
    SaveAsPrefab { entity: EntityId },

    /// Instancie un préfab : crée une entité à partir d'un `SavedEntity` préchargé
    /// et lui attache un marqueur `PrefabInstance { source_path }`.
    InstantiatePrefab { source_path: String, entity: SavedEntity },

    /// Supprime le marqueur `PrefabInstance` de l'entité (devient une entité ordinaire).
    UnpackPrefab { entity: EntityId },

    // --- Scène ---
    /// Charge une scène depuis le chemin donné.
    LoadScene(String),

    /// Sauvegarde la scène courante vers le chemin donné.
    SaveScene(String),

    /// Crée une scène vide.
    NewScene,
}

impl EditorCommand {
    /// Indique si cette commande modifie la scène (et doit donc être undo-able).
    pub fn is_mutating(&self) -> bool {
        !matches!(self,
            EditorCommand::LoadScene(_)
            | EditorCommand::SaveScene(_)
            | EditorCommand::SaveAsPrefab { .. }
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn spawn_entity_is_mutating() {
        assert!(EditorCommand::SpawnEntity.is_mutating());
    }

    #[test]
    fn save_scene_not_mutating() {
        assert!(!EditorCommand::SaveScene("level1.scn.ron".into()).is_mutating());
    }
}
