//! The [`Scene`]: entities, their components, and a transform hierarchy.

use std::collections::HashMap;

use hecs::{Component, Entity, World};

use super::SceneError;
use crate::math::Transform;
use crate::scene::node::Node;

/// What happens to an entity's children when it is despawned.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DespawnPolicy {
    /// Despawn the entity together with its entire subtree.
    Recursive,
    /// Despawn only the entity; reparent each child to the entity's parent,
    /// promoting them to roots if the entity was itself a root.
    DetachChildren,
}

/// A scene graph: a [`hecs`] world plus a parent/child transform hierarchy.
///
/// Entities are [`hecs::Entity`] handles. Every entity created through the
/// scene carries a [`Node`] and a (local) [`Transform`]; arbitrary additional
/// components can be attached via [`world_mut`](Self::world_mut) for the
/// systems added in later stages (meshes, rigid bodies, …).
///
/// The hierarchy is owned by the scene rather than stored as components, which
/// keeps child ordering deterministic (important for serialization and the
/// editor) and lets reparenting avoid archetype churn. Local transforms are the
/// authored values; [`world_transform`](Self::world_transform) and
/// [`world_transforms`](Self::world_transforms) resolve them against the
/// hierarchy as `parent_world * local`.
#[derive(Default)]
pub struct Scene {
    world: World,
    /// Top-level entities, in insertion order.
    roots: Vec<Entity>,
    /// Child lists keyed by parent, each in insertion order. Entities with no
    /// children may be absent.
    children: HashMap<Entity, Vec<Entity>>,
    /// Upward links. Roots are absent from this map.
    parents: HashMap<Entity, Entity>,
}

impl Scene {
    /// Creates an empty scene.
    pub fn new() -> Self {
        Self::default()
    }

    // --- Lifecycle ---------------------------------------------------------

    /// Spawns a new root entity carrying `node` and `transform`.
    ///
    /// Every spawned entity automatically carries the three *node-baked*
    /// components: [`Node`], [`Transform`], and
    /// [`Layer`](crate::layer::Layer) (membership in the default layer).
    /// They are inherent to being an entity in this scene — single-instance,
    /// not added through the editor's "Add Component" menu, not removable.
    /// Modular components (`MeshRenderer`, future colliders, scripts, …) are
    /// attached on top.
    pub fn spawn(&mut self, node: impl Into<Node>, transform: Transform) -> Entity {
        let entity = self
            .world
            .spawn((node.into(), transform, crate::layer::Layer::default()));
        self.roots.push(entity);
        entity
    }

    /// Spawns a new entity as a child of `parent`. Auto-attaches the same
    /// node-baked components as [`spawn`](Self::spawn).
    ///
    /// # Panics
    /// Panics if `parent` is not a live entity in this scene.
    pub fn spawn_child(
        &mut self,
        parent: Entity,
        node: impl Into<Node>,
        transform: Transform,
    ) -> Entity {
        assert!(
            self.world.contains(parent),
            "spawn_child: parent {parent:?} is not a live entity in this scene"
        );
        let entity = self
            .world
            .spawn((node.into(), transform, crate::layer::Layer::default()));
        self.parents.insert(entity, parent);
        self.children.entry(parent).or_default().push(entity);
        entity
    }

    /// Despawns `entity`, handling its children according to `policy`.
    ///
    /// Returns `true` if the entity existed and was removed.
    pub fn despawn(&mut self, entity: Entity, policy: DespawnPolicy) -> bool {
        if !self.world.contains(entity) {
            return false;
        }
        // Remember the parent before unlinking, so DetachChildren can promote
        // the orphans to the right place.
        let grandparent = self.parents.get(&entity).copied();
        self.unlink(entity);

        match policy {
            DespawnPolicy::Recursive => self.despawn_recursive(entity),
            DespawnPolicy::DetachChildren => {
                let kids = self.children.remove(&entity).unwrap_or_default();
                let _ = self.world.despawn(entity);
                for kid in kids {
                    match grandparent {
                        Some(gp) => {
                            self.parents.insert(kid, gp);
                            self.children.entry(gp).or_default().push(kid);
                        }
                        None => {
                            self.parents.remove(&kid);
                            self.roots.push(kid);
                        }
                    }
                }
            }
        }
        true
    }

    /// Recursively despawns `entity` and everything beneath it. Assumes
    /// `entity` has already been unlinked from its parent / the root list.
    fn despawn_recursive(&mut self, entity: Entity) {
        let kids = self.children.remove(&entity).unwrap_or_default();
        self.parents.remove(&entity);
        let _ = self.world.despawn(entity);
        for kid in kids {
            self.despawn_recursive(kid);
        }
    }

