//! Prefabs — named templates that spawn an entity already carrying a set of
//! components.
//!
//! The engine deliberately has **no parallel "object type" system**: an entity
//! *is* its set of components. A [`Prefab`] is therefore nothing more than a
//! named bundle of **(component name, value)** specs, applied on spawn through
//! the [`TypeRegistry`]. "Spawn a Cube" means "spawn an entity, then set its
//! `MeshRenderer` to a cube" — the same name-keyed path the editor and scripts
//! already use, so prefabs are pure data (serializable, dual-editable) rather
//! than code.
//!
//! This is what makes the editor's add-menu **data-driven**: the menu lists the
//! prefabs in a [`PrefabRegistry`] instead of hard-coding one button per type.
//!
//! ```
//! use oxide_engine::prelude::*;
//! use oxide_engine::prefab::{ComponentSpec, Prefab, PrefabRegistry};
//! use oxide_engine::reflect::TypeRegistry;
//!
//! // A registry that knows how to round-trip MeshRenderer by name.
//! let mut types = TypeRegistry::new();
//! types.register_reflected::<MeshRenderer>("MeshRenderer");
//!
//! // A "Cube" prefab: an entity carrying a default MeshRenderer (shape = Cube).
//! let mut prefabs = PrefabRegistry::new();
//! prefabs.register(
//!     Prefab::new("Cube")
//!         .with(ComponentSpec::of("MeshRenderer", &MeshRenderer::default()).unwrap()),
//! );
//!
//! let mut scene = Scene::new();
//! let cube = prefabs.spawn("Cube", &mut scene, &types).unwrap();
//! assert!(types.has(scene.world(), cube, "MeshRenderer").unwrap());
//! ```

use std::collections::BTreeMap;

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

use crate::math::Transform;
use crate::reflect::TypeRegistry;
use crate::scene::Scene;

/// One component a prefab attaches: a registered type **name** plus its value
/// serialized as **RON** — the same representation [`TypeRegistry::set_ron`]
/// consumes.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ComponentSpec {
    /// The component's registered name in the [`TypeRegistry`].
    pub type_name: String,
    /// The component value as RON.
    pub ron: String,
}

impl ComponentSpec {
    /// A spec from a name and an already-serialized RON string.
    pub fn new(type_name: impl Into<String>, ron: impl Into<String>) -> Self {
        Self {
            type_name: type_name.into(),
            ron: ron.into(),
        }
    }

    /// A spec built by serializing a concrete component `value`. Returns `None`
    /// if it cannot be serialized to RON.
    pub fn of<T: Serialize>(type_name: impl Into<String>, value: &T) -> Option<Self> {
        ron::to_string(value)
            .ok()
            .map(|ron| Self::new(type_name, ron))
    }
}

/// A named spawn template: a node name plus the components to attach beyond the
/// node-baked ones.
///
/// Every spawned entity already carries `Node`, `Transform`, and `Layer`
/// (auto-attached by [`Scene::spawn`]); a prefab's [`components`](Self::components)
/// are layered on top. A spec named `"Transform"` overrides the identity
/// transform `spawn` starts with, so a prefab can place itself.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Prefab {
    /// The name given to the spawned node (also the registry key).
    pub name: String,
    /// Components attached on spawn, applied in order.
    pub components: Vec<ComponentSpec>,
}

impl Prefab {
    /// An empty prefab (spawns a bare node with just the node-baked components).
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            components: Vec::new(),
        }
    }

    /// Adds a component spec (builder style).
    pub fn with(mut self, spec: ComponentSpec) -> Self {
        self.components.push(spec);
        self
    }
}

/// A registry of prefabs keyed by name — the data-driven source for the
/// editor's "add an entity that already carries these components" menu.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct PrefabRegistry {
    prefabs: BTreeMap<String, Prefab>,
}

impl PrefabRegistry {
    /// An empty registry.
    pub fn new() -> Self {
        Self::default()
    }

    /// Registers `prefab` under its [`name`](Prefab::name). Re-registering the
    /// same name replaces the entry.
    pub fn register(&mut self, prefab: Prefab) {
        self.prefabs.insert(prefab.name.clone(), prefab);
    }

    /// The prefab registered under `name`, if any.
    pub fn get(&self, name: &str) -> Option<&Prefab> {
        self.prefabs.get(name)
    }

    /// Whether a prefab is registered under `name`.
    pub fn contains(&self, name: &str) -> bool {
        self.prefabs.contains_key(name)
    }

    /// The registered prefab names, sorted — what an add-menu lists.
    pub fn names(&self) -> impl Iterator<Item = &str> + '_ {
        self.prefabs.keys().map(String::as_str)
    }

    /// The number of registered prefabs.
    pub fn len(&self) -> usize {
        self.prefabs.len()
    }

    /// Whether no prefabs are registered.
    pub fn is_empty(&self) -> bool {
        self.prefabs.is_empty()
    }

    /// Spawns the named prefab as a **root** entity, applying its component
    /// specs through `registry`. Returns the new entity, or `None` if `name`
    /// isn't registered.
    ///
    /// Application is best-effort: a spec whose type isn't registered or whose
    /// RON doesn't parse is skipped (the entity is still created with whatever
    /// applied). Use [`unknown_specs`](Self::unknown_specs) to validate a prefab
    /// against a registry up front.
    pub fn spawn(&self, name: &str, scene: &mut Scene, registry: &TypeRegistry) -> Option<Entity> {
        let prefab = self.prefabs.get(name)?;
        let entity = scene.spawn(prefab.name.clone(), Transform::IDENTITY);
        apply(prefab, entity, scene, registry);
        Some(entity)
    }

    /// Like [`spawn`](Self::spawn) but parents the new entity under `parent`.
    pub fn spawn_child(
        &self,
        name: &str,
        parent: Entity,
        scene: &mut Scene,
        registry: &TypeRegistry,
    ) -> Option<Entity> {
        let prefab = self.prefabs.get(name)?;
        let entity = scene.spawn_child(parent, prefab.name.clone(), Transform::IDENTITY);
        apply(prefab, entity, scene, registry);
        Some(entity)
    }

    /// The type names a prefab references that `registry` doesn't know — empty
    /// when the prefab will spawn fully. Handy for surfacing authoring typos.
    pub fn unknown_specs(&self, name: &str, registry: &TypeRegistry) -> Vec<String> {
        self.prefabs
            .get(name)
            .map(|p| {
                p.components
                    .iter()
                    .filter(|s| !registry.is_registered(&s.type_name))
                    .map(|s| s.type_name.clone())
                    .collect()
            })
            .unwrap_or_default()
    }
}

