//! The custom ECS World: the heart of the engine (learning path step 7).
//!
//! An entity is an id (index + generation). A component is plain data living
//! in its own type-erased column. A system is a plain function that borrows
//! the columns it needs. The World owns the slots, the columns, and a small
//! resource map of singletons (input snapshot, camera, render queue).
//!
//! Design pinned by design.md section 4 and the guide `guides/ecs-world.md`,
//! which carries every function in this file with its full teaching text.
//! Zero dependencies: everything here is std plus our own types. No unsafe.
//!
//! Status: implemented and headless-tested (`cargo test --no-default-features`).

use std::any::{Any, TypeId};
use std::collections::HashMap;

// ---------------------------------------------------------------------------
// Entity: an id with a liveness check built in
// ---------------------------------------------------------------------------

/// An entity is an index plus a generation, so a stale handle to a recycled
/// slot is caught instead of silently aliasing a new entity.
///
/// `index` asks "which slot?"; `generation` answers "which *lifetime* of that
/// slot?". Slot 3 might host the player now and a particle later: the player
/// handle is (3, gen 0), the particle's is (3, gen 1). Different values, so
/// the old handle can never reach the new tenant.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Entity {
    index: u32,
    generation: u32,
}

impl Entity {
    /// Pack both halves into one integer for a trip across a language
    /// border (Lua numbers, network ids, save files). The generation rides
    /// along, so a stale id that comes BACK across the border still fails
    /// `is_alive`: the safety survives the round trip.
    pub fn to_bits(self) -> u64 {
        ((self.generation as u64) << 32) | self.index as u64
    }

    pub fn from_bits(bits: u64) -> Self {
        Self {
            index: bits as u32,
            generation: (bits >> 32) as u32,
        }
    }
}

// ---------------------------------------------------------------------------
// Column<T>: per-type storage, struct-of-arrays
// ---------------------------------------------------------------------------

/// Each component type is a column: a Vec indexed by entity index.
/// "Struct of arrays." This IS the storage model, in the open.
/// `None` means "this entity lacks this component".
struct Column<T> {
    data: Vec<Option<T>>,
}

impl<T> Column<T> {
    fn new() -> Self {
        Self { data: Vec::new() }
    }

    /// Grow to hold index n-1; `resize_with` works even when T is not Clone.
    fn ensure_len(&mut self, n: usize) {
        if self.data.len() < n {
            self.data.resize_with(n, || None);
        }
    }
}

/// The appendix exercise, done (item 9 of the v0.4 sweep): a small custom
/// trait object - the same tool `Any` itself is built from - so despawn can
/// walk columns it cannot name and clear the dead entity's slots. This is
/// the C++ "abstract base with a virtual clear()" design, plus a CHECKED
/// route back down to the concrete `Column<T>`.
trait AnyColumn {
    /// The typeless operation despawn needs.
    fn clear_slot(&mut self, index: usize);
    /// The road back down to `Column<T>` (checked, via Any).
    fn as_any(&self) -> &dyn Any;
    fn as_any_mut(&mut self) -> &mut dyn Any;
}

impl<T: 'static> AnyColumn for Column<T> {
    fn clear_slot(&mut self, index: usize) {
        if let Some(slot) = self.data.get_mut(index) {
            *slot = None;
        }
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
    fn as_any_mut(&mut self) -> &mut dyn Any {
        self
    }
}

// ---------------------------------------------------------------------------
// World: slots, columns, resources
// ---------------------------------------------------------------------------

/// The World is three fields plus a resource map: a generation per slot, a
/// free-list of recycled slots, and one column per component type, stored
/// type-erased so one HashMap can hold columns of *any* component type.
///
/// The interesting field is `columns`. A HashMap needs one value type, but we
/// want a `Column<Transform>`, a `Column<Velocity>`, a `Column<Sprite>`...
/// The answer is type erasure: box each column, forget its concrete type
/// (`Box<dyn Any>`), and key it by the `TypeId` that lets you get the type
/// back later with a checked downcast.
#[derive(Default)]
pub struct World {
    generations: Vec<u32>,                      // per-slot liveness
    free: Vec<u32>,                             // recycled slots
    columns: HashMap<TypeId, Box<dyn AnyColumn>>, // Box<Column<T>>, erased
    resources: HashMap<TypeId, Box<dyn Any>>,   // singletons, same trick
}

