//! Widget tree — the data structure laid out by [`super::layout`].
//!
//! Stage 8 splits widgets cleanly into **what** (the [`WidgetKind`]) and
//! **how** (the [`LayoutStyle`] held on every node). The kind decides whether
//! a node has children and how they're arranged; the style is the same fields
//! on every widget so the layout algorithm has one place to look.
//!
//! Piece 1 ships only what the layout algorithm needs: a [`Leaf`](WidgetKind::Leaf)
//! placeholder with an intrinsic size, and three container kinds — [`Stack`]
//! (row/column), [`Grid`], and [`AnchorGroup`]. Interactive widgets (button,
//! checkbox, slider, text input, …) are layered on top in later pieces by
//! decorating leaves with kind-specific style/state; they all participate in
//! the same layout pass without the algorithm having to know about them.
//!
//! # Building a tree
//!
//! ```
//! use glam::Vec2;
//! use oxide_engine::ui::{Insets, LayoutStyle, Sizing, Widget};
//!
//! let panel = Widget::row()
//!     .with_id("toolbar")
//!     .with_style(LayoutStyle {
//!         width: Sizing::Grow(1.0),
//!         height: Sizing::Fixed(32.0),
//!         padding: Insets::all(4.0),
//!         ..Default::default()
//!     })
//!     .with_child(Widget::leaf(Vec2::new(80.0, 24.0)).with_id("file"))
//!     .with_child(Widget::leaf(Vec2::new(80.0, 24.0)).with_id("edit"));
//! assert_eq!(panel.children().len(), 2);
//! ```

use glam::Vec2;
use serde::{Deserialize, Serialize};

use super::style::LayoutStyle;
use super::value::WidgetValue;
use super::visual::VisualStyle;

/// Stable identifier for a widget — used to look up its laid-out rect in a
/// [`LayoutTree`](super::layout::LayoutTree) and (in later pieces) to wire up
/// input routing and data binding.
///
/// Stored as `String` so UI documents can ship author-facing names (`"play"`,
/// `"volume-slider"`) straight through RON. The empty id (`""`) is the default
/// and means "anonymous"; multiple anonymous widgets are allowed and lookups
/// by empty id are rejected by [`LayoutTree::find`](super::layout::LayoutTree::find).
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct WidgetId(pub String);

impl WidgetId {
    /// `true` if the id string is empty.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Borrow the underlying string.
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl From<&str> for WidgetId {
    fn from(s: &str) -> Self {
        Self(s.to_owned())
    }
}

/// A path from a root [`Widget`] to one of its descendants: the sequence of
/// child indices to follow from the root. The **empty** path denotes the root
/// itself.
///
/// Unlike [`WidgetId`] (optional, author-facing, possibly absent or duplicated)
/// a path addresses *exactly one* node positionally, so it is what the editor's
/// UI canvas uses to target structural edits — insert, remove, move — and to
/// record them on the undo stack. Paths are only valid against the tree they
/// were derived from; an edit that changes sibling order invalidates the paths
/// after it (the move helper accounts for this itself).
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct WidgetPath(pub Vec<usize>);

impl WidgetPath {
    /// The root path (addresses the tree's root widget).
    pub fn root() -> Self {
        Self(Vec::new())
    }

    /// Whether this path addresses the root (is empty).
    pub fn is_root(&self) -> bool {
        self.0.is_empty()
    }

    /// Depth from the root (number of indices).
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Whether the path is empty — alias of [`is_root`](Self::is_root), provided
    /// for the clippy `len`/`is_empty` pairing.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// A child path one level deeper, selecting child `index`.
    pub fn child(&self, index: usize) -> Self {
        let mut v = self.0.clone();
        v.push(index);
        Self(v)
    }

    /// Splits into `(parent_path, last_index)`, or `None` for the root.
    pub fn split_last(&self) -> Option<(WidgetPath, usize)> {
        let (last, rest) = self.0.split_last()?;
        Some((WidgetPath(rest.to_vec()), *last))
    }

