//! RON serialization for [`Scene`].
//!
//! `hecs::Entity` handles are runtime values that are not stable across a
//! save/load, so the scene is flattened to a list of records with array
//! indices standing in for entity references. The list is built in a
//! deterministic pre-order walk of the hierarchy, so a serialize → deserialize
//! → serialize cycle is byte-for-byte stable.

use std::collections::HashMap;

use hecs::Entity;
use serde::{Deserialize, Serialize};

use super::{Node, Scene, SceneError};
use crate::math::Transform;

/// One entity in the flattened scene. `children` holds indices into the
/// surrounding [`SceneData::nodes`] list.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct NodeRecord {
    name: String,
    enabled: bool,
    transform: Transform,
    children: Vec<usize>,
}

/// The serializable form of a [`Scene`]: a flat node list plus the indices of
/// the root nodes. Parent links are implied by the `children` arrays.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
struct SceneData {
    nodes: Vec<NodeRecord>,
    roots: Vec<usize>,
}

impl Scene {
    /// Serializes the scene to a pretty-printed RON string.
    pub fn to_ron(&self) -> Result<String, SceneError> {
        let data = self.to_data();
        ron::ser::to_string_pretty(&data, ron::ser::PrettyConfig::default())
            .map_err(|e| SceneError::Serialize(e.to_string()))
    }

    /// Reconstructs a scene from a RON string produced by [`to_ron`](Self::to_ron).
    pub fn from_ron(ron: &str) -> Result<Scene, SceneError> {
        let data: SceneData =
            ron::from_str(ron).map_err(|e| SceneError::Deserialize(e.to_string()))?;
        Scene::from_data(&data)
    }

    /// Flattens the hierarchy into index-based records via a deterministic
    /// pre-order walk (roots in order, then each subtree depth-first).
    fn to_data(&self) -> SceneData {
        let mut index: HashMap<Entity, usize> = HashMap::with_capacity(self.len());
        let mut order: Vec<Entity> = Vec::with_capacity(self.len());
        for &root in self.roots() {
            self.assign_indices(root, &mut index, &mut order);
        }

        let nodes = order
            .iter()
            .map(|&entity| {
                let node = self
                    .get::<Node>(entity)
                    .expect("entity in hierarchy must have a Node");
                let transform = self
                    .local_transform(entity)
                    .expect("entity in hierarchy must have a Transform");
                NodeRecord {
                    name: node.name.clone(),
                    enabled: node.enabled,
                    transform,
                    children: self.children(entity).iter().map(|c| index[c]).collect(),
                }
            })
            .collect();

        let roots = self.roots().iter().map(|r| index[r]).collect();
        SceneData { nodes, roots }
    }

    /// Pre-order index assignment helper for [`to_data`](Self::to_data).
    fn assign_indices(
        &self,
        entity: Entity,
        index: &mut HashMap<Entity, usize>,
        order: &mut Vec<Entity>,
    ) {
        index.insert(entity, order.len());
        order.push(entity);
        for &child in self.children(entity) {
            self.assign_indices(child, index, order);
        }
    }

