//! The settings / preferences framework.
//!
//! A unified, serialized configuration store shared by the engine, the editor,
//! and modules. Each contributor registers a typed **section** (a plain
//! `serde`-serializable struct) under a name; the framework persists every
//! section to RON and restores it, without any central code knowing the
//! sections' shapes. This is what lets:
//!
//! - **engine** preferences (render/quality defaults),
//! - **editor** preferences (theme, layout, shortcuts), and
//! - **per-module** settings (each module's own options)
//!
//! all live in one place, while a [`Project`](crate::project::Project) persists
//! the per-project subset (it stores section → RON blobs that line up exactly
//! with [`Settings::export`]/[`Settings::import`]).
//!
//! ```
//! use oxide_engine::settings::Settings;
//! use serde::{Serialize, Deserialize};
//!
//! #[derive(Serialize, Deserialize, Default, PartialEq, Debug)]
//! struct EditorPrefs { theme: String, grid: bool }
//!
//! let mut settings = Settings::new();
//! settings.register::<EditorPrefs>("editor");
//! settings.get_mut::<EditorPrefs>("editor").unwrap().theme = "dark".into();
//!
//! // Persist every section to RON, and restore it later.
//! let saved = settings.export();
//! let mut restored = Settings::new();
//! restored.register::<EditorPrefs>("editor");
//! restored.import(&saved);
//! assert_eq!(restored.get::<EditorPrefs>("editor").unwrap().theme, "dark");
//! ```

use std::any::Any;
use std::collections::BTreeMap;

use serde::de::DeserializeOwned;
use serde::Serialize;

/// The monomorphized operations for one registered section, as plain function
/// pointers (the closures capture nothing).
struct SectionOps {
    value: Box<dyn Any>,
    to_ron: fn(&dyn Any) -> Option<String>,
    from_ron: fn(&str) -> Option<Box<dyn Any>>,
    default: fn() -> Box<dyn Any>,
}

/// A registry of typed, serializable settings sections keyed by name.
#[derive(Default)]
pub struct Settings {
    sections: BTreeMap<&'static str, SectionOps>,
}

impl Settings {
    /// An empty settings store.
    pub fn new() -> Self {
        Self::default()
    }

    /// Registers section type `T` under `name`, initialized to `T::default()`.
    /// Re-registering the same name resets it to default.
    pub fn register<T>(&mut self, name: &'static str)
    where
        T: Serialize + DeserializeOwned + Default + 'static,
    {
        self.sections.insert(
            name,
            SectionOps {
                value: Box::new(T::default()),
                to_ron: |any| any.downcast_ref::<T>().and_then(|v| ron::to_string(v).ok()),
                from_ron: |text| {
                    ron::from_str::<T>(text)
                        .ok()
                        .map(|v| Box::new(v) as Box<dyn Any>)
                },
                default: || Box::new(T::default()) as Box<dyn Any>,
            },
        );
    }

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

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

    /// Borrows section `name` as `T`, or `None` if absent or the type mismatches.
    pub fn get<T: 'static>(&self, name: &str) -> Option<&T> {
        self.sections.get(name)?.value.downcast_ref::<T>()
    }

