Skip to main content

hypen_engine/reconcile/
tree.rs

1use crate::ir::{Element, NodeId, Value, IRNode, Props};
2use indexmap::IndexMap;
3use slotmap::SlotMap;
4use std::sync::Arc;
5
6/// The kind of control flow node for re-reconciliation
7#[derive(Debug, Clone)]
8pub enum ControlFlowKind {
9    /// ForEach iteration container
10    ForEach {
11        item_name: String,
12        key_path: Option<String>,
13    },
14    /// Conditional (When/If) container
15    Conditional,
16}
17
18/// Instance node - a concrete instance of an element in the tree
19///
20/// Uses im::Vector for children to enable O(1) structural sharing during clones.
21/// This is critical for reconciliation performance where nodes are frequently cloned.
22#[derive(Debug, Clone)]
23pub struct InstanceNode {
24    /// Unique node ID
25    pub id: NodeId,
26
27    /// Element type (e.g., "Column", "Text", "__ForEach", "__Conditional")
28    pub element_type: String,
29
30    /// Resolved props (bindings evaluated to actual values)
31    pub props: IndexMap<String, serde_json::Value>,
32
33    /// Raw props (including bindings) for change detection - Arc-wrapped for O(1) clone
34    pub raw_props: Props,
35
36    /// Original element template (for List re-rendering) - legacy
37    /// Only populated for List elements that need to re-render children
38    /// Arc-wrapped for O(1) clone during reconciliation
39    pub element_template: Option<Arc<Element>>,
40
41    /// Original IRNode template (for ForEach/Conditional re-rendering)
42    /// Used for control flow nodes that need to re-render on state change
43    /// Arc-wrapped for O(1) clone during reconciliation
44    pub ir_node_template: Option<Arc<IRNode>>,
45
46    /// Control flow metadata for ForEach/Conditional nodes
47    pub control_flow: Option<ControlFlowKind>,
48
49    // Event handling removed - now done at renderer level
50
51    /// Optional key for reconciliation
52    pub key: Option<String>,
53
54    /// Parent node ID
55    pub parent: Option<NodeId>,
56
57    /// Child node IDs (ordered) - uses im::Vector for O(1) clone
58    pub children: im::Vector<NodeId>,
59}
60
61impl InstanceNode {
62    pub fn new(id: NodeId, element: &Element, state: &serde_json::Value) -> Self {
63        let props = resolve_props(&element.props, state);
64
65        Self {
66            id,
67            element_type: element.element_type.clone(),
68            props,
69            raw_props: element.props.clone(),
70            element_template: None,
71            ir_node_template: None,
72            control_flow: None,
73            key: element.key.clone(),
74            parent: None,
75            children: im::Vector::new(),
76        }
77    }
78
79    /// Create a control flow container node (ForEach or Conditional)
80    pub fn new_control_flow(
81        id: NodeId,
82        element_type: &str,
83        props: IndexMap<String, serde_json::Value>,
84        raw_props: Props,
85        control_flow: ControlFlowKind,
86        ir_node_template: IRNode,
87    ) -> Self {
88        Self {
89            id,
90            element_type: element_type.to_string(),
91            props,
92            raw_props,
93            element_template: None,
94            ir_node_template: Some(Arc::new(ir_node_template)),
95            control_flow: Some(control_flow),
96            key: None,
97            parent: None,
98            children: im::Vector::new(),
99        }
100    }
101
102    /// Update props by re-evaluating bindings against new state
103    pub fn update_props(&mut self, state: &serde_json::Value) {
104        self.props = resolve_props(&self.raw_props, state);
105    }
106
107    /// Check if this is a ForEach control flow node
108    pub fn is_foreach(&self) -> bool {
109        matches!(self.control_flow, Some(ControlFlowKind::ForEach { .. }))
110    }
111
112    /// Check if this is a Conditional control flow node
113    pub fn is_conditional(&self) -> bool {
114        matches!(self.control_flow, Some(ControlFlowKind::Conditional))
115    }
116}
117
118/// The instance tree - maintains the current UI tree state
119pub struct InstanceTree {
120    /// All nodes in the tree
121    nodes: SlotMap<NodeId, InstanceNode>,
122
123    /// Root node ID
124    root: Option<NodeId>,
125}
126
127impl InstanceTree {
128    pub fn new() -> Self {
129        Self {
130            nodes: SlotMap::with_key(),
131            root: None,
132        }
133    }
134
135    /// Clear all nodes and reset the tree
136    pub fn clear(&mut self) {
137        self.nodes.clear();
138        self.root = None;
139    }
140
141    /// Create a new node and return its ID
142    pub fn create_node(&mut self, element: &Element, state: &serde_json::Value) -> NodeId {
143        let id = self.nodes.insert_with_key(|id| InstanceNode::new(id, element, state));
144        id
145    }
146
147    /// Create a control flow node (ForEach or Conditional) and return its ID
148    pub fn create_control_flow_node(
149        &mut self,
150        element_type: &str,
151        props: IndexMap<String, serde_json::Value>,
152        raw_props: Props,
153        control_flow: ControlFlowKind,
154        ir_node_template: IRNode,
155    ) -> NodeId {
156        self.nodes.insert_with_key(|id| {
157            InstanceNode::new_control_flow(id, element_type, props, raw_props, control_flow, ir_node_template)
158        })
159    }
160
161    /// Get a node by ID
162    pub fn get(&self, id: NodeId) -> Option<&InstanceNode> {
163        self.nodes.get(id)
164    }
165
166    /// Get a mutable node by ID
167    pub fn get_mut(&mut self, id: NodeId) -> Option<&mut InstanceNode> {
168        self.nodes.get_mut(id)
169    }
170
171    /// Remove a node and all its descendants
172    pub fn remove(&mut self, id: NodeId) -> Option<InstanceNode> {
173        if let Some(node) = self.nodes.get(id) {
174            let children = node.children.clone();
175            // Remove all children recursively
176            for child_id in children {
177                self.remove(child_id);
178            }
179        }
180        self.nodes.remove(id)
181    }
182
183    /// Set the root node
184    pub fn set_root(&mut self, id: NodeId) {
185        self.root = Some(id);
186    }
187
188    /// Get the root node ID
189    pub fn root(&self) -> Option<NodeId> {
190        self.root
191    }
192
193    /// Add a child to a parent node
194    pub fn add_child(&mut self, parent_id: NodeId, child_id: NodeId, before: Option<NodeId>) {
195        if let Some(parent) = self.nodes.get_mut(parent_id) {
196            if let Some(before_id) = before {
197                if let Some(pos) = parent.children.iter().position(|&id| id == before_id) {
198                    parent.children.insert(pos, child_id);
199                } else {
200                    parent.children.push_back(child_id);
201                }
202            } else {
203                parent.children.push_back(child_id);
204            }
205        }
206
207        if let Some(child) = self.nodes.get_mut(child_id) {
208            child.parent = Some(parent_id);
209        }
210    }
211
212    /// Remove a child from its parent
213    pub fn remove_child(&mut self, parent_id: NodeId, child_id: NodeId) {
214        if let Some(parent) = self.nodes.get_mut(parent_id) {
215            parent.children = parent.children.iter().filter(|&&id| id != child_id).copied().collect();
216        }
217
218        if let Some(child) = self.nodes.get_mut(child_id) {
219            child.parent = None;
220        }
221    }
222
223    /// Update all nodes that depend on changed state
224    pub fn update_nodes(&mut self, node_ids: &indexmap::IndexSet<NodeId>, state: &serde_json::Value) {
225        for &node_id in node_ids {
226            if let Some(node) = self.nodes.get_mut(node_id) {
227                node.update_props(state);
228            }
229        }
230    }
231
232    /// Iterate over all nodes
233    pub fn iter(&self) -> impl Iterator<Item = (NodeId, &InstanceNode)> {
234        self.nodes.iter()
235    }
236}
237
238impl Default for InstanceTree {
239    fn default() -> Self {
240        Self::new()
241    }
242}
243
244/// Resolve props by evaluating bindings against current state
245fn resolve_props(props: &IndexMap<String, Value>, state: &serde_json::Value) -> IndexMap<String, serde_json::Value> {
246    resolve_props_with_item(props, state, None)
247}
248
249/// Resolve props with optional item context (for list iteration)
250fn resolve_props_with_item(
251    props: &IndexMap<String, Value>,
252    state: &serde_json::Value,
253    item: Option<&serde_json::Value>,
254) -> IndexMap<String, serde_json::Value> {
255    let mut resolved = IndexMap::new();
256
257    for (key, value) in props {
258        let resolved_value = match value {
259            Value::Static(v) => v.clone(),
260            Value::Binding(binding) => {
261                if binding.is_item() {
262                    // Evaluate item binding
263                    if let Some(item_value) = item {
264                        evaluate_item_binding(binding, item_value).unwrap_or(serde_json::Value::Null)
265                    } else {
266                        serde_json::Value::Null
267                    }
268                } else {
269                    // Evaluate state binding
270                    evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
271                }
272            }
273            Value::TemplateString { template, .. } => {
274                // Use expression evaluation for template strings - handles both
275                // simple bindings and complex expressions like ternaries
276                match crate::reactive::evaluate_template_string(template, state, item) {
277                    Ok(result) => serde_json::Value::String(result),
278                    Err(_) => serde_json::Value::String(template.clone()),
279                }
280            }
281            Value::Action(action) => {
282                // Actions are serialized with @ prefix for renderer to detect
283                serde_json::Value::String(format!("@{}", action))
284            }
285        };
286        resolved.insert(key.clone(), resolved_value);
287    }
288
289    resolved
290}
291
292/// Evaluate an item binding against the item object
293fn evaluate_item_binding(binding: &crate::reactive::Binding, item: &serde_json::Value) -> Option<serde_json::Value> {
294    if binding.path.is_empty() {
295        // Bare @item - return the whole item
296        return Some(item.clone());
297    }
298
299    let mut current = item;
300    for segment in &binding.path {
301        current = current.get(segment)?;
302    }
303    Some(current.clone())
304}
305
306/// Evaluate a binding against the state object using its parsed path
307fn evaluate_binding(binding: &crate::reactive::Binding, state: &serde_json::Value) -> Option<serde_json::Value> {
308    let mut current = state;
309
310    for segment in &binding.path {
311        current = current.get(segment)?;
312    }
313
314    Some(current.clone())
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320    use serde_json::json;
321
322    #[test]
323    fn test_evaluate_binding() {
324        use crate::reactive::Binding;
325
326        let state = json!({
327            "user": {
328                "name": "Alice",
329                "age": 30
330            }
331        });
332
333        let name_binding = Binding::state(vec!["user".to_string(), "name".to_string()]);
334        let age_binding = Binding::state(vec!["user".to_string(), "age".to_string()]);
335        let email_binding = Binding::state(vec!["user".to_string(), "email".to_string()]);
336
337        assert_eq!(
338            evaluate_binding(&name_binding, &state),
339            Some(json!("Alice"))
340        );
341        assert_eq!(
342            evaluate_binding(&age_binding, &state),
343            Some(json!(30))
344        );
345        assert_eq!(evaluate_binding(&email_binding, &state), None);
346    }
347}