hypen_engine/reconcile/
tree.rs

1use crate::ir::{Element, NodeId, Value};
2use indexmap::IndexMap;
3use slotmap::SlotMap;
4
5/// Instance node - a concrete instance of an element in the tree
6#[derive(Debug, Clone)]
7pub struct InstanceNode {
8    /// Unique node ID
9    pub id: NodeId,
10
11    /// Element type (e.g., "Column", "Text")
12    pub element_type: String,
13
14    /// Resolved props (bindings evaluated to actual values)
15    pub props: IndexMap<String, serde_json::Value>,
16
17    /// Raw props (including bindings) for change detection
18    pub raw_props: IndexMap<String, Value>,
19
20    /// Original element template (for List re-rendering)
21    /// Only populated for List elements that need to re-render children
22    pub element_template: Option<Element>,
23
24    // Event handling removed - now done at renderer level
25
26    /// Optional key for reconciliation
27    pub key: Option<String>,
28
29    /// Parent node ID
30    pub parent: Option<NodeId>,
31
32    /// Child node IDs (ordered)
33    pub children: Vec<NodeId>,
34}
35
36impl InstanceNode {
37    pub fn new(id: NodeId, element: &Element, state: &serde_json::Value) -> Self {
38        let props = resolve_props(&element.props, state);
39
40        Self {
41            id,
42            element_type: element.element_type.clone(),
43            props,
44            raw_props: element.props.clone(),
45            element_template: None,
46            key: element.key.clone(),
47            parent: None,
48            children: Vec::new(),
49        }
50    }
51
52    /// Update props by re-evaluating bindings against new state
53    pub fn update_props(&mut self, state: &serde_json::Value) {
54        self.props = resolve_props(&self.raw_props, state);
55    }
56}
57
58/// The instance tree - maintains the current UI tree state
59pub struct InstanceTree {
60    /// All nodes in the tree
61    nodes: SlotMap<NodeId, InstanceNode>,
62
63    /// Root node ID
64    root: Option<NodeId>,
65}
66
67impl InstanceTree {
68    pub fn new() -> Self {
69        Self {
70            nodes: SlotMap::with_key(),
71            root: None,
72        }
73    }
74
75    /// Clear all nodes and reset the tree
76    pub fn clear(&mut self) {
77        self.nodes.clear();
78        self.root = None;
79    }
80
81    /// Create a new node and return its ID
82    pub fn create_node(&mut self, element: &Element, state: &serde_json::Value) -> NodeId {
83        let id = self.nodes.insert_with_key(|id| InstanceNode::new(id, element, state));
84        id
85    }
86
87    /// Get a node by ID
88    pub fn get(&self, id: NodeId) -> Option<&InstanceNode> {
89        self.nodes.get(id)
90    }
91
92    /// Get a mutable node by ID
93    pub fn get_mut(&mut self, id: NodeId) -> Option<&mut InstanceNode> {
94        self.nodes.get_mut(id)
95    }
96
97    /// Remove a node and all its descendants
98    pub fn remove(&mut self, id: NodeId) -> Option<InstanceNode> {
99        if let Some(node) = self.nodes.get(id) {
100            let children = node.children.clone();
101            // Remove all children recursively
102            for child_id in children {
103                self.remove(child_id);
104            }
105        }
106        self.nodes.remove(id)
107    }
108
109    /// Set the root node
110    pub fn set_root(&mut self, id: NodeId) {
111        self.root = Some(id);
112    }
113
114    /// Get the root node ID
115    pub fn root(&self) -> Option<NodeId> {
116        self.root
117    }
118
119    /// Add a child to a parent node
120    pub fn add_child(&mut self, parent_id: NodeId, child_id: NodeId, before: Option<NodeId>) {
121        if let Some(parent) = self.nodes.get_mut(parent_id) {
122            if let Some(before_id) = before {
123                if let Some(pos) = parent.children.iter().position(|&id| id == before_id) {
124                    parent.children.insert(pos, child_id);
125                } else {
126                    parent.children.push(child_id);
127                }
128            } else {
129                parent.children.push(child_id);
130            }
131        }
132
133        if let Some(child) = self.nodes.get_mut(child_id) {
134            child.parent = Some(parent_id);
135        }
136    }
137
138    /// Remove a child from its parent
139    pub fn remove_child(&mut self, parent_id: NodeId, child_id: NodeId) {
140        if let Some(parent) = self.nodes.get_mut(parent_id) {
141            parent.children.retain(|&id| id != child_id);
142        }
143
144        if let Some(child) = self.nodes.get_mut(child_id) {
145            child.parent = None;
146        }
147    }
148
149    /// Update all nodes that depend on changed state
150    pub fn update_nodes(&mut self, node_ids: &indexmap::IndexSet<NodeId>, state: &serde_json::Value) {
151        for &node_id in node_ids {
152            if let Some(node) = self.nodes.get_mut(node_id) {
153                node.update_props(state);
154            }
155        }
156    }
157
158    /// Iterate over all nodes
159    pub fn iter(&self) -> impl Iterator<Item = (NodeId, &InstanceNode)> {
160        self.nodes.iter()
161    }
162}
163
164impl Default for InstanceTree {
165    fn default() -> Self {
166        Self::new()
167    }
168}
169
170/// Resolve props by evaluating bindings against current state
171fn resolve_props(props: &IndexMap<String, Value>, state: &serde_json::Value) -> IndexMap<String, serde_json::Value> {
172    resolve_props_with_item(props, state, None)
173}
174
175/// Resolve props with optional item context (for list iteration)
176fn resolve_props_with_item(
177    props: &IndexMap<String, Value>,
178    state: &serde_json::Value,
179    item: Option<&serde_json::Value>,
180) -> IndexMap<String, serde_json::Value> {
181    let mut resolved = IndexMap::new();
182
183    for (key, value) in props {
184        let resolved_value = match value {
185            Value::Static(v) => v.clone(),
186            Value::Binding(binding) => {
187                if binding.is_item() {
188                    // Evaluate item binding
189                    if let Some(item_value) = item {
190                        evaluate_item_binding(binding, item_value).unwrap_or(serde_json::Value::Null)
191                    } else {
192                        serde_json::Value::Null
193                    }
194                } else {
195                    // Evaluate state binding
196                    evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
197                }
198            }
199            Value::TemplateString { template, .. } => {
200                // Use expression evaluation for template strings - handles both
201                // simple bindings and complex expressions like ternaries
202                match crate::reactive::evaluate_template_string(template, state, item) {
203                    Ok(result) => serde_json::Value::String(result),
204                    Err(_) => serde_json::Value::String(template.clone()),
205                }
206            }
207            Value::Action(action) => {
208                // Actions are serialized with @ prefix for renderer to detect
209                serde_json::Value::String(format!("@{}", action))
210            }
211        };
212        resolved.insert(key.clone(), resolved_value);
213    }
214
215    resolved
216}
217
218/// Evaluate an item binding against the item object
219fn evaluate_item_binding(binding: &crate::reactive::Binding, item: &serde_json::Value) -> Option<serde_json::Value> {
220    if binding.path.is_empty() {
221        // Bare @item - return the whole item
222        return Some(item.clone());
223    }
224
225    let mut current = item;
226    for segment in &binding.path {
227        current = current.get(segment)?;
228    }
229    Some(current.clone())
230}
231
232/// Evaluate a binding against the state object using its parsed path
233fn evaluate_binding(binding: &crate::reactive::Binding, state: &serde_json::Value) -> Option<serde_json::Value> {
234    let mut current = state;
235
236    for segment in &binding.path {
237        current = current.get(segment)?;
238    }
239
240    Some(current.clone())
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246    use serde_json::json;
247
248    #[test]
249    fn test_evaluate_binding() {
250        use crate::reactive::Binding;
251
252        let state = json!({
253            "user": {
254                "name": "Alice",
255                "age": 30
256            }
257        });
258
259        let name_binding = Binding::state(vec!["user".to_string(), "name".to_string()]);
260        let age_binding = Binding::state(vec!["user".to_string(), "age".to_string()]);
261        let email_binding = Binding::state(vec!["user".to_string(), "email".to_string()]);
262
263        assert_eq!(
264            evaluate_binding(&name_binding, &state),
265            Some(json!("Alice"))
266        );
267        assert_eq!(
268            evaluate_binding(&age_binding, &state),
269            Some(json!(30))
270        );
271        assert_eq!(evaluate_binding(&email_binding, &state), None);
272    }
273}