use serde_json::Value;
use crate::types::{Props, UiNode};
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct TreeGuide {
pub elbow_at: Option<usize>,
pub tee: bool,
pub pass_through: Vec<usize>,
}
fn continues_after(depths: &[usize], index: usize, level: usize) -> bool {
for &depth in &depths[index + 1..] {
if depth <= level {
return false;
}
if depth == level + 1 {
return true;
}
}
false
}
pub fn tree_guides(depths: &[usize]) -> Vec<TreeGuide> {
depths
.iter()
.enumerate()
.map(|(index, &depth)| {
if depth == 0 {
return TreeGuide::default();
}
let pass_through = (0..depth - 1).filter(|&level| continues_after(depths, index, level)).collect();
TreeGuide { elbow_at: Some(depth - 1), tee: continues_after(depths, index, depth - 1), pass_through }
})
.collect()
}
pub fn is_row_root(node: &UiNode) -> bool {
node.recipe.as_ref().is_some_and(|r| r.macro_ == "Row" && r.part == "root")
}
fn sibling_depth(node: &UiNode) -> usize {
let depth = if is_row_root(node) { node.recipe.as_ref().and_then(|r| r.props.get("depth")) } else { node.props.get("depth") }.and_then(Value::as_f64);
match depth {
Some(d) if d.is_finite() && d > 0.0 => d.floor() as usize,
_ => 0,
}
}
fn has_depth(node: &UiNode) -> bool {
!is_row_root(node) && node.component != "TreeGuides" && node.props.get("depth").is_some_and(Value::is_number)
}
fn guide_props(depth: usize, g: &TreeGuide) -> Props {
let mut props = Props::new();
props.insert("depth".into(), Value::from(depth as u64));
if let Some(elbow) = g.elbow_at {
props.insert("elbowAt".into(), Value::from(elbow as u64));
}
props.insert("tee".into(), Value::Bool(g.tee));
props.insert("passThrough".into(), Value::Array(g.pass_through.iter().map(|&l| Value::from(l as u64)).collect()));
props
}
fn fill_siblings(nodes: &mut [UiNode]) {
let depths: Vec<usize> = nodes.iter().map(sibling_depth).collect();
let guides = tree_guides(&depths);
for (i, node) in nodes.iter_mut().enumerate() {
let g = &guides[i];
if is_row_root(node) {
if let Some(part) = guides_part(node) {
part.props = guide_props(depths[i], g);
}
} else if has_depth(node) {
let mut guide = guide_props(depths[i], g);
guide.shift_remove("depth");
node.props.insert("guide".into(), Value::Object(guide));
}
}
}
fn guides_part(row: &mut UiNode) -> Option<&mut UiNode> {
row.children
.iter_mut()
.find(|c| c.component == "TreeGuides" && c.recipe.as_ref().is_some_and(|r| r.part == "guides"))
}
pub fn apply_tree_guides(root: &mut UiNode) {
fill_siblings(std::slice::from_mut(root));
visit(root);
}
fn visit(node: &mut UiNode) {
fill_siblings(&mut node.children);
if let Some(slots) = node.slots.as_mut() {
for slot in slots.values_mut() {
fill_siblings(std::slice::from_mut(slot));
visit(slot);
}
}
for child in &mut node.children {
visit(child);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_sibling_after_the_subtree_tees_and_passes_through() {
let g = tree_guides(&[0, 1, 2, 1]);
assert_eq!(g[0], TreeGuide::default());
assert_eq!(g[1], TreeGuide { elbow_at: Some(0), tee: true, pass_through: vec![] });
assert_eq!(g[2], TreeGuide { elbow_at: Some(1), tee: false, pass_through: vec![0] });
assert_eq!(g[3], TreeGuide { elbow_at: Some(0), tee: false, pass_through: vec![] });
}
}