impl World {
    pub fn new() -> Self {
        Self::default()
    }

    /// The number of live entities.
    pub fn len(&self) -> usize {
        self.generations.len() - self.free.len()
    }

    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    // -- spawn / despawn / is_alive -----------------------------------------

    /// New entities take a fresh slot; recycled slots come off the free-list
    /// with the generation that was already bumped at despawn, so the new
    /// handle is distinct from every handle the slot ever issued.
    pub fn spawn(&mut self) -> Entity {
        if let Some(index) = self.free.pop() {
            // recycle: the slot's generation was already bumped at despawn
            Entity { index, generation: self.generations[index as usize] }
        } else {
            let index = self.generations.len() as u32;
            self.generations.push(0);
            Entity { index, generation: 0 }
        }
    }

    /// Alive means: the slot still holds the generation this handle was born
    /// with. A stale handle fails here forever; the slot only moves forward.
    pub fn is_alive(&self, entity: Entity) -> bool {
        self.generations.get(entity.index as usize) == Some(&entity.generation)
    }

    /// Bump the slot's generation (killing every outstanding handle), CLEAR
    /// the entity's component values, and put the index on the free-list.
    /// Double-despawn is a no-op.
    ///
    /// The v0.1 wart is gone (the appendix exercise, done): despawn now
    /// walks every column through the typeless `AnyColumn::clear_slot`, so
    /// a recycled slot starts EMPTY and a render system can never draw a
    /// ghost. `Box<dyn Any>` could not write this loop; `Box<dyn AnyColumn>`
    /// can, and that one-trait upgrade is the whole fix.
    pub fn despawn(&mut self, entity: Entity) {
        if !self.is_alive(entity) {
            return; // double-despawn is a no-op
        }
        self.generations[entity.index as usize] += 1; // old handles now mismatch
        for column in self.columns.values_mut() {
            column.clear_slot(entity.index as usize); // the loop dyn Any forbade
        }
        self.free.push(entity.index);
    }

    // -- typed component access through the erased storage ------------------

