//! The application core: an [`App`] assembled by registering [`Module`]s.
//!
//! Stage 5 ties the core framework together. An `App` owns the shared engine
//! state — the [`Scene`], the [`AssetServer`], the [`TypeRegistry`], the
//! [`LayerRegistry`], frame [`Time`], and arbitrary user resources — plus a
//! [`Schedule`] of systems. Functionality is added by **modules**: each
//! [`Module::build`] registers systems, component types, asset loaders, and
//! resources, so the engine is composed rather than hard-wired and an exported
//! game compiles in only the modules it uses.
//!
//! ```
//! use oxide_engine::app::{App, DefaultModules};
//!
//! let mut app = App::new();
//! app.add_modules(DefaultModules);
//! app.update(1.0 / 60.0); // advance one frame
//! ```

mod module;
mod schedule;

pub use module::{CoreModule, DefaultModules, Module, RenderModule};
pub use schedule::Schedule;

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

use schedule::{run_phase, SystemEntry, Systems};

use crate::asset::{AssetLoader, AssetServer};
use crate::layer::LayerRegistry;
use crate::reflect::TypeRegistry;
use crate::scene::Scene;

/// The default fixed-timestep duration (60 Hz) for [`Schedule::FixedUpdate`].
pub const DEFAULT_FIXED_TIMESTEP: f32 = 1.0 / 60.0;

/// An upper bound on fixed steps per frame, so a long stall (e.g. a breakpoint)
/// cannot trigger an unbounded catch-up "spiral of death".
const MAX_FIXED_STEPS_PER_FRAME: u32 = 8;

/// Per-frame timing, refreshed by [`App::update`] and readable by systems.
#[derive(Debug, Clone, Copy)]
pub struct Time {
    /// Seconds elapsed since the previous frame.
    pub delta: f32,
    /// Seconds elapsed since the app started.
    pub elapsed: f32,
    /// The fixed-timestep duration used by [`Schedule::FixedUpdate`].
    pub fixed_delta: f32,
    /// Frames advanced so far.
    pub frame: u64,
}

impl Default for Time {
    fn default() -> Self {
        Self {
            delta: 0.0,
            elapsed: 0.0,
            fixed_delta: DEFAULT_FIXED_TIMESTEP,
            frame: 0,
        }
    }
}

/// The application core. See the [module docs](self).
pub struct App {
    /// The active scene graph.
    pub scene: Scene,
    /// The shared asset server (built-in loaders registered).
    pub assets: AssetServer,
    /// The reflection/type registry for dual-editable components.
    pub types: TypeRegistry,
    /// The project's named layers.
    pub layers: LayerRegistry,
    /// Per-frame timing.
    pub time: Time,

    resources: HashMap<TypeId, Box<dyn Any>>,
    systems: Systems,

    /// Registered modules → enabled flag.
    modules: BTreeMap<&'static str, bool>,
    /// The module currently being built, so registrations can be attributed.
    current_module: Option<&'static str>,
    /// Per-module bookkeeping for clean removal.
    module_types: HashMap<&'static str, Vec<&'static str>>,
    module_loaders: HashMap<&'static str, Vec<String>>,
    module_resources: HashMap<&'static str, Vec<TypeId>>,

    fixed_accumulator: f32,
}

impl App {
    /// A new app with empty core state and no modules. The [`AssetServer`] comes
    /// with the engine's built-in loaders already registered.
    pub fn new() -> Self {
        Self {
            scene: Scene::new(),
            assets: AssetServer::new(),
            types: TypeRegistry::new(),
            layers: LayerRegistry::new(),
            time: Time::default(),
            resources: HashMap::new(),
            systems: Systems::default(),
            modules: BTreeMap::new(),
            current_module: None,
            module_types: HashMap::new(),
            module_loaders: HashMap::new(),
            module_resources: HashMap::new(),
            fixed_accumulator: 0.0,
        }
    }

    // --- Modules -----------------------------------------------------------

    /// Adds a module, running its [`Module::build`] and attributing everything
    /// it registers to it.
    ///
    /// # Panics
    /// Panics if a module with the same [`name`](Module::name) is already added.
    pub fn add_module<M: Module>(&mut self, module: M) -> &mut Self {
        let name = module.name();
        assert!(
            !self.modules.contains_key(name),
            "module '{name}' is already added"
        );
        self.modules.insert(name, true);
        let previous = self.current_module.replace(name);
        module.build(self);
        self.current_module = previous;
        self
    }