    /// Whether `self` is `other` or lies underneath it (prefix test). Used to
    /// reject moving a subtree into its own descendant.
    pub fn starts_with(&self, other: &WidgetPath) -> bool {
        self.0.starts_with(&other.0)
    }
}

impl From<String> for WidgetId {
    fn from(s: String) -> Self {
        Self(s)
    }
}

/// A widget tree node — id, layout style, optional visual style + theme
/// reference, and a kind that decides what children it holds.
///
/// `style` (Stage-8 piece 1) controls layout — where the widget is.
/// `visual` (piece 2) carries per-instance visual overrides — what the
/// widget looks like — and `theme_style` opts into a named entry in the
/// project's [`Theme`](super::theme::Theme). Both default to empty so a
/// piece-1 UI document still parses unchanged.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Widget {
    #[serde(default, skip_serializing_if = "WidgetId::is_empty")]
    pub id: WidgetId,
    #[serde(default)]
    pub style: LayoutStyle,
    #[serde(default, skip_serializing_if = "VisualStyle::is_empty")]
    pub visual: VisualStyle,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub theme_style: Option<String>,
    /// Text content shaped inside this widget's `content_rect`. Orthogonal
    /// to `kind`: a button is a `Leaf` with `text` + `visual.background`; a
    /// label is a `Leaf` with `text` only. Renderers shape this string
    /// against the resolved [`VisualStyle::font`] and [`VisualStyle::font_size`].
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub text: Option<String>,
    /// Per-widget typed state — `Bool` for a checkbox, `Float` for a
    /// slider, `Text` for a text input. Orthogonal to `kind`; absent
    /// means "no state". See [`super::value::WidgetValue`] and the
    /// piece-6 [`Widget::value`](Self::value) / [`set_value`](Self::set_value)
    /// helpers.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub value: Option<WidgetValue>,
    pub kind: WidgetKind,
}

/// What a widget *is* — leaf or one of three container layout modes.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum WidgetKind {
    /// A childless node with an intrinsic logical size. Real interactive
    /// widgets (label, button, image) layer on top of this in later pieces.
    Leaf { intrinsic: Vec2 },
    /// Row or column container.
    Stack(Stack),
    /// Equal-cell grid container.
    Grid(Grid),
    /// Container that positions each child via the child's own
    /// [`Anchor`](super::style::Anchor).
    Anchor(AnchorGroup),
}

impl Default for WidgetKind {
    fn default() -> Self {
        Self::Leaf {
            intrinsic: Vec2::ZERO,
        }
    }
}

/// Stack container — arranges children along a main axis.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Stack {
    pub direction: StackDirection,
    /// Logical-pixel gap between adjacent children.
    #[serde(default)]
    pub gap: f32,
    /// How leftover space on the main axis is distributed *after* children
    /// have been sized. Ignored when any child uses [`Sizing::Grow`](super::style::Sizing::Grow),
    /// since `Grow` consumes the leftover space directly.
    #[serde(default)]
    pub main_align: super::style::Align,
    #[serde(default)]
    pub children: Vec<Widget>,
}

/// Direction of a [`Stack`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum StackDirection {
    /// Children flow left-to-right.
    #[default]
    Row,
    /// Children flow top-to-bottom.
    Column,
}

/// Equal-cell grid container — `cols × rows` cells filled in row-major order.
///
/// Piece-1 grids are intentionally simple: every cell is the same size,
/// computed from the parent's content rect. More flexible grids (auto-sized
/// rows/columns, spans) are a follow-up; the use cases the editor's Stage-7
/// preferences page and the Stage-8 settings examples actually need are all
/// served by the equal-cell case.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct Grid {
    pub cols: u32,
    pub rows: u32,
    /// `gap.x` between columns, `gap.y` between rows (logical pixels).
    #[serde(default)]
    pub gap: Vec2,
    #[serde(default)]
    pub children: Vec<Widget>,
}

/// Anchor container — each child is placed according to its own
/// [`LayoutStyle::anchor`](super::style::LayoutStyle::anchor).
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct AnchorGroup {
    #[serde(default)]
    pub children: Vec<Widget>,
}

impl Widget {
    /// Build a leaf widget with the given intrinsic logical size.
    pub fn leaf(intrinsic: Vec2) -> Self {
        Self {
            kind: WidgetKind::Leaf { intrinsic },
            ..Default::default()
        }
    }

    /// Build an empty stack with the given direction (gap 0, default align).
    pub fn stack(direction: StackDirection) -> Self {
        Self {
            kind: WidgetKind::Stack(Stack {
                direction,
                ..Default::default()
            }),
            ..Default::default()
        }
    }