    /// Removes `entity` from its parent's child list (or the root list) and
    /// from the parent map, without touching the entity itself.
    fn unlink(&mut self, entity: Entity) {
        match self.parents.remove(&entity) {
            Some(parent) => {
                if let Some(siblings) = self.children.get_mut(&parent) {
                    siblings.retain(|&e| e != entity);
                }
            }
            None => self.roots.retain(|&e| e != entity),
        }
    }

    // --- Hierarchy ---------------------------------------------------------

    /// Reparents `entity` under `new_parent`, or makes it a root when
    /// `new_parent` is `None`. Child ordering places `entity` last among its
    /// new siblings.
    ///
    /// Local transforms are preserved as-is (this does not compensate to keep
    /// the world transform fixed).
    ///
    /// # Errors
    /// - [`SceneError::NoSuchEntity`] if `entity` or `new_parent` is not live.
    /// - [`SceneError::WouldCycle`] if `new_parent` is `entity` itself or one
    ///   of its descendants.
    pub fn set_parent(
        &mut self,
        entity: Entity,
        new_parent: Option<Entity>,
    ) -> Result<(), SceneError> {
        if !self.world.contains(entity) {
            return Err(SceneError::NoSuchEntity);
        }
        if let Some(parent) = new_parent {
            if !self.world.contains(parent) {
                return Err(SceneError::NoSuchEntity);
            }
            // Walking up from the prospective parent must not reach `entity`,
            // otherwise the link would form a cycle.
            if parent == entity || self.is_ancestor(entity, parent) {
                return Err(SceneError::WouldCycle);
            }
        }

        self.unlink(entity);
        match new_parent {
            Some(parent) => {
                self.parents.insert(entity, parent);
                self.children.entry(parent).or_default().push(entity);
            }
            None => self.roots.push(entity),
        }
        Ok(())
    }

    /// Moves `entity` under `new_parent` (or to the root level when `None`),
    /// positioned **immediately before** sibling `before`. If `before` is
    /// `None` or isn't a child of the target, `entity` is appended.
    ///
    /// Unlike [`set_parent`](Self::set_parent) (which always appends), this
    /// controls the sibling order, so it covers both reparenting *and*
    /// reordering within the same parent — the operation a hierarchy
    /// drag-and-drop with an insertion indicator needs. The position is
    /// resolved *after* unlinking `entity`, so reordering within one parent
    /// doesn't suffer an off-by-one. Rejects cycles like `set_parent`.
    pub fn reorder(
        &mut self,
        entity: Entity,
        new_parent: Option<Entity>,
        before: Option<Entity>,
    ) -> Result<(), SceneError> {
        if !self.world.contains(entity) {
            return Err(SceneError::NoSuchEntity);
        }
        if let Some(parent) = new_parent {
            if !self.world.contains(parent) {
                return Err(SceneError::NoSuchEntity);
            }
            if parent == entity || self.is_ancestor(entity, parent) {
                return Err(SceneError::WouldCycle);
            }
        }

        self.unlink(entity);
        let siblings = match new_parent {
            Some(parent) => {
                self.parents.insert(entity, parent);
                self.children.entry(parent).or_default()
            }
            None => &mut self.roots,
        };
        let index = before
            .and_then(|b| siblings.iter().position(|&e| e == b))
            .unwrap_or(siblings.len());
        siblings.insert(index, entity);
        Ok(())
    }

    /// Returns `true` if `ancestor` lies on the parent chain above `entity`.
    fn is_ancestor(&self, ancestor: Entity, entity: Entity) -> bool {
        let mut cursor = self.parents.get(&entity).copied();
        while let Some(p) = cursor {
            if p == ancestor {
                return true;
            }
            cursor = self.parents.get(&p).copied();
        }
        false
    }

    /// The parent of `entity`, or `None` if it is a root or absent.
    pub fn parent(&self, entity: Entity) -> Option<Entity> {
        self.parents.get(&entity).copied()
    }

    /// The direct children of `entity`, in order. Empty for leaves.
    pub fn children(&self, entity: Entity) -> &[Entity] {
        self.children.get(&entity).map_or(&[], Vec::as_slice)
    }

    /// The top-level entities, in insertion order.
    pub fn roots(&self) -> &[Entity] {
        &self.roots
    }

    // --- Component access --------------------------------------------------