    /// File `value` into T's column at the entity's index, creating the
    /// column on first use. Every typed method makes the same two moves:
    /// find the column by TypeId, downcast it back to `Column<T>`.
    pub fn insert<T: 'static>(&mut self, entity: Entity, value: T) {
        if !self.is_alive(entity) {
            return;
        }
        let column = self
            .columns
            .entry(TypeId::of::<T>())                        // find or create the column...
            .or_insert_with(|| Box::new(Column::<T>::new()) as Box<dyn AnyColumn>)
            .as_any_mut()
            .downcast_mut::<Column<T>>()                     // ...and recover its real type
            .expect("column type mismatch");
        column.ensure_len(entity.index as usize + 1);
        column.data[entity.index as usize] = Some(value);
    }

    /// Read as a chain of four ways to miss, each one a `?`: no such column,
    /// downcast mismatch (cannot happen), index past the column's end, empty
    /// slot. Any miss short-circuits to `None`. No path returns garbage, and
    /// a stale handle stops at `is_alive` before touching the column at all.
    pub fn get<T: 'static>(&self, entity: Entity) -> Option<&T> {
        if !self.is_alive(entity) {
            return None; // stale handles stop HERE
        }
        self.columns
            .get(&TypeId::of::<T>())?
            .as_any()
            .downcast_ref::<Column<T>>()?
            .data
            .get(entity.index as usize)?
            .as_ref()
    }

    pub fn get_mut<T: 'static>(&mut self, entity: Entity) -> Option<&mut T> {
        if !self.is_alive(entity) {
            return None;
        }
        self.columns
            .get_mut(&TypeId::of::<T>())?
            .as_any_mut()
            .downcast_mut::<Column<T>>()?
            .data
            .get_mut(entity.index as usize)?
            .as_mut()
    }

    /// Move the value out, leaving `None` behind.
    pub fn remove<T: 'static>(&mut self, entity: Entity) -> Option<T> {
        if !self.is_alive(entity) {
            return None;
        }
        self.columns
            .get_mut(&TypeId::of::<T>())?
            .as_any_mut()
            .downcast_mut::<Column<T>>()?
            .data
            .get_mut(entity.index as usize)?
            .take()
    }

    /// Empty T's whole column and hand back the values (fire-and-forget
    /// request components like `PlaySound` use this; see the module guide).
    /// Column-level, not entity-level: it drains every occupied slot.
    pub fn drain<T: 'static>(&mut self) -> Vec<T> {
        match self
            .columns
            .get_mut(&TypeId::of::<T>())
            .and_then(|c| c.as_any_mut().downcast_mut::<Column<T>>())
        {
            Some(column) => column.data.iter_mut().filter_map(Option::take).collect(),
            None => Vec::new(),
        }
    }

    // -- whole-column access for systems ------------------------------------

    /// One mutable column, for single-column systems
    /// (`world.column_mut::<Transform>().iter_mut().flatten()`).
    pub fn column_mut<T: 'static>(&mut self) -> &mut [Option<T>] {
        let column = self
            .columns
            .entry(TypeId::of::<T>())
            .or_insert_with(|| Box::new(Column::<T>::new()) as Box<dyn AnyColumn>)
            .as_any_mut()
            .downcast_mut::<Column<T>>()
            .expect("column type mismatch");
        column.data.as_mut_slice()
    }

    /// One `&mut World` goes in; two provably disjoint mutable columns come
    /// out. THE aliasing lesson: two `&mut` into one World are only sound if
    /// they cannot overlap. Different TypeIds = different columns = disjoint.
    ///
    /// The naive version (`let a = self.columns.get_mut(&ka); let b = ...`)
    /// does not compile: two live `&mut` borrows of `self.columns`, and the
    /// compiler cannot know the keys differ. `get_disjoint_mut` takes both
    /// keys in a single call, checks they differ, and only then splits the
    /// borrow. Our `assert_ne!` is the same check with a better message.
    pub fn columns_mut<A: 'static, B: 'static>(
        &mut self,
    ) -> (&mut [Option<A>], &mut [Option<B>]) {
        let (ka, kb) = (TypeId::of::<A>(), TypeId::of::<B>());
        assert_ne!(ka, kb, "columns_mut::<A, B>() needs two DIFFERENT component types");

        // make sure both columns exist, so a fresh World just yields empty slices
        self.columns
            .entry(ka)
            .or_insert_with(|| Box::new(Column::<A>::new()) as Box<dyn AnyColumn>);
        self.columns
            .entry(kb)
            .or_insert_with(|| Box::new(Column::<B>::new()) as Box<dyn AnyColumn>);

        // std's disjoint-borrow API: two keys in, two independent &mut out.
        let [a, b] = self.columns.get_disjoint_mut([&ka, &kb]);
        let a = a
            .unwrap()
            .as_any_mut()
            .downcast_mut::<Column<A>>()
            .expect("column type mismatch");
        let b = b
            .unwrap()
            .as_any_mut()
            .downcast_mut::<Column<B>>()
            .expect("column type mismatch");
        (a.data.as_mut_slice(), b.data.as_mut_slice())
    }

    /// The three-column join (about 20 more lines, same shape as
    /// `columns_mut`): what a collide system needs to read transforms,
    /// colliders, and velocities together. Same proof, one more key.
    #[allow(clippy::type_complexity)] // three named slices IS the clear form
    pub fn columns_mut3<A: 'static, B: 'static, C: 'static>(
        &mut self,
    ) -> (&mut [Option<A>], &mut [Option<B>], &mut [Option<C>]) {
        let (ka, kb, kc) = (TypeId::of::<A>(), TypeId::of::<B>(), TypeId::of::<C>());
        assert_ne!(ka, kb, "columns_mut3 needs three DIFFERENT component types");
        assert_ne!(kb, kc, "columns_mut3 needs three DIFFERENT component types");
        assert_ne!(ka, kc, "columns_mut3 needs three DIFFERENT component types");

        self.columns
            .entry(ka)
            .or_insert_with(|| Box::new(Column::<A>::new()) as Box<dyn AnyColumn>);
        self.columns
            .entry(kb)
            .or_insert_with(|| Box::new(Column::<B>::new()) as Box<dyn AnyColumn>);
        self.columns
            .entry(kc)
            .or_insert_with(|| Box::new(Column::<C>::new()) as Box<dyn AnyColumn>);

        let [a, b, c] = self.columns.get_disjoint_mut([&ka, &kb, &kc]);
        let a = a
            .unwrap()
            .as_any_mut()
            .downcast_mut::<Column<A>>()
            .expect("column type mismatch");
        let b = b
            .unwrap()
            .as_any_mut()
            .downcast_mut::<Column<B>>()
            .expect("column type mismatch");
        let c = c
            .unwrap()
            .as_any_mut()
            .downcast_mut::<Column<C>>()
            .expect("column type mismatch");
        (a.data.as_mut_slice(), b.data.as_mut_slice(), c.data.as_mut_slice())
    }

    // -- resources: singletons stored by type --------------------------------

    /// Store one value per type: the input snapshot, the camera, the render
    /// queue. The exact same type-erasure trick as the columns, one level
    /// simpler because there is no per-entity indexing.
    pub fn insert_resource<T: 'static>(&mut self, value: T) {
        self.resources.insert(TypeId::of::<T>(), Box::new(value));
    }

    /// Borrow a resource. Panics with the type name if absent: a missing
    /// resource is a wiring bug in setup, not a runtime condition.
    pub fn resource<T: 'static>(&self) -> &T {
        self.try_resource::<T>()
            .unwrap_or_else(|| panic!("resource not inserted: {}", std::any::type_name::<T>()))
    }

    pub fn resource_mut<T: 'static>(&mut self) -> &mut T {
        self.resources
            .get_mut(&TypeId::of::<T>())
            .and_then(|r| r.downcast_mut::<T>())
            .unwrap_or_else(|| panic!("resource not inserted: {}", std::any::type_name::<T>()))
    }

    /// The non-panicking form, for probing.
    pub fn try_resource<T: 'static>(&self) -> Option<&T> {
        self.resources.get(&TypeId::of::<T>())?.downcast_ref::<T>()
    }

    // -- read-only iteration -------------------------------------------------

    /// Every live entity that has a `T`, as `(Entity, &T)`. The read-only
    /// walk a script view (or any inspection code) needs.
    ///
    /// Since despawn clears slots (the AnyColumn upgrade), an occupied slot
    /// IS a live entity: no free-list scan needed. Reconstructing the
    /// handle from the slot's current generation is sound for the same
    /// reason.
    pub fn iter<T: 'static>(&self) -> impl Iterator<Item = (Entity, &T)> {
        let column = self
            .columns
            .get(&TypeId::of::<T>())
            .and_then(|c| c.as_any().downcast_ref::<Column<T>>());
        column
            .map(|c| c.data.as_slice())
            .unwrap_or(&[])
            .iter()
            .enumerate()
            .filter_map(move |(i, slot)| {
                let value = slot.as_ref()?;
                let generation = *self.generations.get(i)?;
                Some((Entity { index: i as u32, generation }, value))
            })
    }
}