    /// Shortcut for `Widget::stack(StackDirection::Row)`.
    pub fn row() -> Self {
        Self::stack(StackDirection::Row)
    }

    /// Shortcut for `Widget::stack(StackDirection::Column)`.
    pub fn column() -> Self {
        Self::stack(StackDirection::Column)
    }

    /// Build an empty grid container.
    pub fn grid(cols: u32, rows: u32) -> Self {
        Self {
            kind: WidgetKind::Grid(Grid {
                cols,
                rows,
                ..Default::default()
            }),
            ..Default::default()
        }
    }

    /// Build an empty anchor container.
    pub fn anchor() -> Self {
        Self {
            kind: WidgetKind::Anchor(AnchorGroup::default()),
            ..Default::default()
        }
    }

    /// Set the widget id (builder).
    pub fn with_id(mut self, id: impl Into<WidgetId>) -> Self {
        self.id = id.into();
        self
    }

    /// Replace the whole [`LayoutStyle`] (builder).
    pub fn with_style(mut self, style: LayoutStyle) -> Self {
        self.style = style;
        self
    }

    /// Replace the per-instance [`VisualStyle`] (builder).
    pub fn with_visual(mut self, visual: VisualStyle) -> Self {
        self.visual = visual;
        self
    }

    /// Opt this widget into a named entry of the active
    /// [`Theme`](super::theme::Theme) (builder). Pass `""` or call
    /// [`Widget::clear_theme_style`] to remove the reference.
    pub fn with_theme_style(mut self, name: impl Into<String>) -> Self {
        let name = name.into();
        self.theme_style = if name.is_empty() { None } else { Some(name) };
        self
    }

    /// Drop any `theme_style` reference (builder).
    pub fn clear_theme_style(mut self) -> Self {
        self.theme_style = None;
        self
    }

    /// Set this widget's text content (builder). Pass `""` to clear it. The
    /// text is shaped at paint time against the widget's resolved font and
    /// font size from the active theme.
    pub fn with_text(mut self, text: impl Into<String>) -> Self {
        let s = text.into();
        self.text = if s.is_empty() { None } else { Some(s) };
        self
    }

    /// Set this widget's typed value (builder).
    pub fn with_value(mut self, value: impl Into<WidgetValue>) -> Self {
        self.value = Some(value.into());
        self
    }

    /// Set the stack gap (builder). Panics if not a stack — surfaces author
    /// mistakes during construction rather than producing a silently
    /// misshapen UI at layout time.
    pub fn with_gap(mut self, gap: f32) -> Self {
        match &mut self.kind {
            WidgetKind::Stack(s) => s.gap = gap,
            _ => panic!("with_gap is only valid on Stack widgets"),
        }
        self
    }

    /// Set the stack main-axis alignment (builder). Panics if not a stack.
    pub fn with_main_align(mut self, align: super::style::Align) -> Self {
        match &mut self.kind {
            WidgetKind::Stack(s) => s.main_align = align,
            _ => panic!("with_main_align is only valid on Stack widgets"),
        }
        self
    }

    /// Set the grid gap vector (builder). Panics if not a grid.
    pub fn with_grid_gap(mut self, gap: Vec2) -> Self {
        match &mut self.kind {
            WidgetKind::Grid(g) => g.gap = gap,
            _ => panic!("with_grid_gap is only valid on Grid widgets"),
        }
        self
    }

    /// Append a single child to a container widget (builder). Panics on a
    /// leaf so the misuse is caught at construction.
    pub fn with_child(mut self, child: Widget) -> Self {
        children_mut(&mut self.kind, |c| c.push(child));
        self
    }

    /// Append many children (builder).
    pub fn with_children(mut self, children: impl IntoIterator<Item = Widget>) -> Self {
        children_mut(&mut self.kind, |c| c.extend(children));
        self
    }

    /// Borrow the direct children of this widget. Empty for leaves.
    pub fn children(&self) -> &[Widget] {
        match &self.kind {
            WidgetKind::Leaf { .. } => &[],
            WidgetKind::Stack(s) => &s.children,
            WidgetKind::Grid(g) => &g.children,
            WidgetKind::Anchor(a) => &a.children,
        }
    }