    /// Adds a bundle of modules (e.g. [`DefaultModules`]).
    pub fn add_modules<B: ModuleBundle>(&mut self, bundle: B) -> &mut Self {
        bundle.add_to(self);
        self
    }

    /// Whether a module is registered.
    pub fn has_module(&self, name: &str) -> bool {
        self.modules.contains_key(name)
    }

    /// The registered module names, sorted.
    pub fn modules(&self) -> impl Iterator<Item = &'static str> + '_ {
        self.modules.keys().copied()
    }

    /// Whether a registered module is enabled. Unknown modules report `false`.
    pub fn is_module_enabled(&self, name: &str) -> bool {
        self.modules.get(name).copied().unwrap_or(false)
    }

    /// Enables or disables a module's systems without removing them. Disabled
    /// modules' systems are skipped each frame. Returns whether the module exists.
    pub fn set_module_enabled(&mut self, name: &str, enabled: bool) -> bool {
        match self.modules.get_mut(name) {
            Some(flag) => {
                *flag = enabled;
                true
            }
            None => false,
        }
    }

    /// Removes a module and everything it contributed — systems, registered
    /// component types, asset loaders, and resources — leaving no dangling
    /// references. Returns whether the module existed.
    pub fn remove_module(&mut self, name: &str) -> bool {
        if self.modules.remove(name).is_none() {
            return false;
        }
        self.systems.remove_module(name);
        for type_name in self.module_types.remove(name).unwrap_or_default() {
            self.types.unregister(type_name);
        }
        for ext in self.module_loaders.remove(name).unwrap_or_default() {
            self.assets.unregister_loader(&ext);
        }
        for type_id in self.module_resources.remove(name).unwrap_or_default() {
            self.resources.remove(&type_id);
        }
        true
    }

    /// Whether a system contributed by `module` should run this frame: systems
    /// with no owning module always run; module-owned systems run only while
    /// their module is enabled.
    pub(crate) fn is_system_enabled(&self, module: Option<&'static str>) -> bool {
        match module {
            None => true,
            Some(name) => self.is_module_enabled(name),
        }
    }

    // --- Registration (attributed to the current module) -------------------

    /// Adds a system to a schedule phase. Systems run in phase order, then in
    /// registration order within a phase.
    pub fn add_system(
        &mut self,
        phase: Schedule,
        system: impl FnMut(&mut App) + 'static,
    ) -> &mut Self {
        self.systems.push(
            phase,
            SystemEntry {
                module: self.current_module,
                run: Box::new(system),
            },
        );
        self
    }

    /// Registers a reflected component type under `name` (see [`TypeRegistry`]).
    pub fn register_type<T>(&mut self, name: &'static str) -> &mut Self
    where
        T: hecs::Component + serde::Serialize + serde::de::DeserializeOwned,
    {
        self.types.register::<T>(name);
        if let Some(module) = self.current_module {
            self.module_types.entry(module).or_default().push(name);
        }
        self
    }

    /// Registers an asset loader (see [`AssetServer::register_loader`]).
    pub fn add_loader<L: AssetLoader>(&mut self, loader: L) -> &mut Self {
        if let Some(module) = self.current_module {
            let exts = loader.extensions().iter().map(|e| e.to_lowercase());
            self.module_loaders.entry(module).or_default().extend(exts);
        }
        self.assets.register_loader(loader);
        self
    }

    // --- Resources ---------------------------------------------------------

    /// Inserts (or replaces) a shared resource of type `T`.
    pub fn insert_resource<T: 'static>(&mut self, value: T) -> &mut Self {
        let id = TypeId::of::<T>();
        if let Some(module) = self.current_module {
            self.module_resources.entry(module).or_default().push(id);
        }
        self.resources.insert(id, Box::new(value));
        self
    }