    /// Rebuilds a scene from flattened records, validating index references.
    fn from_data(data: &SceneData) -> Result<Scene, SceneError> {
        let mut scene = Scene::new();
        let n = data.nodes.len();

        // Spawn every entity first (as a root), so all indices resolve before
        // wiring up parent/child links.
        let entities: Vec<Entity> = data
            .nodes
            .iter()
            .map(|rec| {
                scene.spawn(
                    Node {
                        name: rec.name.clone(),
                        enabled: rec.enabled,
                    },
                    rec.transform,
                )
            })
            .collect();

        // Re-link: each record's children become children of that record's
        // entity (and are removed from the root list).
        for (i, rec) in data.nodes.iter().enumerate() {
            for &child_idx in &rec.children {
                let child = *entities
                    .get(child_idx)
                    .ok_or(SceneError::Deserialize(format!(
                        "child index {child_idx} out of range (have {n} nodes)"
                    )))?;
                scene
                    .set_parent(child, Some(entities[i]))
                    .map_err(|e| SceneError::Deserialize(e.to_string()))?;
            }
        }

        // Validate the declared roots match the entities left parentless. The
        // re-link above already produced the correct root set; we just confirm
        // the file's `roots` list is consistent so corrupt input is rejected.
        for &root_idx in &data.roots {
            let entity = *entities
                .get(root_idx)
                .ok_or(SceneError::Deserialize(format!(
                    "root index {root_idx} out of range (have {n} nodes)"
                )))?;
            if scene.parent(entity).is_some() {
                return Err(SceneError::Deserialize(format!(
                    "node {root_idx} is listed as a root but is also a child"
                )));
            }
        }

        Ok(scene)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::math::{Quat, Vec3};
    use crate::scene::DespawnPolicy;

    /// Builds a small, varied scene used by the round-trip tests.
    fn sample() -> Scene {
        let mut scene = Scene::new();
        let root = scene.spawn(
            Node {
                name: "root".into(),
                enabled: true,
            },
            Transform::from_translation(Vec3::new(1.0, 2.0, 3.0)),
        );
        let arm = scene.spawn_child(
            root,
            "arm",
            Transform::from_rotation(Quat::from_rotation_y(0.5)),
        );
        scene.spawn_child(arm, "hand", Transform::from_scale(Vec3::splat(2.0)));
        let mut disabled = Node::new("disabled");
        disabled.enabled = false;
        scene.spawn_child(root, disabled, Transform::IDENTITY);
        // A second independent root, to exercise multi-root serialization.
        scene.spawn("other-root", Transform::from_translation(Vec3::NEG_X));
        scene
    }

    #[test]
    fn round_trip_preserves_structure() {
        let scene = sample();
        let ron = scene.to_ron().unwrap();
        let restored = Scene::from_ron(&ron).unwrap();

        // Re-serializing the restored scene yields identical text: structure,
        // names, flags, transforms, and ordering all survived.
        assert_eq!(ron, restored.to_ron().unwrap());
        assert_eq!(scene.len(), restored.len());
        assert_eq!(scene.roots().len(), restored.roots().len());
    }

    #[test]
    fn round_trip_preserves_world_transforms() {
        let scene = sample();
        let restored = Scene::from_ron(&scene.to_ron().unwrap()).unwrap();

        // Compare resolved world transforms by name, since entity ids differ
        // across the rebuild.
        let by_name = |s: &Scene| -> Vec<(String, Vec3)> {
            let worlds = s.world_transforms();
            let mut v: Vec<_> = worlds
                .iter()
                .map(|(&e, t)| (s.name(e).unwrap(), t.translation))
                .collect();
            v.sort_by(|a, b| a.0.cmp(&b.0));
            v
        };
        let a = by_name(&scene);
        let b = by_name(&restored);
        assert_eq!(a.len(), b.len());
        for ((na, ta), (nb, tb)) in a.iter().zip(b.iter()) {
            assert_eq!(na, nb);
            assert!((*ta - *tb).length() <= 1e-5, "{na}: {ta} vs {tb}");
        }
    }

    #[test]
    fn empty_scene_round_trips() {
        let scene = Scene::new();
        let restored = Scene::from_ron(&scene.to_ron().unwrap()).unwrap();
        assert!(restored.is_empty());
    }

    #[test]
    fn reordering_after_edits_still_round_trips() {
        // Mutating the scene (despawn) must not break index bookkeeping.
        let mut scene = sample();
        let root = scene.roots()[0];
        let kid = scene.children(root)[0];
        scene.despawn(kid, DespawnPolicy::DetachChildren);
        let ron = scene.to_ron().unwrap();
        assert_eq!(ron, Scene::from_ron(&ron).unwrap().to_ron().unwrap());
    }

    #[test]
    fn corrupt_child_index_is_rejected() {
        let bad = r#"(nodes: [(name: "a", enabled: true, transform: (translation: (0,0,0), rotation: (0,0,0,1), scale: (1,1,1)), children: [5])], roots: [0])"#;
        assert!(Scene::from_ron(bad).is_err());
    }
}