    /// The node name, or `None` if `entity` is not live.
    pub fn name(&self, entity: Entity) -> Option<String> {
        self.world.get::<&Node>(entity).ok().map(|n| n.name.clone())
    }

    /// Renames `entity`. Returns `false` if it is not live.
    pub fn set_name(&mut self, entity: Entity, name: impl Into<String>) -> bool {
        match self.world.get::<&mut Node>(entity) {
            Ok(mut node) => {
                node.name = name.into();
                true
            }
            Err(_) => false,
        }
    }

    /// Whether `entity` is enabled, or `None` if it is not live.
    pub fn is_enabled(&self, entity: Entity) -> Option<bool> {
        self.world.get::<&Node>(entity).ok().map(|n| n.enabled)
    }

    /// Sets the enabled flag on `entity`. Returns `false` if it is not live.
    pub fn set_enabled(&mut self, entity: Entity, enabled: bool) -> bool {
        match self.world.get::<&mut Node>(entity) {
            Ok(mut node) => {
                node.enabled = enabled;
                true
            }
            Err(_) => false,
        }
    }

    /// Whether the named component on `entity` is marked disabled by a
    /// [`DisabledComponents`](crate::scene::DisabledComponents) component.
    /// Defaults to `false` when no `DisabledComponents` is attached.
    ///
    /// Systems that act on a per-entity component query check this to honor
    /// "attached but inactive" — the ECS equivalent of Unity's
    /// `Component.enabled = false`.
    pub fn is_component_disabled(&self, entity: hecs::Entity, type_name: &str) -> bool {
        self.world
            .get::<&super::DisabledComponents>(entity)
            .ok()
            .map(|d| d.is_disabled(type_name))
            .unwrap_or(false)
    }

    /// Whether `entity` is enabled **and every ancestor is enabled** — its
    /// effective state in the hierarchy. `None` if it is not live.
    ///
    /// [`is_enabled`](Self::is_enabled) reports an entity's own authored flag;
    /// this reports whether it is actually active, since disabling a node
    /// disables its whole subtree (rendering, physics, audio, and queries skip
    /// effectively-disabled entities). This is the Unity/Godot
    /// `activeInHierarchy` distinction: the per-node flag is what you author,
    /// the effective value is what systems honor.
    pub fn is_effectively_enabled(&self, entity: Entity) -> Option<bool> {
        if !self.contains(entity) {
            return None;
        }
        let mut current = Some(entity);
        while let Some(e) = current {
            if !self.is_enabled(e).unwrap_or(true) {
                return Some(false);
            }
            current = self.parent(e);
        }
        Some(true)
    }

    /// The authored (local) transform of `entity`, or `None` if not live.
    pub fn local_transform(&self, entity: Entity) -> Option<Transform> {
        self.world.get::<&Transform>(entity).ok().map(|t| *t)
    }

    /// Sets the local transform of `entity`. Returns `false` if not live.
    pub fn set_local_transform(&mut self, entity: Entity, transform: Transform) -> bool {
        match self.world.get::<&mut Transform>(entity) {
            Ok(mut t) => {
                *t = transform;
                true
            }
            Err(_) => false,
        }
    }

    // --- World transforms --------------------------------------------------

    /// Resolves the world-space transform of a single `entity` by composing
    /// local transforms up the parent chain. `None` if `entity` is not live.
    ///
    /// For resolving many entities at once, prefer
    /// [`world_transforms`](Self::world_transforms), which is a single pass.
    pub fn world_transform(&self, entity: Entity) -> Option<Transform> {
        let local = self.local_transform(entity)?;
        match self.parents.get(&entity) {
            Some(&parent) => Some(self.world_transform(parent)?.mul_transform(&local)),
            None => Some(local),
        }
    }

    /// Resolves world-space transforms for every entity in the scene in a
    /// single top-down pass (`parent_world * local`).
    pub fn world_transforms(&self) -> HashMap<Entity, Transform> {
        let mut out = HashMap::with_capacity(self.len());
        // Depth-first from each root, carrying the accumulated parent world
        // transform down the stack.
        let mut stack: Vec<(Entity, Transform)> = Vec::new();
        for &root in &self.roots {
            if let Some(local) = self.local_transform(root) {
                stack.push((root, local));
            }
        }
        while let Some((entity, world)) = stack.pop() {
            out.insert(entity, world);
            if let Some(children) = self.children.get(&entity) {
                for &child in children {
                    if let Some(local) = self.local_transform(child) {
                        stack.push((child, world.mul_transform(&local)));
                    }
                }
            }
        }
        out
    }