    /// Borrow the direct children mutably. Empty slice for leaves.
    ///
    /// Underpins [`find_by_id_mut`](Self::find_by_id_mut) and the piece-6
    /// data-binding helpers; safer than reaching into `kind` because all
    /// container kinds funnel through one accessor.
    pub fn children_mut(&mut self) -> &mut [Widget] {
        match &mut self.kind {
            WidgetKind::Leaf { .. } => &mut [],
            WidgetKind::Stack(s) => &mut s.children,
            WidgetKind::Grid(g) => &mut g.children,
            WidgetKind::Anchor(a) => &mut a.children,
        }
    }

    /// Borrow this widget's children as the owning `Vec`, or `None` for a
    /// [`Leaf`](WidgetKind::Leaf) (which cannot hold children). Unlike
    /// [`children_mut`](Self::children_mut) this exposes the `Vec` itself, so
    /// callers can insert/remove — the basis of the structural edits below.
    pub fn children_vec_mut(&mut self) -> Option<&mut Vec<Widget>> {
        match &mut self.kind {
            WidgetKind::Leaf { .. } => None,
            WidgetKind::Stack(s) => Some(&mut s.children),
            WidgetKind::Grid(g) => Some(&mut g.children),
            WidgetKind::Anchor(a) => Some(&mut a.children),
        }
    }

    /// Whether this widget is a container (can hold children) rather than a leaf.
    pub fn is_container(&self) -> bool {
        !matches!(self.kind, WidgetKind::Leaf { .. })
    }

    /// Borrow the widget addressed by `path` (the root for the empty path), or
    /// `None` if any index along the way is out of range.
    pub fn get_path(&self, path: &WidgetPath) -> Option<&Widget> {
        let mut node = self;
        for &i in &path.0 {
            node = node.children().get(i)?;
        }
        Some(node)
    }

    /// Mutable counterpart of [`get_path`](Self::get_path).
    pub fn get_path_mut(&mut self, path: &WidgetPath) -> Option<&mut Widget> {
        let mut node = self;
        for &i in &path.0 {
            node = node.children_mut().get_mut(i)?;
        }
        Some(node)
    }

    /// Inserts `child` at `index` among the children of the widget addressed by
    /// `parent`, returning whether it succeeded. `index` is clamped to the
    /// child count (so it can append). Fails if `parent` does not resolve or is
    /// a leaf.
    pub fn insert_child(&mut self, parent: &WidgetPath, index: usize, child: Widget) -> bool {
        let Some(parent) = self.get_path_mut(parent) else {
            return false;
        };
        let Some(children) = parent.children_vec_mut() else {
            return false;
        };
        children.insert(index.min(children.len()), child);
        true
    }

    /// Appends `child` to the children of the widget addressed by `parent`.
    /// Convenience over [`insert_child`](Self::insert_child) with a trailing
    /// index.
    pub fn push_child_at(&mut self, parent: &WidgetPath, child: Widget) -> bool {
        self.insert_child(parent, usize::MAX, child)
    }

    /// Removes and returns the widget addressed by `path`. The root cannot be
    /// removed (returns `None` for the empty path), nor can an out-of-range or
    /// unreachable path.
    pub fn remove_path(&mut self, path: &WidgetPath) -> Option<Widget> {
        let (parent, index) = path.split_last()?;
        let children = self.get_path_mut(&parent)?.children_vec_mut()?;
        (index < children.len()).then(|| children.remove(index))
    }

    /// Moves the subtree at `from` to be child `index` of `to_parent`,
    /// returning whether it succeeded. Rejects moving the root, or moving a node
    /// into itself or one of its own descendants. Sibling indices shift when the
    /// node is detached, so both `to_parent` and `index` are adjusted internally
    /// to mean what the caller intended *before* the move.
    pub fn move_subtree(
        &mut self,
        from: &WidgetPath,
        to_parent: &WidgetPath,
        index: usize,
    ) -> bool {
        if from.is_root() || to_parent.starts_with(from) {
            return false;
        }
        // The destination must exist and be a container; check before detaching
        // (removing `from`, which is not an ancestor of `to_parent`, leaves the
        // destination node itself unchanged — only its path may shift).
        if !self.get_path(to_parent).is_some_and(Widget::is_container) {
            return false;
        }
        let Some(node) = self.remove_path(from) else {
            return false;
        };
        let to_parent = adjust_path_for_removal(to_parent, from);
        let (from_parent, from_index) = from.split_last().expect("non-root checked above");
        // Inserting back into the same parent after the detach point shifts the
        // target slot down by one.
        let index = if from_parent.0 == to_parent.0 && from_index < index {
            index - 1
        } else {
            index
        };
        self.insert_child(&to_parent, index, node)
    }