/// Applies a prefab's component specs onto an already-spawned `entity`.
fn apply(prefab: &Prefab, entity: Entity, scene: &mut Scene, registry: &TypeRegistry) {
    for spec in &prefab.components {
        // Best-effort: an unknown type or malformed RON simply doesn't apply,
        // leaving the rest of the prefab intact.
        let _ = registry.set_ron(scene.world_mut(), entity, &spec.type_name, &spec.ron);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::render::{MeshRenderer, PrimitiveShape};
    use crate::scene::Node;

    fn types() -> TypeRegistry {
        let mut r = TypeRegistry::new();
        r.register_reflected::<Transform>("Transform");
        r.register_reflected::<MeshRenderer>("MeshRenderer");
        r
    }

    #[test]
    fn registry_lists_names_sorted_and_looks_up() {
        let mut prefabs = PrefabRegistry::new();
        prefabs.register(Prefab::new("Sphere"));
        prefabs.register(Prefab::new("Cube"));
        assert_eq!(prefabs.names().collect::<Vec<_>>(), vec!["Cube", "Sphere"]);
        assert!(prefabs.contains("Cube"));
        assert!(prefabs.get("Cube").is_some());
        assert_eq!(prefabs.len(), 2);
    }

    #[test]
    fn spawn_attaches_specced_components() {
        let types = types();
        let mut prefabs = PrefabRegistry::new();
        let mesh = MeshRenderer {
            shape: PrimitiveShape::Sphere,
            ..MeshRenderer::default()
        };
        prefabs
            .register(Prefab::new("Ball").with(ComponentSpec::of("MeshRenderer", &mesh).unwrap()));

        let mut scene = Scene::new();
        let e = prefabs.spawn("Ball", &mut scene, &types).unwrap();

        // Node name comes from the prefab; the spec'd component is attached.
        assert_eq!(scene.world().get::<&Node>(e).unwrap().name, "Ball");
        let got = scene.world().get::<&MeshRenderer>(e).unwrap();
        assert_eq!(got.shape, PrimitiveShape::Sphere);
    }

    #[test]
    fn spawn_child_parents_under_the_target() {
        let types = types();
        let mut prefabs = PrefabRegistry::new();
        prefabs.register(Prefab::new("Child"));

        let mut scene = Scene::new();
        let parent = scene.spawn("parent", Transform::IDENTITY);
        let child = prefabs
            .spawn_child("Child", parent, &mut scene, &types)
            .unwrap();
        assert_eq!(scene.parent(child), Some(parent));
    }

    #[test]
    fn transform_spec_overrides_the_identity_spawn() {
        let types = types();
        let mut prefabs = PrefabRegistry::new();
        let placed = Transform::from_translation(crate::math::Vec3::new(1.0, 2.0, 3.0));
        prefabs
            .register(Prefab::new("Placed").with(ComponentSpec::of("Transform", &placed).unwrap()));

        let mut scene = Scene::new();
        let e = prefabs.spawn("Placed", &mut scene, &types).unwrap();
        let t = scene.local_transform(e).unwrap();
        assert!((t.translation - crate::math::Vec3::new(1.0, 2.0, 3.0)).length() < 1e-6);
    }

    #[test]
    fn unknown_prefab_name_spawns_nothing() {
        let types = types();
        let prefabs = PrefabRegistry::new();
        let mut scene = Scene::new();
        assert!(prefabs.spawn("Nope", &mut scene, &types).is_none());
    }

    #[test]
    fn unknown_specs_are_reported_and_skipped() {
        let types = types(); // knows Transform + MeshRenderer
        let mut prefabs = PrefabRegistry::new();
        prefabs.register(
            Prefab::new("Mixed")
                .with(ComponentSpec::of("MeshRenderer", &MeshRenderer::default()).unwrap())
                .with(ComponentSpec::new("Ghost", "()")),
        );
        assert_eq!(prefabs.unknown_specs("Mixed", &types), vec!["Ghost"]);

        // Spawn still succeeds; the known component applies, the ghost is skipped.
        let mut scene = Scene::new();
        let e = prefabs.spawn("Mixed", &mut scene, &types).unwrap();
        assert!(types.has(scene.world(), e, "MeshRenderer").unwrap());
    }

    #[test]
    fn prefab_round_trips_through_ron() {
        let mut prefabs = PrefabRegistry::new();
        prefabs.register(
            Prefab::new("Cube")
                .with(ComponentSpec::of("MeshRenderer", &MeshRenderer::default()).unwrap()),
        );
        let ron = ron::to_string(&prefabs).unwrap();
        let back: PrefabRegistry = ron::from_str(&ron).unwrap();
        assert_eq!(prefabs, back);
    }
}