    /// Mutably borrows section `name` as `T`.
    pub fn get_mut<T: 'static>(&mut self, name: &str) -> Option<&mut T> {
        self.sections.get_mut(name)?.value.downcast_mut::<T>()
    }

    /// Replaces the value of section `name`. Returns whether it was registered
    /// (with a matching type).
    pub fn set<T: 'static>(&mut self, name: &str, value: T) -> bool {
        match self.sections.get_mut(name) {
            // Only overwrite if the registered type matches.
            Some(section) if section.value.is::<T>() => {
                section.value = Box::new(value);
                true
            }
            _ => false,
        }
    }

    /// Resets section `name` to its default. Returns whether it was registered.
    pub fn reset(&mut self, name: &str) -> bool {
        match self.sections.get_mut(name) {
            Some(section) => {
                section.value = (section.default)();
                true
            }
            None => false,
        }
    }

    /// Serializes section `name` to RON, or `None` if it is not registered.
    pub fn section_ron(&self, name: &str) -> Option<String> {
        let section = self.sections.get(name)?;
        (section.to_ron)(section.value.as_ref())
    }

    /// Loads section `name` from a RON blob, replacing its value. Returns `false`
    /// if the section is not registered or the text fails to parse.
    pub fn load_section(&mut self, name: &str, ron: &str) -> bool {
        match self.sections.get_mut(name) {
            Some(section) => match (section.from_ron)(ron) {
                Some(value) => {
                    section.value = value;
                    true
                }
                None => false,
            },
            None => false,
        }
    }

    /// Serializes every section to a `name → RON` map (the format a
    /// [`Project`](crate::project::Project) stores).
    pub fn export(&self) -> BTreeMap<String, String> {
        self.sections
            .iter()
            .filter_map(|(name, section)| {
                (section.to_ron)(section.value.as_ref()).map(|ron| (name.to_string(), ron))
            })
            .collect()
    }

    /// Loads every matching, registered section from a `name → RON` map.
    /// Unknown sections are ignored (a module may be disabled); malformed
    /// sections are skipped, leaving their current value.
    pub fn import(&mut self, map: &BTreeMap<String, String>) {
        for (name, ron) in map {
            self.load_section(name, ron);
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde::{Deserialize, Serialize};

    #[derive(Serialize, Deserialize, Default, PartialEq, Debug)]
    struct EditorPrefs {
        theme: String,
        grid: bool,
    }

    #[derive(Serialize, Deserialize, PartialEq, Debug)]
    struct Render {
        shadows: bool,
        msaa: u32,
    }
    impl Default for Render {
        fn default() -> Self {
            Self {
                shadows: true,
                msaa: 4,
            }
        }
    }

    fn settings() -> Settings {
        let mut s = Settings::new();
        s.register::<EditorPrefs>("editor");
        s.register::<Render>("render");
        s
    }

    #[test]
    fn defaults_and_typed_access() {
        let mut s = settings();
        assert_eq!(s.names().collect::<Vec<_>>(), vec!["editor", "render"]);
        assert!(s.get::<Render>("render").unwrap().shadows);
        s.get_mut::<EditorPrefs>("editor").unwrap().theme = "dark".into();
        assert_eq!(s.get::<EditorPrefs>("editor").unwrap().theme, "dark");
        // Wrong type → None.
        assert!(s.get::<Render>("editor").is_none());
    }

    #[test]
    fn set_and_reset() {
        let mut s = settings();
        assert!(s.set(
            "render",
            Render {
                shadows: false,
                msaa: 8
            }
        ));
        assert_eq!(s.get::<Render>("render").unwrap().msaa, 8);
        // Setting an unregistered section fails.
        assert!(!s.set("missing", 5u32));
        // Reset returns to default.
        assert!(s.reset("render"));
        assert_eq!(s.get::<Render>("render").unwrap(), &Render::default());
    }

    #[test]
    fn export_import_round_trips() {
        let mut s = settings();
        s.get_mut::<EditorPrefs>("editor").unwrap().theme = "light".into();
        s.get_mut::<EditorPrefs>("editor").unwrap().grid = true;
        s.set(
            "render",
            Render {
                shadows: false,
                msaa: 2,
            },
        );
        let saved = s.export();

        // A fresh store with the same sections restores the saved values.
        let mut restored = settings();
        restored.import(&saved);
        assert_eq!(
            restored.get::<EditorPrefs>("editor").unwrap(),
            &EditorPrefs {
                theme: "light".into(),
                grid: true
            }
        );
        assert_eq!(restored.get::<Render>("render").unwrap().msaa, 2);
    }

    #[test]
    fn import_ignores_unknown_and_malformed() {
        let mut s = settings();
        let mut map = BTreeMap::new();
        map.insert("editor".to_string(), "(theme:\"x\",grid:true)".to_string());
        map.insert("disabled_module".to_string(), "(whatever:1)".to_string());
        map.insert("render".to_string(), "not valid ron".to_string());
        s.import(&map);
        // Known + valid applied.
        assert_eq!(s.get::<EditorPrefs>("editor").unwrap().theme, "x");
        // Malformed left the section at its default (unchanged).
        assert_eq!(s.get::<Render>("render").unwrap(), &Render::default());
        // Unknown silently ignored.
        assert!(!s.is_registered("disabled_module"));
    }
}
