/// Responsive<T> — a value that varies by breakpoint.
///
/// The base value is always required (mobile-first). `sm`, `md`, `lg`, `xl`
/// are all optional — if not set, the nearest smaller value is used.
///
/// You generally don't need Responsive<T> for layout — VStack and Grid handle
/// reflow automatically. Use Responsive<T> when you need to vary a non-layout
/// value (e.g. font size, column count, visibility) at specific breakpoints.

use crate::breakpoint::Breakpoint;

/// A value that varies by viewport breakpoint.
///
/// Follows mobile-first cascade: base < sm < md < lg < xl.
/// If a breakpoint value is not set, it inherits from the next smaller one.
#[derive(Debug, Clone, PartialEq)]
pub struct Responsive<T: Clone> {
    /// Mobile-first base value. Always required.
    pub base: T,
    /// 640dp+. If None, uses `base`.
    pub sm: Option<T>,
    /// 768dp+. If None, uses `sm` or `base`.
    pub md: Option<T>,
    /// 1024dp+. If None, uses `md`, `sm`, or `base`.
    pub lg: Option<T>,
    /// 1280dp+. If None, uses `lg`, `md`, `sm`, or `base`.
    pub xl: Option<T>,
}

impl<T: Clone> Responsive<T> {
    /// Create a responsive value with only the base (same on all screen sizes).
    pub fn fixed(value: T) -> Self {
        Self {
            base: value,
            sm: None,
            md: None,
            lg: None,
            xl: None,
        }
    }

    /// Create a fully-specified responsive value.
    pub fn new(
        base: T,
        sm: Option<T>,
        md: Option<T>,
        lg: Option<T>,
        xl: Option<T>,
    ) -> Self {
        Self { base, sm, md, lg, xl }
    }

    /// Resolve to the most-specific value that applies at the given breakpoint.
    ///
    /// Cascades downward: xl → lg → md → sm → base.
    pub fn resolve(&self, breakpoint: Breakpoint) -> &T {
        match breakpoint {
            Breakpoint::Xl => {
                self.xl.as_ref()
                    .or(self.lg.as_ref())
                    .or(self.md.as_ref())
                    .or(self.sm.as_ref())
                    .unwrap_or(&self.base)
            }
            Breakpoint::Lg => {
                self.lg.as_ref()
                    .or(self.md.as_ref())
                    .or(self.sm.as_ref())
                    .unwrap_or(&self.base)
            }
            Breakpoint::Md => {
                self.md.as_ref()
                    .or(self.sm.as_ref())
                    .unwrap_or(&self.base)
            }
            Breakpoint::Sm => {
                self.sm.as_ref().unwrap_or(&self.base)
            }
            Breakpoint::Base => &self.base,
        }
    }

    /// Set the sm value (builder pattern).
    pub fn sm(mut self, value: T) -> Self {
        self.sm = Some(value);
        self
    }

    /// Set the md value.
    pub fn md(mut self, value: T) -> Self {
        self.md = Some(value);
        self
    }

    /// Set the lg value.
    pub fn lg(mut self, value: T) -> Self {
        self.lg = Some(value);
        self
    }

    /// Set the xl value.
    pub fn xl(mut self, value: T) -> Self {
        self.xl = Some(value);
        self
    }
}

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

    #[test]
    fn fixed_always_returns_base() {
        let r = Responsive::fixed(42u32);
        assert_eq!(*r.resolve(Breakpoint::Base), 42);
        assert_eq!(*r.resolve(Breakpoint::Xl), 42);
    }

    #[test]
    fn resolves_most_specific() {
        let r = Responsive::fixed(1u32).sm(2).md(3).lg(4).xl(5);
        assert_eq!(*r.resolve(Breakpoint::Base), 1);
        assert_eq!(*r.resolve(Breakpoint::Sm), 2);
        assert_eq!(*r.resolve(Breakpoint::Md), 3);
        assert_eq!(*r.resolve(Breakpoint::Lg), 4);
        assert_eq!(*r.resolve(Breakpoint::Xl), 5);
    }

    #[test]
    fn cascades_down_when_specific_missing() {
        let r = Responsive::fixed(1u32).md(3);
        // sm not set → falls back to base
        assert_eq!(*r.resolve(Breakpoint::Sm), 1);
        // lg not set → falls back to md
        assert_eq!(*r.resolve(Breakpoint::Lg), 3);
        // xl not set → falls back to md (lg not set either)
        assert_eq!(*r.resolve(Breakpoint::Xl), 3);
    }

    #[test]
    fn cascade_xl_to_lg_to_sm_to_base() {
        let r = Responsive::fixed("base").sm("sm");
        assert_eq!(*r.resolve(Breakpoint::Md), "sm");
        assert_eq!(*r.resolve(Breakpoint::Lg), "sm");
        assert_eq!(*r.resolve(Breakpoint::Xl), "sm");
    }

    #[test]
    fn responsive_with_strings() {
        let r = Responsive::fixed("mobile").lg("desktop");
        assert_eq!(*r.resolve(Breakpoint::Base), "mobile");
        assert_eq!(*r.resolve(Breakpoint::Lg), "desktop");
    }
}