    /// Find a descendant (or self) with this id. Returns the first match
    /// in pre-order. `None` if no widget matches (or `id` is empty).
    pub fn find_by_id(&self, id: &WidgetId) -> Option<&Widget> {
        if id.is_empty() {
            return None;
        }
        if self.id == *id {
            return Some(self);
        }
        for child in self.children() {
            if let Some(found) = child.find_by_id(id) {
                return Some(found);
            }
        }
        None
    }

    /// Mutable counterpart of [`find_by_id`](Self::find_by_id).
    pub fn find_by_id_mut(&mut self, id: &WidgetId) -> Option<&mut Widget> {
        if id.is_empty() {
            return None;
        }
        if self.id == *id {
            return Some(self);
        }
        for child in self.children_mut() {
            if let Some(found) = child.find_by_id_mut(id) {
                return Some(found);
            }
        }
        None
    }

    /// Borrow the [`WidgetValue`] of the descendant with this id, if any.
    /// One half of the piece-6 data-binding loop: read what the UI says.
    pub fn value(&self, id: &WidgetId) -> Option<&WidgetValue> {
        self.find_by_id(id).and_then(|w| w.value.as_ref())
    }

    /// Set the [`WidgetValue`] of the descendant with this id, returning
    /// `true` if such a widget exists. The other half of the piece-6
    /// data-binding loop: write game state into the UI.
    pub fn set_value(&mut self, id: &WidgetId, value: impl Into<WidgetValue>) -> bool {
        match self.find_by_id_mut(id) {
            Some(w) => {
                w.value = Some(value.into());
                true
            }
            None => false,
        }
    }

    /// Recursive count of nodes including `self`. Handy for sanity checks
    /// in tests when comparing against a [`LayoutTree::nodes`](super::layout::LayoutTree::nodes)
    /// length.
    pub fn node_count(&self) -> usize {
        1 + self
            .children()
            .iter()
            .map(Widget::node_count)
            .sum::<usize>()
    }

    /// Resolve this widget's effective [`VisualStyle`] under a given theme,
    /// cascading `theme.default` → `theme.styles[self.theme_style]` →
    /// `self.visual`. See [`Theme::resolve`](super::theme::Theme::resolve)
    /// for the merge rules. Children are *not* recursively resolved here —
    /// piece 4 walks the tree pairing each [`super::layout::LayoutNode`] with
    /// its resolved style.
    pub fn resolve_visual(&self, theme: &super::theme::Theme) -> VisualStyle {
        theme.resolve(self.theme_style.as_deref(), &self.visual)
    }

    /// Serialize this widget tree to a pretty-printed RON string — the
    /// canonical UI-document format an editor saves and the runtime loads.
    pub fn to_ron(&self) -> Result<String, ron::Error> {
        ron::ser::to_string_pretty(self, ron::ser::PrettyConfig::default())
    }

    /// Parse a widget tree from a RON string produced by [`to_ron`](Self::to_ron).
    pub fn from_ron(text: &str) -> Result<Self, ron::de::SpannedError> {
        ron::de::from_str(text)
    }
}

/// Rewrites `path` to stay valid after the widget at `removed` is detached.
///
/// Detaching shifts the later siblings of `removed` down by one. A path is
/// affected only if it descends through `removed`'s parent and its index at
/// that depth is *after* the removed index; then that one index decrements.
/// `path` must not be `removed` or beneath it (the caller guarantees this).
fn adjust_path_for_removal(path: &WidgetPath, removed: &WidgetPath) -> WidgetPath {
    let Some((removed_parent, removed_index)) = removed.split_last() else {
        return path.clone();
    };
    let depth = removed_parent.0.len();
    let mut out = path.0.clone();
    if out.len() > depth && out[..depth] == removed_parent.0[..] && out[depth] > removed_index {
        out[depth] -= 1;
    }
    WidgetPath(out)
}