    /// Borrows a resource of type `T`, or `None` if absent.
    pub fn get_resource<T: 'static>(&self) -> Option<&T> {
        self.resources
            .get(&TypeId::of::<T>())
            .and_then(|b| b.downcast_ref::<T>())
    }

    /// Mutably borrows a resource of type `T`, or `None` if absent.
    pub fn get_resource_mut<T: 'static>(&mut self) -> Option<&mut T> {
        self.resources
            .get_mut(&TypeId::of::<T>())
            .and_then(|b| b.downcast_mut::<T>())
    }

    /// Removes and returns the resource of type `T`, or `None` if absent.
    ///
    /// Lets a system take exclusive ownership of a resource for the duration of
    /// a call — e.g. the physics step takes the `PhysicsWorld` out so it can
    /// borrow the [`Scene`] mutably at the same time — then re-inserts it.
    pub fn remove_resource<T: 'static>(&mut self) -> Option<T> {
        self.resources
            .remove(&TypeId::of::<T>())
            .and_then(|b| b.downcast::<T>().ok())
            .map(|b| *b)
    }

    /// Whether a resource of type `T` is present.
    pub fn has_resource<T: 'static>(&self) -> bool {
        self.resources.contains_key(&TypeId::of::<T>())
    }

    // --- Running -----------------------------------------------------------

    /// Sets the fixed-timestep duration used by [`Schedule::FixedUpdate`].
    pub fn set_fixed_timestep(&mut self, seconds: f32) -> &mut Self {
        assert!(seconds > 0.0, "fixed timestep must be positive");
        self.time.fixed_delta = seconds;
        self
    }

    /// The number of systems registered across all phases.
    pub fn system_count(&self) -> usize {
        self.systems.total()
    }

    /// Advances one frame by `delta` seconds: runs the per-frame phases once and
    /// [`FixedUpdate`](Schedule::FixedUpdate) as many whole fixed steps as the
    /// accumulated time allows (capped to avoid a catch-up spiral).
    pub fn update(&mut self, delta: f32) {
        self.time.delta = delta;
        self.time.elapsed += delta;
        self.time.frame += 1;

        // How many fixed steps to run this frame.
        self.fixed_accumulator += delta;
        let mut steps = (self.fixed_accumulator / self.time.fixed_delta) as u32;
        if steps > MAX_FIXED_STEPS_PER_FRAME {
            steps = MAX_FIXED_STEPS_PER_FRAME;
            self.fixed_accumulator = 0.0;
        } else {
            self.fixed_accumulator -= steps as f32 * self.time.fixed_delta;
        }

        self.run_frame(steps);
    }

    /// Advances **exactly one fixed timestep**: bumps frame time by
    /// [`fixed_delta`](Time::fixed_delta) and runs the per-frame phases once with
    /// a single [`FixedUpdate`](Schedule::FixedUpdate), bypassing the
    /// accumulator. This is the editor play-mode **Step** primitive — single-step
    /// the simulation while paused — and yields one deterministic tick.
    pub fn step(&mut self) {
        let dt = self.time.fixed_delta;
        self.time.delta = dt;
        self.time.elapsed += dt;
        self.time.frame += 1;
        self.run_frame(1);
    }

    /// Runs the per-frame phases once with `fixed_steps` runs of
    /// [`FixedUpdate`](Schedule::FixedUpdate). The systems are moved out first so
    /// each gets exclusive `&mut App`, then anything registered mid-frame is
    /// folded back. Shared by [`update`](Self::update) and [`step`](Self::step).
    fn run_frame(&mut self, fixed_steps: u32) {
        let mut systems = std::mem::take(&mut self.systems);
        run_phase(&mut systems, self, Schedule::First);
        run_phase(&mut systems, self, Schedule::Input);
        run_phase(&mut systems, self, Schedule::PreUpdate);
        for _ in 0..fixed_steps {
            run_phase(&mut systems, self, Schedule::FixedUpdate);
        }
        run_phase(&mut systems, self, Schedule::Update);
        run_phase(&mut systems, self, Schedule::PostUpdate);
        run_phase(&mut systems, self, Schedule::Render);
        run_phase(&mut systems, self, Schedule::Last);

        // Fold back anything registered during the frame, then restore.
        systems.merge(std::mem::take(&mut self.systems));
        self.systems = systems;
    }
}

impl Default for App {
    fn default() -> Self {
        Self::new()
    }
}

/// A group of modules added together. Implemented for [`DefaultModules`] and for
/// tuples, so `app.add_modules((ModuleA, ModuleB))` works.
pub trait ModuleBundle {
    /// Adds every module in the bundle to `app`.
    fn add_to(self, app: &mut App);
}

impl<A: Module> ModuleBundle for (A,) {
    fn add_to(self, app: &mut App) {
        app.add_module(self.0);
    }
}

impl<A: Module, B: Module> ModuleBundle for (A, B) {
    fn add_to(self, app: &mut App) {
        app.add_module(self.0);
        app.add_module(self.1);
    }
}