// ---------------------------------------------------------------------------
// Tests: prove the World headless (no GPU, no window, milliseconds)
// ---------------------------------------------------------------------------

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

    /// A "newtype": distinct type, zero overhead. Velocity is NOT a Vec3, it
    /// merely contains one; so it gets its own column in the World.
    #[derive(Clone, Copy)]
    struct Velocity(Vec3);

    /// A system is a plain function. No trait, no registration ceremony:
    /// step 8's scheduler is just a Vec of these, called in order.
    fn movement(world: &mut World, dt: f32) {
        let (xforms, vels) = world.columns_mut::<Transform, Velocity>();
        for (t, v) in xforms.iter_mut().zip(vels.iter()) {
            if let (Some(t), Some(v)) = (t, v) {
                t.translation += v.0 * dt; // 3D math, even in 2D (z stays 0)
            }
        }
    }

    #[test]
    fn spawn_insert_get() {
        let mut w = World::new();
        let e = w.spawn();
        w.insert(e, Transform::from_xy(10.0, 20.0));
        let t = w.get::<Transform>(e).expect("component present");
        assert_eq!(t.translation, Vec3::new(10.0, 20.0, 0.0));
        assert_eq!(w.len(), 1);
    }

    #[test]
    fn stale_handle_is_caught() {
        let mut w = World::new();
        let a = w.spawn();
        w.insert(a, Transform::from_xy(1.0, 1.0));
        w.despawn(a);

        assert!(!w.is_alive(a));
        assert!(w.get::<Transform>(a).is_none()); // None, never garbage

        let b = w.spawn(); // recycles slot 0 with generation 1
        assert!(w.is_alive(b));
        assert!(a != b); // same index, different generation
        assert!(w.get::<Transform>(a).is_none()); // a stays dead forever
        // The appendix exercise, passing: despawn CLEARED the slot, so the
        // recycled entity starts empty instead of inheriting a's bytes.
        // (This exact assert fails on the pre-exercise World.)
        assert!(w.get::<Transform>(b).is_none());
    }

    #[test]
    fn movement_moves_entities() {
        let mut w = World::new();
        let e = w.spawn();
        w.insert(e, Transform::from_xy(0.0, 0.0));
        w.insert(e, Velocity(Vec3::new(100.0, 50.0, 0.0)));

        let bystander = w.spawn(); // has a Transform but NO Velocity...
        w.insert(bystander, Transform::from_xy(7.0, 7.0));

        movement(&mut w, 0.5); // half a second of simulation

        let t = w.get::<Transform>(e).unwrap();
        assert!(t.translation.abs_diff_eq(Vec3::new(50.0, 25.0, 0.0), 1e-6));

        let t2 = w.get::<Transform>(bystander).unwrap();
        // ...so it did not move: the zip + if-let join needs BOTH components
        assert!(t2.translation.abs_diff_eq(Vec3::new(7.0, 7.0, 0.0), 1e-6));
    }

    #[test]
    fn remove_takes_the_value_out() {
        let mut w = World::new();
        let e = w.spawn();
        w.insert(e, Velocity(Vec3::X));
        let v = w.remove::<Velocity>(e).expect("was present");
        assert!(v.0.abs_diff_eq(Vec3::X, 1e-6));
        assert!(w.get::<Velocity>(e).is_none()); // gone after remove
    }

    #[test]
    fn resources_round_trip() {
        #[derive(Clone, Copy, PartialEq, Debug)]
        struct Gravity(f32);

        let mut w = World::new();
        assert!(w.try_resource::<Gravity>().is_none());
        w.insert_resource(Gravity(9.81));
        assert_eq!(*w.resource::<Gravity>(), Gravity(9.81));
        w.resource_mut::<Gravity>().0 = 1.62; // the moon
        assert_eq!(w.resource::<Gravity>().0, 1.62);
        w.insert_resource(Gravity(0.0)); // insert replaces
        assert_eq!(w.resource::<Gravity>().0, 0.0);
    }

    #[test]
    fn drain_empties_a_column() {
        struct PlaySound(u32);

        let mut w = World::new();
        let a = w.spawn();
        let b = w.spawn();
        w.insert(a, PlaySound(1));
        w.insert(b, PlaySound(2));

        let mut drained: Vec<u32> = w.drain::<PlaySound>().into_iter().map(|p| p.0).collect();
        drained.sort_unstable();
        assert_eq!(drained, vec![1, 2]);
        assert!(w.get::<PlaySound>(a).is_none()); // column is empty now
        assert!(w.drain::<PlaySound>().is_empty()); // second drain: nothing
    }

    #[test]
    fn column_mut_single_column_systems() {
        let mut w = World::new();
        let e = w.spawn();
        w.insert(e, Transform::from_xy(1.0, 2.0));

        // the step 8 `spin` shape: one column, iter_mut + flatten skips Nones
        for t in w.column_mut::<Transform>().iter_mut().flatten() {
            t.translation.z += 5.0;
        }
        assert_eq!(w.get::<Transform>(e).unwrap().translation.z, 5.0);
    }

    #[test]
    #[should_panic(expected = "DIFFERENT component types")]
    fn columns_mut_same_type_panics() {
        let mut w = World::new();
        let _ = w.columns_mut::<Transform, Transform>();
    }
}
