//! Per-widget typed value — the state interactive widgets carry.
//!
//! Stage 8's UI is data-driven: a slider knows its current position, a
//! text input knows the string the user has typed, a checkbox knows
//! whether it's checked. Rather than encoding "which kind of state does
//! this widget have" inside the layout enum, every [`Widget`](super::widget::Widget)
//! has an optional `value: Option<WidgetValue>` orthogonal to its `kind`.
//! That keeps the layout algorithm simple (it doesn't care about state)
//! and lets the same `Leaf` form a button (no value) or a checkbox
//! (`Bool` value).
//!
//! # Data binding model
//!
//! Stage-8 piece-6 uses the **immediate-mode** pattern (the same as
//! `egui` and Bevy UI): the widget tree is the source of truth for the
//! frame. Each frame the host:
//!
//! 1. Pulls latest game data into the matching widget values (e.g.,
//!    `root.set_value("volume", WidgetValue::Float(audio.master_volume as f64))`).
//! 2. Runs the [`Router`](super::routing::Router).
//! 3. Reads back any widget values that interactive widgets may have
//!    changed, and pushes them into game data
//!    (`audio.master_volume = root.value("volume")?.as_float()? as f32`).
//!
//! No callback storage, no `Rc<RefCell<...>>` for state, no lifetime
//! gymnastics — exactly what a game's main loop wants.

use serde::{Deserialize, Serialize};

/// A typed value carried on an interactive widget — the slider's
/// position, a checkbox's check, a text-input's string.
///
/// Variants are intentionally minimal; richer types (Color, Vec2, etc.)
/// can be added as widget needs grow. RON round-trips so a UI document
/// can ship default values inline.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum WidgetValue {
    Bool(bool),
    Int(i64),
    Float(f64),
    Text(String),
}

impl WidgetValue {
    /// Borrow as a bool if this is a [`Bool`](Self::Bool).
    pub fn as_bool(&self) -> Option<bool> {
        match self {
            Self::Bool(v) => Some(*v),
            _ => None,
        }
    }

    /// Borrow as an i64 if this is an [`Int`](Self::Int).
    pub fn as_int(&self) -> Option<i64> {
        match self {
            Self::Int(v) => Some(*v),
            _ => None,
        }
    }

    /// Borrow as an f64 if this is a [`Float`](Self::Float).
    pub fn as_float(&self) -> Option<f64> {
        match self {
            Self::Float(v) => Some(*v),
            _ => None,
        }
    }

    /// Borrow as a string slice if this is a [`Text`](Self::Text).
    pub fn as_text(&self) -> Option<&str> {
        match self {
            Self::Text(s) => Some(s.as_str()),
            _ => None,
        }
    }
}

impl From<bool> for WidgetValue {
    fn from(v: bool) -> Self {
        Self::Bool(v)
    }
}

impl From<i64> for WidgetValue {
    fn from(v: i64) -> Self {
        Self::Int(v)
    }
}

impl From<i32> for WidgetValue {
    fn from(v: i32) -> Self {
        Self::Int(v as i64)
    }
}

impl From<f64> for WidgetValue {
    fn from(v: f64) -> Self {
        Self::Float(v)
    }
}

impl From<f32> for WidgetValue {
    fn from(v: f32) -> Self {
        Self::Float(v as f64)
    }
}

impl From<String> for WidgetValue {
    fn from(v: String) -> Self {
        Self::Text(v)
    }
}

impl From<&str> for WidgetValue {
    fn from(v: &str) -> Self {
        Self::Text(v.to_owned())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn as_accessors_match_variants() {
        assert_eq!(WidgetValue::Bool(true).as_bool(), Some(true));
        assert_eq!(WidgetValue::Bool(true).as_int(), None);
        assert_eq!(WidgetValue::Int(42).as_int(), Some(42));
        assert_eq!(WidgetValue::Float(1.5).as_float(), Some(1.5));
        assert_eq!(WidgetValue::Text("hi".into()).as_text(), Some("hi"));
    }

    #[test]
    fn primitive_conversions() {
        let v: WidgetValue = true.into();
        assert_eq!(v, WidgetValue::Bool(true));
        let v: WidgetValue = 7_i32.into();
        assert_eq!(v, WidgetValue::Int(7));
        let v: WidgetValue = 1.5_f32.into();
        assert!((v.as_float().unwrap() - 1.5_f64).abs() < 1e-5);
        let v: WidgetValue = "label".into();
        assert_eq!(v.as_text(), Some("label"));
    }

    #[test]
    fn ron_round_trips_each_variant() {
        for v in [
            WidgetValue::Bool(true),
            WidgetValue::Int(-99),
            WidgetValue::Float(0.42),
            WidgetValue::Text("hello".into()),
        ] {
            let text = ron::ser::to_string(&v).unwrap();
            let decoded: WidgetValue = ron::de::from_str(&text).unwrap();
            assert_eq!(v, decoded);
        }
    }
}