impl<A: Module, B: Module, C: Module> ModuleBundle for (A, B, C) {
    fn add_to(self, app: &mut App) {
        app.add_module(self.0);
        app.add_module(self.1);
        app.add_module(self.2);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::math::{Transform, Vec3};
    use std::cell::Cell;
    use std::rc::Rc;

    #[test]
    fn empty_app_updates_and_advances_time() {
        let mut app = App::new();
        assert_eq!(app.time.frame, 0);
        app.update(0.5);
        assert_eq!(app.time.frame, 1);
        assert!((app.time.elapsed - 0.5).abs() < 1e-6);
        assert!((app.time.delta - 0.5).abs() < 1e-6);
    }

    #[test]
    fn systems_run_in_phase_then_registration_order() {
        let log = Rc::new(std::cell::RefCell::new(Vec::new()));
        let mut app = App::new();
        let l = log.clone();
        app.add_system(Schedule::Update, move |_| l.borrow_mut().push("update-1"));
        let l = log.clone();
        app.add_system(Schedule::Update, move |_| l.borrow_mut().push("update-2"));
        let l = log.clone();
        app.add_system(Schedule::First, move |_| l.borrow_mut().push("first"));
        app.update(0.0);
        assert_eq!(*log.borrow(), vec!["first", "update-1", "update-2"]);
    }

    #[test]
    fn fixed_update_runs_by_accumulated_time() {
        let count = Rc::new(Cell::new(0u32));
        let mut app = App::new();
        app.set_fixed_timestep(0.1);
        let c = count.clone();
        app.add_system(Schedule::FixedUpdate, move |_| c.set(c.get() + 1));

        app.update(0.25); // 0.25 / 0.1 = 2 whole steps, ~0.05 left over
        assert_eq!(count.get(), 2);
        // 0.05 carried + 0.06 = 0.11 -> 1 more step (kept off the exact float
        // boundary so the result is robust to f32 rounding).
        app.update(0.06);
        assert_eq!(count.get(), 3);
    }

    #[test]
    fn fixed_update_is_capped_against_spiral() {
        let count = Rc::new(Cell::new(0u32));
        let mut app = App::new();
        app.set_fixed_timestep(0.001);
        let c = count.clone();
        app.add_system(Schedule::FixedUpdate, move |_| c.set(c.get() + 1));
        app.update(10.0); // would be 10000 steps; capped
        assert_eq!(count.get(), MAX_FIXED_STEPS_PER_FRAME);
    }

    #[test]
    fn step_runs_one_fixed_tick_and_the_per_frame_phases_once() {
        let fixed = Rc::new(Cell::new(0u32));
        let update = Rc::new(Cell::new(0u32));
        let mut app = App::new();
        app.set_fixed_timestep(0.1);
        let f = fixed.clone();
        app.add_system(Schedule::FixedUpdate, move |_| f.set(f.get() + 1));
        let u = update.clone();
        app.add_system(Schedule::Update, move |_| u.set(u.get() + 1));

        app.step();
        // Exactly one fixed step and one Update, regardless of accumulator.
        assert_eq!(fixed.get(), 1);
        assert_eq!(update.get(), 1);
        assert_eq!(app.time.frame, 1);
        assert!((app.time.elapsed - 0.1).abs() < 1e-6);
        assert!((app.time.delta - 0.1).abs() < 1e-6);

        // A second step advances exactly one more, deterministically.
        app.step();
        assert_eq!(fixed.get(), 2);
        assert_eq!(update.get(), 2);
    }

    #[test]
    fn resources_round_trip() {
        let mut app = App::new();
        app.insert_resource(42u32);
        assert_eq!(app.get_resource::<u32>(), Some(&42));
        *app.get_resource_mut::<u32>().unwrap() += 1;
        assert_eq!(app.get_resource::<u32>(), Some(&43));
        assert!(app.get_resource::<String>().is_none());

        // remove_resource takes ownership and clears the slot.
        assert_eq!(app.remove_resource::<u32>(), Some(43));
        assert!(!app.has_resource::<u32>());
        assert_eq!(app.remove_resource::<u32>(), None);
    }

    #[test]
    fn a_system_can_mutate_the_scene_each_frame() {
        let mut app = App::new();
        app.scene.spawn("a", Transform::IDENTITY);
        // Each Update, nudge every entity's transform.
        app.add_system(Schedule::Update, |app| {
            let entities: Vec<_> = app.scene.entities().collect();
            for e in entities {
                if let Some(mut t) = app.scene.get_mut::<Transform>(e) {
                    t.translation += Vec3::X;
                }
            }
        });
        app.update(0.0);
        app.update(0.0);
        let e = app.scene.entities().next().unwrap();
        assert!((app.scene.local_transform(e).unwrap().translation.x - 2.0).abs() < 1e-6);
    }
}