fn children_mut(kind: &mut WidgetKind, f: impl FnOnce(&mut Vec<Widget>)) {
    match kind {
        WidgetKind::Stack(s) => f(&mut s.children),
        WidgetKind::Grid(g) => f(&mut g.children),
        WidgetKind::Anchor(a) => f(&mut a.children),
        WidgetKind::Leaf { .. } => panic!("cannot add children to a Leaf widget"),
    }
}

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

    /// A row root with three leaf children id'd "a","b","c".
    fn abc_tree() -> Widget {
        Widget::row()
            .with_id("root")
            .with_child(Widget::leaf(Vec2::ZERO).with_id("a"))
            .with_child(Widget::leaf(Vec2::ZERO).with_id("b"))
            .with_child(Widget::leaf(Vec2::ZERO).with_id("c"))
    }

    fn ids_of(children: &[Widget]) -> Vec<&str> {
        children.iter().map(|w| w.id.as_str()).collect()
    }

    #[test]
    fn get_path_addresses_nodes() {
        let root = abc_tree();
        assert_eq!(
            root.get_path(&WidgetPath::root()).unwrap().id.as_str(),
            "root"
        );
        assert_eq!(
            root.get_path(&WidgetPath(vec![1])).unwrap().id.as_str(),
            "b"
        );
        assert!(root.get_path(&WidgetPath(vec![9])).is_none());
    }

    #[test]
    fn insert_and_remove_children_by_path() {
        let mut root = abc_tree();
        // Insert "x" between a and b.
        assert!(root.insert_child(
            &WidgetPath::root(),
            1,
            Widget::leaf(Vec2::ZERO).with_id("x")
        ));
        assert_eq!(ids_of(root.children()), ["a", "x", "b", "c"]);
        // Append "z" via the clamping path.
        assert!(root.push_child_at(&WidgetPath::root(), Widget::leaf(Vec2::ZERO).with_id("z")));
        assert_eq!(ids_of(root.children()), ["a", "x", "b", "c", "z"]);
        // A leaf rejects children; the root cannot be removed.
        assert!(!root.insert_child(&WidgetPath(vec![0]), 0, Widget::default()));
        assert!(root.remove_path(&WidgetPath::root()).is_none());
        // Remove "x".
        let removed = root.remove_path(&WidgetPath(vec![1])).unwrap();
        assert_eq!(removed.id.as_str(), "x");
        assert_eq!(ids_of(root.children()), ["a", "b", "c", "z"]);
    }

    #[test]
    fn move_subtree_reorders_within_parent() {
        let mut root = abc_tree();
        // Move "a" (index 0) to the end (index 3 in pre-removal terms).
        assert!(root.move_subtree(&WidgetPath(vec![0]), &WidgetPath::root(), 3));
        assert_eq!(ids_of(root.children()), ["b", "c", "a"]);
    }

    #[test]
    fn move_subtree_across_branches_adjusts_paths() {
        // root[ col(0) [a], b(1), c(2) ]: move c into the column before a.
        let mut root = Widget::row()
            .with_id("root")
            .with_child(
                Widget::column()
                    .with_id("col")
                    .with_child(Widget::leaf(Vec2::ZERO).with_id("a")),
            )
            .with_child(Widget::leaf(Vec2::ZERO).with_id("b"))
            .with_child(Widget::leaf(Vec2::ZERO).with_id("c"));
        assert!(root.move_subtree(&WidgetPath(vec![2]), &WidgetPath(vec![0]), 0));
        // c now leads the column; root has col + b left.
        assert_eq!(
            ids_of(root.get_path(&WidgetPath(vec![0])).unwrap().children()),
            ["c", "a"]
        );
        assert_eq!(ids_of(root.children()), ["col", "b"]);
    }

    #[test]
    fn move_subtree_rejects_into_own_descendant_and_root() {
        let mut root = Widget::row().with_id("root").with_child(
            Widget::column()
                .with_id("col")
                .with_child(Widget::leaf(Vec2::ZERO).with_id("a")),
        );
        // Can't move "col" (path [0]) under its own child "a" (path [0,0]).
        assert!(!root.move_subtree(&WidgetPath(vec![0]), &WidgetPath(vec![0, 0]), 0));
        // Can't move the root.
        assert!(!root.move_subtree(&WidgetPath::root(), &WidgetPath(vec![0]), 0));
        // Tree is unchanged.
        assert_eq!(ids_of(root.children()), ["col"]);
    }

    #[test]
    fn widget_id_from_str_and_string() {
        let a: WidgetId = "abc".into();
        let b: WidgetId = String::from("abc").into();
        assert_eq!(a, b);
        assert_eq!(a.as_str(), "abc");
        assert!(!a.is_empty());
        assert!(WidgetId::default().is_empty());
    }

    #[test]
    fn default_widget_is_zero_leaf() {
        let w = Widget::default();
        assert_eq!(w.id, WidgetId::default());
        assert_eq!(w.style, LayoutStyle::default());
        assert!(matches!(w.kind, WidgetKind::Leaf { intrinsic } if intrinsic == Vec2::ZERO));
    }

    #[test]
    fn builder_methods_compose() {
        let w = Widget::row()
            .with_id("toolbar")
            .with_gap(4.0)
            .with_main_align(super::super::style::Align::Center)
            .with_child(Widget::leaf(Vec2::new(10.0, 10.0)).with_id("a"))
            .with_children([Widget::leaf(Vec2::new(20.0, 10.0)).with_id("b")]);
        assert_eq!(w.id.as_str(), "toolbar");
        let WidgetKind::Stack(s) = &w.kind else {
            panic!("expected stack");
        };
        assert_eq!(s.direction, StackDirection::Row);
        assert_eq!(s.gap, 4.0);
        assert_eq!(s.main_align, super::super::style::Align::Center);
        assert_eq!(s.children.len(), 2);
        assert_eq!(s.children[0].id.as_str(), "a");
        assert_eq!(s.children[1].id.as_str(), "b");
    }

    #[test]
    #[should_panic(expected = "cannot add children to a Leaf widget")]
    fn adding_child_to_leaf_panics() {
        let _ = Widget::leaf(Vec2::new(1.0, 1.0)).with_child(Widget::leaf(Vec2::ONE));
    }

    #[test]
    #[should_panic(expected = "with_gap is only valid on Stack widgets")]
    fn gap_on_non_stack_panics() {
        let _ = Widget::grid(2, 2).with_gap(4.0);
    }

    #[test]
    fn node_count_recurses() {
        let tree = Widget::row()
            .with_child(Widget::leaf(Vec2::ONE))
            .with_child(
                Widget::column()
                    .with_child(Widget::leaf(Vec2::ONE))
                    .with_child(Widget::leaf(Vec2::ONE)),
            );
        // root + leaf + (column + 2 leaves) = 5
        assert_eq!(tree.node_count(), 5);
    }

    #[test]
    fn widget_round_trips_through_ron() {
        let w = Widget::row()
            .with_id("root")
            .with_gap(8.0)
            .with_child(Widget::leaf(Vec2::new(40.0, 20.0)).with_id("a"))
            .with_child(Widget::anchor().with_child(Widget::leaf(Vec2::new(10.0, 10.0))));
        let text = ron::ser::to_string_pretty(&w, ron::ser::PrettyConfig::default()).unwrap();
        let decoded: Widget = ron::de::from_str(&text).unwrap();
        assert_eq!(w, decoded);
    }

    #[test]
    fn visual_and_theme_style_builders_set_fields() {
        use super::super::visual::VisualStyle;
        use crate::math::Color;

        let w = Widget::leaf(Vec2::ONE)
            .with_id("a")
            .with_visual(VisualStyle {
                background: Some(Color::RED),
                ..VisualStyle::EMPTY
            })
            .with_theme_style("button");
        assert_eq!(w.visual.background, Some(Color::RED));
        assert_eq!(w.theme_style.as_deref(), Some("button"));

        // Passing an empty string drops the reference.
        let cleared = w.clone().with_theme_style("");
        assert_eq!(cleared.theme_style, None);

        let explicitly_cleared = w.clear_theme_style();
        assert_eq!(explicitly_cleared.theme_style, None);
    }

    #[test]
    fn resolve_visual_cascades_theme_named_overrides() {
        use super::super::theme::Theme;
        use super::super::visual::VisualStyle;
        use crate::math::Color;

        let theme = Theme::new()
            .with_default(VisualStyle {
                foreground: Some(Color::BLACK),
                background: Some(Color::WHITE),
                ..VisualStyle::EMPTY
            })
            .with_style(
                "button",
                VisualStyle {
                    background: Some(Color::rgb(0.85, 0.85, 0.9)),
                    ..VisualStyle::EMPTY
                },
            );
        let w = Widget::leaf(Vec2::ONE)
            .with_theme_style("button")
            .with_visual(VisualStyle {
                foreground: Some(Color::RED),
                ..VisualStyle::EMPTY
            });
        let resolved = w.resolve_visual(&theme);
        assert_eq!(resolved.foreground, Some(Color::RED)); // per-instance
        assert_eq!(resolved.background, Some(Color::rgb(0.85, 0.85, 0.9))); // named
    }

    #[test]
    fn widget_with_visual_and_theme_style_round_trips_through_ron() {
        use super::super::visual::{FontRef, VisualStyle};
        use crate::math::Color;

        let w = Widget::row()
            .with_id("toolbar")
            .with_theme_style("toolbar")
            .with_visual(VisualStyle {
                background: Some(Color::rgb(0.1, 0.1, 0.1)),
                font: Some(FontRef::bold("Inter")),
                ..VisualStyle::EMPTY
            })
            .with_child(Widget::leaf(Vec2::new(40.0, 20.0)).with_theme_style("button"));
        let text = w.to_ron().unwrap();
        let decoded = Widget::from_ron(&text).unwrap();
        assert_eq!(w, decoded);
    }

    #[test]
    fn default_widget_serializes_without_new_fields() {
        // The new `visual` and `theme_style` fields skip when empty/None, so
        // a piece-1 default widget should still serialize to the piece-1
        // form (no `visual:` or `theme_style:` keys in the output).
        let w = Widget::default();
        let text = w.to_ron().unwrap();
        assert!(!text.contains("visual:"));
        assert!(!text.contains("theme_style:"));
        // And re-parsing yields the same value.
        assert_eq!(Widget::from_ron(&text).unwrap(), w);
    }

    #[test]
    fn find_by_id_walks_the_subtree() {
        let tree = Widget::row()
            .with_id("root")
            .with_child(Widget::leaf(Vec2::ONE).with_id("a"))
            .with_child(
                Widget::column()
                    .with_id("group")
                    .with_child(Widget::leaf(Vec2::ONE).with_id("buried")),
            );
        assert_eq!(tree.find_by_id(&"root".into()).unwrap().id.as_str(), "root");
        assert_eq!(tree.find_by_id(&"a".into()).unwrap().id.as_str(), "a");
        assert_eq!(
            tree.find_by_id(&"buried".into()).unwrap().id.as_str(),
            "buried"
        );
        assert!(tree.find_by_id(&"missing".into()).is_none());
        // Empty id is never a match.
        assert!(tree.find_by_id(&WidgetId::default()).is_none());
    }

    #[test]
    fn set_value_updates_a_descendant() {
        let mut tree = Widget::row()
            .with_id("root")
            .with_child(Widget::leaf(Vec2::ONE).with_id("volume"))
            .with_child(Widget::leaf(Vec2::ONE).with_id("invert_y"));
        assert!(tree.set_value(&"volume".into(), 0.75_f32));
        assert!(tree.set_value(&"invert_y".into(), true));
        assert_eq!(
            tree.value(&"volume".into()).and_then(|v| v.as_float()),
            Some(0.75_f32 as f64)
        );
        assert_eq!(
            tree.value(&"invert_y".into()).and_then(|v| v.as_bool()),
            Some(true)
        );
        // Unknown id: returns false, tree unchanged.
        assert!(!tree.set_value(&"missing".into(), 0.0_f32));
    }

    #[test]
    fn with_value_builder_sets_value() {
        let w = Widget::leaf(Vec2::ONE).with_id("checkbox").with_value(true);
        assert_eq!(w.value.as_ref().unwrap().as_bool(), Some(true));
    }

    #[test]
    fn value_round_trips_through_widget_ron() {
        use super::super::value::WidgetValue;
        let w = Widget::leaf(Vec2::ONE)
            .with_id("slider")
            .with_value(WidgetValue::Float(0.42));
        let text = w.to_ron().unwrap();
        let decoded = Widget::from_ron(&text).unwrap();
        assert_eq!(w, decoded);
    }
}