    // --- ECS access --------------------------------------------------------

    /// Whether `entity` is live in this scene.
    pub fn contains(&self, entity: Entity) -> bool {
        self.world.contains(entity)
    }

    /// Number of live entities.
    pub fn len(&self) -> usize {
        self.world.len() as usize
    }

    /// Whether the scene has no entities.
    pub fn is_empty(&self) -> bool {
        self.world.len() == 0
    }

    /// An iterator over every live entity, in unspecified order.
    pub fn entities(&self) -> impl Iterator<Item = Entity> + '_ {
        self.world.iter().map(|e| e.entity())
    }

    /// Borrows a component of `entity`, e.g. `scene.get::<Transform>(e)`.
    pub fn get<T: Component>(&self, entity: Entity) -> Option<hecs::Ref<'_, T>> {
        self.world.get::<&T>(entity).ok()
    }

    /// Mutably borrows a component of `entity`.
    ///
    /// Do not mutate hierarchy state through here — use the scene's own
    /// methods so the parent/child bookkeeping stays consistent.
    pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<hecs::RefMut<'_, T>> {
        self.world.get::<&mut T>(entity).ok()
    }

    /// The underlying [`hecs::World`], for read-only ECS queries.
    pub fn world(&self) -> &World {
        &self.world
    }

    /// The underlying [`hecs::World`], for attaching extra components.
    ///
    /// Spawning or despawning directly through the world bypasses the scene's
    /// hierarchy bookkeeping; use [`spawn`](Self::spawn) /
    /// [`despawn`](Self::despawn) for lifecycle and reserve this for adding or
    /// querying non-hierarchy components.
    pub fn world_mut(&mut self) -> &mut World {
        &mut self.world
    }
}

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

    fn t(x: f32, y: f32, z: f32) -> Transform {
        Transform::from_translation(Vec3::new(x, y, z))
    }

    fn approx(a: Vec3, b: Vec3) -> bool {
        (a - b).length() <= 1e-5
    }

    #[test]
    fn spawn_makes_roots() {
        let mut scene = Scene::new();
        let a = scene.spawn("a", Transform::IDENTITY);
        let b = scene.spawn("b", Transform::IDENTITY);
        assert_eq!(scene.roots(), &[a, b]);
        assert_eq!(scene.len(), 2);
        assert_eq!(scene.parent(a), None);
    }

    #[test]
    fn spawn_child_links_both_ways() {
        let mut scene = Scene::new();
        let parent = scene.spawn("parent", Transform::IDENTITY);
        let child = scene.spawn_child(parent, "child", Transform::IDENTITY);
        assert_eq!(scene.parent(child), Some(parent));
        assert_eq!(scene.children(parent), &[child]);
        assert_eq!(scene.roots(), &[parent]); // child is not a root
    }

    #[test]
    fn world_transform_composes_down_the_chain() {
        let mut scene = Scene::new();
        let a = scene.spawn("a", t(1.0, 0.0, 0.0));
        let b = scene.spawn_child(a, "b", t(0.0, 2.0, 0.0));
        let c = scene.spawn_child(b, "c", t(0.0, 0.0, 3.0));
        let w = scene.world_transform(c).unwrap();
        assert!(approx(w.translation, Vec3::new(1.0, 2.0, 3.0)));
    }

    #[test]
    fn bulk_world_transforms_match_single() {
        let mut scene = Scene::new();
        let a = scene.spawn("a", t(5.0, 0.0, 0.0));
        let b = scene.spawn_child(a, "b", t(0.0, 1.0, 0.0));
        let c = scene.spawn_child(a, "c", t(0.0, 0.0, 1.0));
        let all = scene.world_transforms();
        for e in [a, b, c] {
            assert!(approx(
                all[&e].translation,
                scene.world_transform(e).unwrap().translation
            ));
        }
        assert!(approx(all[&b].translation, Vec3::new(5.0, 1.0, 0.0)));
        assert!(approx(all[&c].translation, Vec3::new(5.0, 0.0, 1.0)));
    }

    #[test]
    fn rotation_propagates_to_children() {
        use crate::math::Quat;
        use std::f32::consts::FRAC_PI_2;
        let mut scene = Scene::new();
        // Parent rotated 90° about Z, child offset +X by 1.
        let parent = scene.spawn(
            "p",
            Transform::from_rotation(Quat::from_rotation_z(FRAC_PI_2)),
        );
        let child = scene.spawn_child(parent, "c", t(1.0, 0.0, 0.0));
        let w = scene.world_transform(child).unwrap();
        // The +X offset is rotated into +Y by the parent.
        assert!(approx(w.translation, Vec3::new(0.0, 1.0, 0.0)));
    }

    #[test]
    fn despawn_recursive_removes_subtree() {
        let mut scene = Scene::new();
        let a = scene.spawn("a", Transform::IDENTITY);
        let b = scene.spawn_child(a, "b", Transform::IDENTITY);
        let c = scene.spawn_child(b, "c", Transform::IDENTITY);
        assert!(scene.despawn(a, DespawnPolicy::Recursive));
        assert!(!scene.contains(a));
        assert!(!scene.contains(b));
        assert!(!scene.contains(c));
        assert!(scene.roots().is_empty());
        assert_eq!(scene.len(), 0);
    }

    #[test]
    fn despawn_detach_promotes_children_to_grandparent() {
        let mut scene = Scene::new();
        let root = scene.spawn("root", Transform::IDENTITY);
        let mid = scene.spawn_child(root, "mid", Transform::IDENTITY);
        let leaf = scene.spawn_child(mid, "leaf", Transform::IDENTITY);
        assert!(scene.despawn(mid, DespawnPolicy::DetachChildren));
        assert!(!scene.contains(mid));
        assert!(scene.contains(leaf));
        // leaf is now a child of root directly.
        assert_eq!(scene.parent(leaf), Some(root));
        assert_eq!(scene.children(root), &[leaf]);
    }

    #[test]
    fn despawn_detach_root_promotes_children_to_roots() {
        let mut scene = Scene::new();
        let root = scene.spawn("root", Transform::IDENTITY);
        let child = scene.spawn_child(root, "child", Transform::IDENTITY);
        assert!(scene.despawn(root, DespawnPolicy::DetachChildren));
        assert_eq!(scene.parent(child), None);
        assert_eq!(scene.roots(), &[child]);
    }

    #[test]
    fn reparent_updates_links() {
        let mut scene = Scene::new();
        let a = scene.spawn("a", Transform::IDENTITY);
        let b = scene.spawn("b", Transform::IDENTITY);
        let c = scene.spawn_child(a, "c", Transform::IDENTITY);
        scene.set_parent(c, Some(b)).unwrap();
        assert_eq!(scene.parent(c), Some(b));
        assert_eq!(scene.children(a), &[] as &[Entity]);
        assert_eq!(scene.children(b), &[c]);
    }

    #[test]
    fn reparent_to_root() {
        let mut scene = Scene::new();
        let a = scene.spawn("a", Transform::IDENTITY);
        let c = scene.spawn_child(a, "c", Transform::IDENTITY);
        scene.set_parent(c, None).unwrap();
        assert_eq!(scene.parent(c), None);
        assert!(scene.roots().contains(&c));
        assert_eq!(scene.children(a), &[] as &[Entity]);
    }

    #[test]
    fn reparent_cycle_is_rejected() {
        let mut scene = Scene::new();
        let a = scene.spawn("a", Transform::IDENTITY);
        let b = scene.spawn_child(a, "b", Transform::IDENTITY);
        // Making `a` a child of its own descendant `b` would form a cycle.
        assert_eq!(scene.set_parent(a, Some(b)), Err(SceneError::WouldCycle));
        // Self-parenting is also a cycle.
        assert_eq!(scene.set_parent(a, Some(a)), Err(SceneError::WouldCycle));
        // The hierarchy is unchanged.
        assert_eq!(scene.parent(b), Some(a));
        assert_eq!(scene.parent(a), None);
    }

    #[test]
    fn reparent_missing_entity_errors() {
        let mut scene = Scene::new();
        let a = scene.spawn("a", Transform::IDENTITY);
        scene.despawn(a, DespawnPolicy::Recursive);
        assert_eq!(scene.set_parent(a, None), Err(SceneError::NoSuchEntity));
    }

    #[test]
    fn enable_and_rename() {
        let mut scene = Scene::new();
        let a = scene.spawn("a", Transform::IDENTITY);
        assert_eq!(scene.is_enabled(a), Some(true));
        assert!(scene.set_enabled(a, false));
        assert_eq!(scene.is_enabled(a), Some(false));
        assert!(scene.set_name(a, "renamed"));
        assert_eq!(scene.name(a).as_deref(), Some("renamed"));
    }

    #[test]
    fn reorder_moves_within_and_between_parents() {
        let mut scene = Scene::new();
        let a = scene.spawn("a", Transform::IDENTITY);
        let b = scene.spawn("b", Transform::IDENTITY);
        let c = scene.spawn("c", Transform::IDENTITY);
        assert_eq!(scene.roots(), &[a, b, c]);

        // Reorder within the root list: move c before a → [c, a, b].
        scene.reorder(c, None, Some(a)).unwrap();
        assert_eq!(scene.roots(), &[c, a, b]);

        // Same-parent move that crosses its old slot (no off-by-one): move c to
        // just before b → [a, c, b].
        scene.reorder(c, None, Some(b)).unwrap();
        assert_eq!(scene.roots(), &[a, c, b]);

        // Reparent + position: put b under a, before none → appended child.
        scene.reorder(b, Some(a), None).unwrap();
        assert_eq!(scene.roots(), &[a, c]);
        assert_eq!(scene.children(a), &[b]);
        assert_eq!(scene.parent(b), Some(a));

        // Insert before an existing child: c under a, before b → [c, b].
        scene.reorder(c, Some(a), Some(b)).unwrap();
        assert_eq!(scene.children(a), &[c, b]);

        // Cycle rejected: a cannot become a child of its descendant b.
        assert!(matches!(
            scene.reorder(a, Some(b), None),
            Err(SceneError::WouldCycle)
        ));
    }

    #[test]
    fn spawn_auto_attaches_layers_default() {
        // Every entity is inherently *on* some layer (the default if not
        // overridden) — so Layer is a node-baked component the scene always
        // provides, not something the user has to add. Pinned here.
        use super::super::super::layer::Layer;
        let mut scene = Scene::new();
        let e = scene.spawn("e", Transform::IDENTITY);
        {
            let layers = scene.world().get::<&Layer>(e).expect("Layer attached");
            assert_eq!(*layers, Layer::DEFAULT);
        }

        let child = scene.spawn_child(e, "child", Transform::IDENTITY);
        let layers = scene
            .world()
            .get::<&Layer>(child)
            .expect("child gets Layer too");
        assert_eq!(*layers, Layer::DEFAULT);
    }

    #[test]
    fn is_component_disabled_reads_the_disabled_set() {
        use super::super::DisabledComponents;
        let mut scene = Scene::new();
        let e = scene.spawn("e", Transform::IDENTITY);
        // No DisabledComponents attached → nothing is disabled.
        assert!(!scene.is_component_disabled(e, "MeshRenderer"));

        let mut d = DisabledComponents::new();
        d.set_disabled("MeshRenderer", true);
        scene.world_mut().insert_one(e, d).unwrap();
        assert!(scene.is_component_disabled(e, "MeshRenderer"));
        assert!(!scene.is_component_disabled(e, "RigidBody"));
    }

    #[test]
    fn effective_enabled_cascades_from_ancestors() {
        let mut scene = Scene::new();
        let player = scene.spawn("player", Transform::IDENTITY);
        let camera = scene.spawn_child(player, "camera", Transform::IDENTITY);
        let mesh = scene.spawn_child(camera, "mesh", Transform::IDENTITY);

        // All enabled by default → effectively enabled.
        assert_eq!(scene.is_effectively_enabled(mesh), Some(true));

        // Disabling the root disables the whole subtree's effective state,
        // even though each descendant's own flag is still true.
        scene.set_enabled(player, false);
        assert_eq!(scene.is_enabled(camera), Some(true)); // own flag unchanged
        assert_eq!(scene.is_effectively_enabled(camera), Some(false));
        assert_eq!(scene.is_effectively_enabled(mesh), Some(false));

        // Re-enable the root; disable a middle node → only it + below are off.
        scene.set_enabled(player, true);
        scene.set_enabled(camera, false);
        assert_eq!(scene.is_effectively_enabled(player), Some(true));
        assert_eq!(scene.is_effectively_enabled(camera), Some(false));
        assert_eq!(scene.is_effectively_enabled(mesh), Some(false));

        // A dead entity has no effective state.
        let ghost = scene.spawn("ghost", Transform::IDENTITY);
        scene.despawn(ghost, DespawnPolicy::Recursive);
        assert_eq!(scene.is_effectively_enabled(ghost), None);
    }

    #[test]
    fn extra_components_via_world() {
        // The scene is a real ECS: extra components can ride along on entities.
        let mut scene = Scene::new();
        let a = scene.spawn("a", Transform::IDENTITY);
        scene.world_mut().insert_one(a, 42u32).unwrap();
        assert_eq!(*scene.get::<u32>(a).unwrap(), 42);
    }
}
