Skip to main content

hypen_engine/reconcile/
diff.rs

1use super::{InstanceTree, Patch, ControlFlowKind};
2use crate::ir::{Element, NodeId, Value, IRNode, ConditionalBranch, Props};
3use crate::reactive::{DependencyGraph, evaluate_template_string, Binding};
4use indexmap::IndexMap;
5
6/// Reconcile an element tree against the instance tree and generate patches
7pub fn reconcile(
8    tree: &mut InstanceTree,
9    element: &Element,
10    parent_id: Option<NodeId>,
11    state: &serde_json::Value,
12    dependencies: &mut DependencyGraph,
13) -> Vec<Patch> {
14    let mut patches = Vec::new();
15
16    // For initial render, just create the tree
17    if tree.root().is_none() {
18        let node_id = create_tree(tree, element, parent_id, state, &mut patches, true, dependencies);
19        tree.set_root(node_id);
20        return patches;
21    }
22
23    // Incremental update: reconcile root against existing tree
24    if let Some(root_id) = tree.root() {
25        reconcile_node(tree, root_id, element, state, &mut patches, dependencies);
26    }
27
28    patches
29}
30
31/// Reconcile an IRNode tree against the instance tree and generate patches
32/// This is the primary entry point for the IRNode-based reconciliation system,
33/// which supports first-class ForEach, When/If, and custom item variable names.
34pub fn reconcile_ir(
35    tree: &mut InstanceTree,
36    node: &IRNode,
37    parent_id: Option<NodeId>,
38    state: &serde_json::Value,
39    dependencies: &mut DependencyGraph,
40) -> Vec<Patch> {
41    let mut patches = Vec::new();
42
43    // For initial render, create the tree
44    if tree.root().is_none() {
45        let node_id = create_ir_node_tree(tree, node, parent_id, state, &mut patches, true, dependencies);
46        tree.set_root(node_id);
47        return patches;
48    }
49
50    // Incremental update: reconcile root against existing tree
51    if let Some(root_id) = tree.root() {
52        reconcile_ir_node(tree, root_id, node, state, &mut patches, dependencies);
53    }
54
55    patches
56}
57
58/// Create a new subtree from an element
59pub fn create_tree(
60    tree: &mut InstanceTree,
61    element: &Element,
62    parent_id: Option<NodeId>,
63    state: &serde_json::Value,
64    patches: &mut Vec<Patch>,
65    is_root: bool,
66    dependencies: &mut DependencyGraph,
67) -> NodeId {
68    // Special handling for iterable elements (List, Grid, etc.)
69    // An element is iterable if it has BOTH:
70    // 1. prop "0" with a Binding (like @state.items) - not a static value
71    // 2. children (the template to repeat for each item)
72    // This distinguishes iterables from:
73    // - Text elements (prop "0" but no children)
74    // - Route/Link elements (prop "0" with static path, not a binding)
75    if let Some(Value::Binding(_)) = element.props.get("0") {
76        if !element.children.is_empty() {
77            return create_list_tree(tree, element, parent_id, state, patches, is_root, dependencies);
78        }
79    }
80
81    // Create the node
82    let node_id = tree.create_node(element, state);
83
84    // Register dependencies for this node
85    for value in element.props.values() {
86        match value {
87            Value::Binding(binding) => {
88                dependencies.add_dependency(node_id, binding);
89            }
90            Value::TemplateString { bindings, .. } => {
91                // Register all bindings in the template string
92                for binding in bindings {
93                    dependencies.add_dependency(node_id, binding);
94                }
95            }
96            _ => {}
97        }
98    }
99
100    // Generate Create patch
101    let node = tree.get(node_id).unwrap();
102    
103    // For lazy elements, add child component name as a prop
104    let mut props = node.props.clone();
105    if let Some(Value::Static(val)) = element.props.get("__lazy") {
106        if val.as_bool().unwrap_or(false) && !element.children.is_empty() {
107            // Store the first child's element type so renderer knows what component to load
108            let child_component = &element.children[0].element_type;
109            props.insert("__lazy_child".to_string(), serde_json::json!(child_component));
110        }
111    }
112    
113    patches.push(Patch::create(
114        node_id,
115        node.element_type.clone(),
116        props,
117    ));
118
119    // Event handling is now done at the renderer level
120
121    // If there's a parent, insert this node
122    if let Some(parent) = parent_id {
123        tree.add_child(parent, node_id, None);
124        patches.push(Patch::insert(parent, node_id, None));
125    } else if is_root {
126        // This is the root node - insert into "root" container
127        patches.push(Patch::insert_root(node_id));
128    }
129
130    // Create children (skip if element is marked as lazy)
131    let is_lazy = element.props.get("__lazy")
132        .and_then(|v| {
133            if let Value::Static(val) = v {
134                val.as_bool()
135            } else {
136                None
137            }
138        })
139        .unwrap_or(false);
140    
141    if !is_lazy {
142        for child_element in &element.children {
143            create_tree(tree, child_element, Some(node_id), state, patches, false, dependencies);
144        }
145    }
146
147    node_id
148}
149
150/// Create an iterable element (List, Grid, etc.) that iterates over an array in state
151/// Iterable elements are identified by having prop "0" with an array binding
152fn create_list_tree(
153    tree: &mut InstanceTree,
154    element: &Element,
155    parent_id: Option<NodeId>,
156    state: &serde_json::Value,
157    patches: &mut Vec<Patch>,
158    is_root: bool,
159    dependencies: &mut DependencyGraph,
160) -> NodeId {
161    // Get the array binding from first prop (prop "0")
162    let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
163        evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
164    } else {
165        serde_json::Value::Array(vec![])
166    };
167
168    // Create a container element - use the original element type (List, Grid, etc.)
169    // but remove the "0" prop since it's only for iteration, not rendering
170    let mut list_element = Element::new(&element.element_type);
171    for (key, value) in &element.props {
172        // Skip prop "0" - it's only for array binding, not for rendering
173        if key != "0" {
174            list_element.props.insert(key.clone(), value.clone());
175        }
176    }
177    
178    let node_id = tree.create_node(&list_element, state);
179
180    // Register the List node as depending on the array binding
181    // This is critical for reactive updates when the array changes
182    if let Some(Value::Binding(binding)) = element.props.get("0") {
183        dependencies.add_dependency(node_id, binding);
184    }
185
186    // Store the original element template for re-reconciliation
187    // This allows us to rebuild the list when the array changes
188    if let Some(node) = tree.get_mut(node_id) {
189        node.raw_props = element.props.clone();
190        node.element_template = Some(std::sync::Arc::new(element.clone()));
191    }
192
193    // Generate Create patch for container
194    let node = tree.get(node_id).unwrap();
195    patches.push(Patch::create(
196        node_id,
197        node.element_type.clone(),
198        node.props.clone(),
199    ));
200
201    // Insert container
202    if let Some(parent) = parent_id {
203        tree.add_child(parent, node_id, None);
204        patches.push(Patch::insert(parent, node_id, None));
205    } else if is_root {
206        patches.push(Patch::insert_root(node_id));
207    }
208
209    // If array is empty, return early
210    if let serde_json::Value::Array(items) = &array {
211        // Create children for each item in the array
212        for (index, item) in items.iter().enumerate() {
213            for child_template in &element.children {
214                // Clone child and replace ${item.x} bindings with actual item data
215                let child_with_item = replace_item_bindings(child_template, item, index);
216                create_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
217            }
218        }
219    }
220
221    node_id
222}
223
224/// Navigate a nested path in a JSON value (e.g., "images.0" or "category.name")
225/// Supports both object keys and array indices
226fn navigate_item_path<'a>(item: &'a serde_json::Value, path: &str) -> Option<&'a serde_json::Value> {
227    let mut current = item;
228
229    for segment in path.split('.') {
230        // Try to parse as array index first
231        if let Ok(index) = segment.parse::<usize>() {
232            current = current.get(index)?;
233        } else {
234            // Otherwise treat as object key
235            current = current.get(segment)?;
236        }
237    }
238
239    Some(current)
240}
241
242/// A replacement to be applied: (start_index, end_index, replacement_string)
243/// Using a struct for clarity
244#[derive(Debug)]
245struct Replacement {
246    start: usize,
247    end: usize,
248    text: String,
249}
250
251/// Apply all collected replacements to a string in a single pass.
252/// Builds result by copying segments between replacements - true O(n + m) complexity.
253fn apply_replacements(s: &str, mut replacements: Vec<Replacement>) -> String {
254    if replacements.is_empty() {
255        return s.to_string();
256    }
257
258    // Sort by start position ascending
259    replacements.sort_by(|a, b| a.start.cmp(&b.start));
260
261    // Filter invalid replacements and calculate result size
262    let valid_replacements: Vec<_> = replacements
263        .into_iter()
264        .filter(|r| r.start <= s.len() && r.end <= s.len() && r.start <= r.end)
265        .collect();
266
267    if valid_replacements.is_empty() {
268        return s.to_string();
269    }
270
271    // Calculate total size: original - removed + added
272    let removed: usize = valid_replacements.iter().map(|r| r.end - r.start).sum();
273    let added: usize = valid_replacements.iter().map(|r| r.text.len()).sum();
274    let capacity = s.len() - removed + added;
275
276    let mut result = String::with_capacity(capacity);
277    let mut pos = 0;
278
279    for r in valid_replacements {
280        // Push the segment before this replacement
281        if r.start > pos {
282            result.push_str(&s[pos..r.start]);
283        }
284        // Push the replacement text
285        result.push_str(&r.text);
286        pos = r.end;
287    }
288
289    // Push remaining segment after last replacement
290    if pos < s.len() {
291        result.push_str(&s[pos..]);
292    }
293
294    result
295}
296
297/// Find the end of a path (valid identifier chars: alphanumeric, _, and . for nesting)
298fn find_path_end(s: &str, start: usize) -> usize {
299    let chars: Vec<char> = s[start..].chars().collect();
300    let mut end = start;
301
302    for (i, ch) in chars.iter().enumerate() {
303        if ch.is_alphanumeric() || *ch == '_' {
304            end = start + i + 1;
305        } else if *ch == '.' {
306            // Dot is valid only if followed by alphanumeric or underscore
307            if i + 1 < chars.len() {
308                let next = chars[i + 1];
309                if next.is_alphanumeric() || next == '_' {
310                    end = start + i + 1;
311                    continue;
312                }
313            }
314            break;
315        } else {
316            break;
317        }
318    }
319
320    end
321}
322
323/// Format a JSON value for replacement in expressions
324fn format_value_for_replacement(val: &serde_json::Value, quote_strings: bool) -> String {
325    match val {
326        serde_json::Value::String(s) => {
327            if quote_strings {
328                format!("'{}'", s)
329            } else {
330                s.clone()
331            }
332        }
333        serde_json::Value::Number(n) => n.to_string(),
334        serde_json::Value::Bool(b) => b.to_string(),
335        serde_json::Value::Null => "null".to_string(),
336        _ => serde_json::to_string(val).unwrap_or_default(),
337    }
338}
339
340/// Replace item bindings (Value::Binding with is_item() or TemplateString with item bindings) with actual item values
341/// OPTIMIZED: Uses single-pass replacement instead of O(n²) repeated scans
342/// This is a convenience wrapper that uses "item" as the default item name.
343pub fn replace_item_bindings(element: &Element, item: &serde_json::Value, index: usize) -> Element {
344    replace_item_bindings_with_name(element, item, index, "item")
345}
346
347/// Reconcile a single node with a new element
348pub fn reconcile_node(
349    tree: &mut InstanceTree,
350    node_id: NodeId,
351    element: &Element,
352    state: &serde_json::Value,
353    patches: &mut Vec<Patch>,
354    dependencies: &mut DependencyGraph,
355) {
356    let node = tree.get(node_id).cloned();
357    if node.is_none() {
358        return;
359    }
360    let node = node.unwrap();
361
362    // Special handling for iterable elements (List, Grid, etc.)
363    // An element is iterable if it has BOTH prop "0" (array binding) AND children (template)
364    // This distinguishes iterables from Text elements, which also use prop "0" but have no children
365    let is_iterable = element.props.get("0").is_some() && !element.children.is_empty();
366    
367    if is_iterable {
368        // Get the array binding from first prop (prop "0")
369        let array = if let Some(Value::Binding(binding)) = element.props.get("0") {
370            evaluate_binding(binding, state).unwrap_or(serde_json::Value::Array(vec![]))
371        } else {
372            serde_json::Value::Array(vec![])
373        };
374
375        // Regenerate iterable children
376        if let serde_json::Value::Array(items) = &array {
377            let old_children = node.children.clone();
378            
379            // Calculate expected number of children
380            let expected_children_count = items.len() * element.children.len();
381            
382            // Remove old children if count doesn't match
383            if old_children.len() != expected_children_count {
384                for &old_child_id in &old_children {
385                    patches.push(Patch::remove(old_child_id));
386                }
387                
388                // Clear children from node
389                if let Some(node) = tree.get_mut(node_id) {
390                    node.children.clear();
391                }
392                
393                // Create new children
394                for (index, item) in items.iter().enumerate() {
395                    for child_template in &element.children {
396                        let child_with_item = replace_item_bindings(child_template, item, index);
397                        create_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
398                    }
399                }
400            } else {
401                // Reconcile existing children
402                let mut child_index = 0;
403                for (item_index, item) in items.iter().enumerate() {
404                    for child_template in &element.children {
405                        if let Some(&old_child_id) = old_children.get(child_index) {
406                            let child_with_item = replace_item_bindings(child_template, item, item_index);
407                            reconcile_node(tree, old_child_id, &child_with_item, state, patches, dependencies);
408                        }
409                        child_index += 1;
410                    }
411                }
412            }
413        }
414        
415        return; // Done with List reconciliation
416    }
417
418    // If element type changed, replace the entire subtree
419    if node.element_type != element.element_type {
420        replace_subtree(tree, node_id, &node, element, state, patches, dependencies);
421        return;
422    }
423
424    // Register dependencies for this node (same as create_tree)
425    for value in element.props.values() {
426        if let Value::Binding(binding) = value {
427            dependencies.add_dependency(node_id, binding);
428        }
429    }
430
431    // Diff props
432    let new_props = resolve_props(&element.props, state);
433    let prop_patches = diff_props(node_id, &node.props, &new_props);
434    patches.extend(prop_patches);
435
436    // Update node props in tree
437    if let Some(node) = tree.get_mut(node_id) {
438        node.props = new_props.clone();
439        node.raw_props = element.props.clone();
440    }
441
442    // Reconcile children (skip if element is marked as lazy)
443    let is_lazy = element.props.get("__lazy")
444        .and_then(|v| {
445            if let Value::Static(val) = v {
446                val.as_bool()
447            } else {
448                None
449            }
450        })
451        .unwrap_or(false);
452    
453    if !is_lazy {
454        let old_children = node.children.clone();
455        let new_children = &element.children;
456
457        // Simple strategy: match children by index
458        for (i, new_child_element) in new_children.iter().enumerate() {
459            if let Some(&old_child_id) = old_children.get(i) {
460                // Reconcile existing child
461                reconcile_node(tree, old_child_id, new_child_element, state, patches, dependencies);
462            } else {
463                // Create new child
464                let new_child_id = create_tree(tree, new_child_element, Some(node_id), state, patches, false, dependencies);
465                if let Some(node) = tree.get_mut(node_id) {
466                    node.children.push_back(new_child_id);
467                }
468            }
469        }
470
471        // Remove extra old children
472        if old_children.len() > new_children.len() {
473            for old_child_id in old_children.iter().skip(new_children.len()).copied() {
474                // Collect all nodes in subtree first (for Remove patches)
475                let subtree_ids = collect_subtree_ids(tree, old_child_id);
476                for &id in &subtree_ids {
477                    patches.push(Patch::remove(id));
478                    dependencies.remove_node(id);
479                }
480                // Remove from parent's children and from tree
481                tree.remove_child(node_id, old_child_id);
482                tree.remove(old_child_id);
483            }
484        }
485    }
486}
487
488/// Replace an entire subtree when element types don't match.
489/// This removes the old node and its descendants, then creates a new subtree in its place.
490fn replace_subtree(
491    tree: &mut InstanceTree,
492    old_node_id: NodeId,
493    old_node: &super::InstanceNode,
494    new_element: &Element,
495    state: &serde_json::Value,
496    patches: &mut Vec<Patch>,
497    dependencies: &mut DependencyGraph,
498) {
499    let parent_id = old_node.parent;
500
501    // Remember the position of the old node in its parent's children list
502    let old_position = if let Some(pid) = parent_id {
503        tree.get(pid).and_then(|parent| {
504            parent.children.iter().position(|&id| id == old_node_id)
505        })
506    } else {
507        None
508    };
509
510    // Collect all node IDs in the old subtree (depth-first, children before parents)
511    let ids_to_remove = collect_subtree_ids(tree, old_node_id);
512
513    // Generate Remove patches and clear dependencies in one pass
514    for &id in &ids_to_remove {
515        patches.push(Patch::remove(id));
516        dependencies.remove_node(id);
517    }
518
519    // Remove old node from parent's children list
520    if let Some(pid) = parent_id {
521        if let Some(parent) = tree.get_mut(pid) {
522            parent.children = parent.children.iter().filter(|&&id| id != old_node_id).copied().collect();
523        }
524    }
525
526    // Remove the old subtree from the tree
527    tree.remove(old_node_id);
528
529    // Create the new subtree
530    let is_root = parent_id.is_none();
531    let new_node_id = create_tree(tree, new_element, parent_id, state, patches, is_root, dependencies);
532
533    // If this was the root, update it
534    if is_root {
535        tree.set_root(new_node_id);
536    } else if let Some(pid) = parent_id {
537        if let Some(pos) = old_position {
538            if let Some(parent) = tree.get_mut(pid) {
539                let current_len = parent.children.len();
540                // create_tree appended at end, so new node is at current_len - 1
541                // We want it at position `pos`
542                if pos < current_len - 1 {
543                    // Pop from end (O(log n) for im::Vector) and insert at correct position
544                    let new_id = parent.children.pop_back().unwrap();
545                    parent.children.insert(pos, new_id);
546
547                    // Get the next sibling for the Move patch
548                    let next_sibling = parent.children.get(pos + 1).copied();
549                    patches.push(Patch::move_node(pid, new_node_id, next_sibling));
550                }
551                // If pos == current_len - 1, it's already in the right place (was last child)
552            }
553        }
554    }
555}
556
557/// Collect all node IDs in a subtree (post-order: children before parents)
558/// Uses iterative approach to avoid stack overflow on deep trees
559fn collect_subtree_ids(tree: &InstanceTree, root_id: NodeId) -> Vec<NodeId> {
560    let mut result = Vec::new();
561    let mut stack: Vec<(NodeId, bool)> = vec![(root_id, false)];
562
563    while let Some((node_id, children_processed)) = stack.pop() {
564        if children_processed {
565            // Children already processed, add this node
566            result.push(node_id);
567        } else {
568            // Push self back with flag, then push children
569            stack.push((node_id, true));
570            if let Some(node) = tree.get(node_id) {
571                // Push children in reverse order so they're processed left-to-right
572                for &child_id in node.children.iter().rev() {
573                    stack.push((child_id, false));
574                }
575            }
576        }
577    }
578
579    result
580}
581
582/// Resolve props by evaluating bindings against state
583fn resolve_props(props: &IndexMap<String, crate::ir::Value>, state: &serde_json::Value) -> IndexMap<String, serde_json::Value> {
584    resolve_props_with_item(props, state, None)
585}
586
587/// Resolve props with optional item context (for list iteration)
588fn resolve_props_with_item(
589    props: &IndexMap<String, crate::ir::Value>,
590    state: &serde_json::Value,
591    item: Option<&serde_json::Value>,
592) -> IndexMap<String, serde_json::Value> {
593    let mut resolved = IndexMap::new();
594
595    for (key, value) in props {
596        let resolved_value = match value {
597            crate::ir::Value::Static(v) => v.clone(),
598            crate::ir::Value::Binding(binding) => {
599                if binding.is_item() {
600                    // Evaluate item binding
601                    if let Some(item_value) = item {
602                        evaluate_item_binding(binding, item_value).unwrap_or(serde_json::Value::Null)
603                    } else {
604                        serde_json::Value::Null
605                    }
606                } else {
607                    // Evaluate state binding
608                    evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
609                }
610            }
611            crate::ir::Value::TemplateString { template, .. } => {
612                // Use expression evaluation for template strings - handles both
613                // simple bindings and complex expressions like ternaries
614                match crate::reactive::evaluate_template_string(template, state, item) {
615                    Ok(result) => serde_json::Value::String(result),
616                    Err(_) => serde_json::Value::String(template.clone()),
617                }
618            }
619            crate::ir::Value::Action(action) => {
620                // Actions are serialized with @ prefix for renderer to detect
621                serde_json::Value::String(format!("@{}", action))
622            }
623        };
624        resolved.insert(key.clone(), resolved_value);
625    }
626
627    resolved
628}
629
630/// Navigate a binding path against a root JSON value.
631/// If the path is empty, returns the root value itself.
632fn evaluate_binding_path(binding: &crate::reactive::Binding, root: &serde_json::Value) -> Option<serde_json::Value> {
633    if binding.path.is_empty() {
634        return Some(root.clone());
635    }
636
637    let mut current = root;
638    for segment in &binding.path {
639        current = current.get(segment)?;
640    }
641    Some(current.clone())
642}
643
644/// Evaluate a binding against state
645fn evaluate_binding(binding: &crate::reactive::Binding, state: &serde_json::Value) -> Option<serde_json::Value> {
646    evaluate_binding_path(binding, state)
647}
648
649/// Evaluate an item binding against the item object
650fn evaluate_item_binding(binding: &crate::reactive::Binding, item: &serde_json::Value) -> Option<serde_json::Value> {
651    evaluate_binding_path(binding, item)
652}
653
654/// Generate a stable key for a list item.
655/// Tries to use a key_path (e.g., "id") from the item, falling back to index-based key.
656fn generate_item_key(item: &serde_json::Value, key_path: Option<&str>, item_name: &str, index: usize) -> String {
657    if let Some(kp) = key_path {
658        item.get(kp)
659            .map(|v| match v {
660                serde_json::Value::String(s) => s.clone(),
661                serde_json::Value::Number(n) => n.to_string(),
662                _ => format!("{}-{}", item_name, index),
663            })
664            .unwrap_or_else(|| format!("{}-{}", item_name, index))
665    } else {
666        format!("{}-{}", item_name, index)
667    }
668}
669
670/// Reconcile children using keys for efficient updates
671/// Uses LIS (Longest Increasing Subsequence) algorithm to minimize DOM moves
672pub fn reconcile_keyed_children(
673    tree: &mut InstanceTree,
674    parent_id: NodeId,
675    old_children: &[NodeId],
676    new_children: &[Element],
677    state: &serde_json::Value,
678    dependencies: &mut DependencyGraph,
679) -> Vec<Patch> {
680    let mut patches = Vec::new();
681
682    // Build a map of old children by key
683    let mut old_keyed: IndexMap<String, NodeId> = IndexMap::new();
684    let mut old_unkeyed: Vec<NodeId> = Vec::new();
685
686    for &child_id in old_children {
687        if let Some(node) = tree.get(child_id) {
688            if let Some(key) = &node.key {
689                old_keyed.insert(key.clone(), child_id);
690            } else {
691                old_unkeyed.push(child_id);
692            }
693        }
694    }
695
696    let mut new_child_ids: Vec<NodeId> = Vec::new();
697    let mut unkeyed_idx = 0;
698
699    // First pass: match children by key or create new ones
700    for new_element in new_children {
701        let new_key = new_element.key.as_ref();
702
703        let child_id = if let Some(key) = new_key {
704            // Try to find matching keyed child
705            if let Some(&old_id) = old_keyed.get(key) {
706                // Found a match - reconcile it
707                reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
708                old_keyed.shift_remove(key); // Mark as used
709                old_id
710            } else {
711                // No match - create new child
712                create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
713            }
714        } else {
715            // Unkeyed child - try to reuse unkeyed old child
716            if let Some(&old_id) = old_unkeyed.get(unkeyed_idx) {
717                reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies);
718                unkeyed_idx += 1;
719                old_id
720            } else {
721                // No unkeyed child available - create new
722                create_tree(tree, new_element, Some(parent_id), state, &mut patches, false, dependencies)
723            }
724        };
725
726        new_child_ids.push(child_id);
727    }
728
729    // Second pass: generate move/insert patches using LIS algorithm
730    // Build list of old positions for children that were reused
731    let mut old_positions: Vec<Option<usize>> = Vec::new();
732    for &new_id in &new_child_ids {
733        let old_pos = old_children.iter().position(|&old_id| old_id == new_id);
734        old_positions.push(old_pos);
735    }
736
737    // Find longest increasing subsequence to minimize moves
738    let lis = longest_increasing_subsequence(&old_positions);
739
740    // Update parent's children list
741    if let Some(parent_node) = tree.get_mut(parent_id) {
742        parent_node.children = new_child_ids.iter().copied().collect();
743    }
744
745    // Generate move/insert patches for children not in LIS
746    for (new_idx, &child_id) in new_child_ids.iter().enumerate() {
747        // Check if this child needs to be moved
748        if !lis.contains(&new_idx) {
749            // Calculate before_id (the next child in the list, or None for last position)
750            let before_id = new_child_ids.get(new_idx + 1).copied();
751            patches.push(Patch::move_node(parent_id, child_id, before_id));
752        }
753    }
754
755    // Third pass: remove old children that weren't reused
756    // Also clean up their dependencies to prevent memory leaks
757    for &old_id in old_keyed.values() {
758        dependencies.remove_node(old_id);
759        tree.remove(old_id);
760        patches.push(Patch::remove(old_id));
761    }
762    for &old_id in &old_unkeyed[unkeyed_idx..] {
763        dependencies.remove_node(old_id);
764        tree.remove(old_id);
765        patches.push(Patch::remove(old_id));
766    }
767
768    patches
769}
770
771/// Find the longest increasing subsequence (LIS) indices
772/// This is used to minimize DOM moves during reconciliation
773/// Returns indices of elements that are already in correct relative order
774fn longest_increasing_subsequence(positions: &[Option<usize>]) -> Vec<usize> {
775    // Extract valid positions with their indices
776    let valid: Vec<(usize, usize)> = positions
777        .iter()
778        .enumerate()
779        .filter_map(|(idx, &pos)| pos.map(|p| (idx, p)))
780        .collect();
781
782    if valid.is_empty() {
783        return Vec::new();
784    }
785
786    // Use dynamic programming to find LIS
787    let n = valid.len();
788    let mut dp: Vec<usize> = vec![1; n];
789    let mut prev: Vec<Option<usize>> = vec![None; n];
790
791    for i in 1..n {
792        for j in 0..i {
793            if valid[j].1 < valid[i].1 && dp[j] + 1 > dp[i] {
794                dp[i] = dp[j] + 1;
795                prev[i] = Some(j);
796            }
797        }
798    }
799
800    // Find the index with maximum LIS length
801    let mut max_idx = 0;
802    for i in 1..n {
803        if dp[i] > dp[max_idx] {
804            max_idx = i;
805        }
806    }
807
808    // Reconstruct the LIS indices
809    let mut result = Vec::new();
810    let mut idx = Some(max_idx);
811    while let Some(i) = idx {
812        result.push(valid[i].0);
813        idx = prev[i];
814    }
815    result.reverse();
816
817    result
818}
819
820/// Diff two sets of props and generate SetProp patches
821pub fn diff_props(
822    node_id: NodeId,
823    old_props: &IndexMap<String, serde_json::Value>,
824    new_props: &IndexMap<String, serde_json::Value>,
825) -> Vec<Patch> {
826    let mut patches = Vec::new();
827
828    // Check for changed or new props
829    for (key, new_value) in new_props {
830        if old_props.get(key) != Some(new_value) {
831            patches.push(Patch::set_prop(node_id, key.clone(), new_value.clone()));
832        }
833    }
834
835    // Note: We don't remove old props, just set new ones
836    // Platforms should handle prop removal if needed
837
838    patches
839}
840
841// ============================================================================
842// IRNode-based reconciliation (first-class control flow constructs)
843// ============================================================================
844
845/// Create a tree from an IRNode (supports ForEach, When/If, and regular elements)
846pub fn create_ir_node_tree(
847    tree: &mut InstanceTree,
848    node: &IRNode,
849    parent_id: Option<NodeId>,
850    state: &serde_json::Value,
851    patches: &mut Vec<Patch>,
852    is_root: bool,
853    dependencies: &mut DependencyGraph,
854) -> NodeId {
855    match node {
856        IRNode::Element(element) => {
857            // Regular element - use existing create_tree logic
858            create_tree(tree, element, parent_id, state, patches, is_root, dependencies)
859        }
860        IRNode::ForEach { source, item_name, key_path, template, props } => {
861            create_foreach_ir_tree(
862                tree,
863                source,
864                item_name,
865                key_path.as_deref(),
866                template,
867                props,
868                node,
869                parent_id,
870                state,
871                patches,
872                is_root,
873                dependencies,
874            )
875        }
876        IRNode::Conditional { value, branches, fallback } => {
877            create_conditional_tree(
878                tree,
879                value,
880                branches,
881                fallback.as_deref(),
882                node,
883                parent_id,
884                state,
885                patches,
886                is_root,
887                dependencies,
888            )
889        }
890    }
891}
892
893/// Create an IRNode tree with separate logical and render parents.
894///
895/// This is used for control flow children where:
896/// - `logical_parent`: Where the node goes in the InstanceTree (the control flow node)
897/// - `render_parent`: Where Insert patches should target (the grandparent)
898///
899/// Control flow nodes are transparent - they exist in the tree for bookkeeping
900/// but don't create DOM elements. Their children render directly into the grandparent.
901fn create_ir_node_tree_with_render_parent(
902    tree: &mut InstanceTree,
903    node: &IRNode,
904    logical_parent: Option<NodeId>,
905    render_parent: Option<NodeId>,
906    state: &serde_json::Value,
907    patches: &mut Vec<Patch>,
908    is_root: bool,
909    dependencies: &mut DependencyGraph,
910) -> NodeId {
911    match node {
912        IRNode::Element(element) => {
913            // Create element and add to logical parent in tree
914            let node_id = tree.create_node(element, state);
915
916            // Add to logical parent's children list
917            if let Some(parent) = logical_parent {
918                tree.add_child(parent, node_id, None);
919            }
920
921            // Emit patches targeting the render parent
922            patches.push(Patch::create(
923                node_id,
924                element.element_type.clone(),
925                tree.get(node_id).map(|n| n.props.clone()).unwrap_or_default(),
926            ));
927
928            if let Some(render_p) = render_parent {
929                patches.push(Patch::insert(render_p, node_id, None));
930            } else if is_root {
931                patches.push(Patch::insert_root(node_id));
932            }
933
934            // Register dependencies
935            for (_, value) in &element.props {
936                if let Value::Binding(binding) = value {
937                    dependencies.add_dependency(node_id, binding);
938                }
939            }
940
941            // Recursively create children - they use this node as both logical and render parent
942            for child in &element.children {
943                let child_ir = IRNode::Element((**child).clone());
944                create_ir_node_tree(tree, &child_ir, Some(node_id), state, patches, false, dependencies);
945            }
946
947            node_id
948        }
949        IRNode::ForEach { source, item_name, key_path, template, props } => {
950            // ForEach within control flow - still uses grandparent for its own children
951            create_foreach_ir_tree(
952                tree,
953                source,
954                item_name,
955                key_path.as_deref(),
956                template,
957                props,
958                node,
959                logical_parent,  // ForEach goes into logical parent
960                state,
961                patches,
962                is_root,
963                dependencies,
964            )
965        }
966        IRNode::Conditional { value, branches, fallback } => {
967            // Nested conditional - also transparent
968            create_conditional_tree(
969                tree,
970                value,
971                branches,
972                fallback.as_deref(),
973                node,
974                logical_parent,  // Conditional goes into logical parent
975                state,
976                patches,
977                is_root,
978                dependencies,
979            )
980        }
981    }
982}
983
984/// Create a ForEach iteration tree from IRNode::ForEach
985fn create_foreach_ir_tree(
986    tree: &mut InstanceTree,
987    source: &Binding,
988    item_name: &str,
989    key_path: Option<&str>,
990    template: &[IRNode],
991    props: &Props,
992    original_node: &IRNode,
993    parent_id: Option<NodeId>,
994    state: &serde_json::Value,
995    patches: &mut Vec<Patch>,
996    is_root: bool,
997    dependencies: &mut DependencyGraph,
998) -> NodeId {
999    // Evaluate the source array binding
1000    let array = evaluate_binding(source, state).unwrap_or(serde_json::Value::Array(vec![]));
1001
1002    // Resolve container props
1003    let resolved_props = resolve_props(props, state);
1004
1005    // Create the ForEach container node (internal bookkeeping only - no DOM element)
1006    let node_id = tree.create_control_flow_node(
1007        "__ForEach",
1008        resolved_props.clone(),
1009        props.clone(),
1010        ControlFlowKind::ForEach {
1011            item_name: item_name.to_string(),
1012            key_path: key_path.map(|s| s.to_string()),
1013        },
1014        original_node.clone(),
1015    );
1016
1017    // Register dependency on the source array binding
1018    dependencies.add_dependency(node_id, source);
1019
1020    // Add to parent's children list (for tree structure) but NO patches for the container itself
1021    // Control flow nodes are transparent - they don't create DOM elements
1022    if let Some(parent) = parent_id {
1023        tree.add_child(parent, node_id, None);
1024    }
1025
1026    // Render children for each item in the array
1027    // Children are inserted into the GRANDPARENT for rendering purposes
1028    let render_parent = parent_id; // Children render directly into ForEach's parent
1029
1030    if let serde_json::Value::Array(items) = &array {
1031        for (index, item) in items.iter().enumerate() {
1032            let item_key = generate_item_key(item, key_path, item_name, index);
1033
1034            // Create each template child with item bindings replaced
1035            for child_template in template {
1036                let child_with_item = replace_ir_node_item_bindings(
1037                    child_template,
1038                    item,
1039                    index,
1040                    item_name,
1041                    &item_key,
1042                );
1043                // Children are added to ForEach node in tree, but patches go to grandparent
1044                let child_id = create_ir_node_tree_with_render_parent(
1045                    tree,
1046                    &child_with_item,
1047                    Some(node_id),  // logical parent (ForEach)
1048                    render_parent,   // render parent (grandparent)
1049                    state,
1050                    patches,
1051                    is_root && render_parent.is_none(),
1052                    dependencies
1053                );
1054                let _ = child_id; // silence unused warning
1055            }
1056        }
1057    }
1058
1059    node_id
1060}
1061
1062/// Create a Conditional (When/If) tree from IRNode::Conditional
1063fn create_conditional_tree(
1064    tree: &mut InstanceTree,
1065    value: &Value,
1066    branches: &[ConditionalBranch],
1067    fallback: Option<&[IRNode]>,
1068    original_node: &IRNode,
1069    parent_id: Option<NodeId>,
1070    state: &serde_json::Value,
1071    patches: &mut Vec<Patch>,
1072    is_root: bool,
1073    dependencies: &mut DependencyGraph,
1074) -> NodeId {
1075    // Evaluate the condition value
1076    let evaluated_value = evaluate_value(value, state);
1077
1078    // Create the Conditional container node (internal bookkeeping only - no DOM element)
1079    let mut raw_props = Props::new();
1080    raw_props.insert("__condition".to_string(), value.clone());
1081
1082    let node_id = tree.create_control_flow_node(
1083        "__Conditional",
1084        IndexMap::new(),
1085        raw_props,
1086        ControlFlowKind::Conditional,
1087        original_node.clone(),
1088    );
1089
1090    // Register dependency on the condition binding
1091    if let Value::Binding(binding) = value {
1092        dependencies.add_dependency(node_id, binding);
1093    } else if let Value::TemplateString { bindings, .. } = value {
1094        for binding in bindings {
1095            dependencies.add_dependency(node_id, binding);
1096        }
1097    }
1098
1099    // Add to parent's children list (for tree structure) but NO patches for the container itself
1100    // Control flow nodes are transparent - they don't create DOM elements
1101    if let Some(parent) = parent_id {
1102        tree.add_child(parent, node_id, None);
1103    }
1104
1105    // Find matching branch
1106    let matched_children = find_matching_branch(&evaluated_value, branches, fallback);
1107
1108    // Render matched children - insert directly into grandparent for rendering
1109    let render_parent = parent_id; // Children render directly into Conditional's parent
1110
1111    if let Some(children) = matched_children {
1112        for child in children {
1113            // Children are added to Conditional node in tree, but patches go to grandparent
1114            create_ir_node_tree_with_render_parent(
1115                tree,
1116                child,
1117                Some(node_id),  // logical parent (Conditional)
1118                render_parent,   // render parent (grandparent)
1119                state,
1120                patches,
1121                is_root && render_parent.is_none(),
1122                dependencies
1123            );
1124        }
1125    }
1126
1127    node_id
1128}
1129
1130/// Find the matching branch for a conditional value
1131fn find_matching_branch<'a>(
1132    evaluated_value: &serde_json::Value,
1133    branches: &'a [ConditionalBranch],
1134    fallback: Option<&'a [IRNode]>,
1135) -> Option<&'a [IRNode]> {
1136    for branch in branches {
1137        if pattern_matches(evaluated_value, &branch.pattern) {
1138            return Some(&branch.children);
1139        }
1140    }
1141
1142    fallback
1143}
1144
1145/// Check if a pattern matches an evaluated value
1146/// Supports:
1147/// - Static values: exact equality matching
1148/// - Bindings: evaluate the binding as a boolean expression
1149/// - TemplateStrings: evaluate as expression with `value` available
1150fn pattern_matches(evaluated_value: &serde_json::Value, pattern: &Value) -> bool {
1151    match pattern {
1152        Value::Static(pattern_value) => {
1153            values_match(evaluated_value, pattern_value)
1154        }
1155        Value::Binding(_binding) => {
1156            // Binding patterns are evaluated as boolean expressions
1157            // The binding itself should resolve to true/false
1158            // This allows: Case(match: @state.isAdmin)
1159            false // Bindings in patterns need state context, handled elsewhere
1160        }
1161        Value::TemplateString { template, .. } => {
1162            // Expression pattern: evaluate with `value` variable available
1163            // This allows: Case(when: "${value > 100}") or Case(match: "${value == 'loading'}")
1164            evaluate_expression_pattern(template, evaluated_value)
1165        }
1166        Value::Action(_) => false, // Actions don't make sense as patterns
1167    }
1168}
1169
1170/// Evaluate an expression pattern against a value
1171/// The expression has access to `value` which is the evaluated When condition
1172fn evaluate_expression_pattern(template: &str, value: &serde_json::Value) -> bool {
1173    use crate::reactive::evaluate_expression;
1174    use std::collections::HashMap;
1175
1176    // Build a context with the value available
1177    let mut context: HashMap<String, serde_json::Value> = HashMap::new();
1178    context.insert("value".to_string(), value.clone());
1179
1180    // Extract the expression from the template (e.g., "${value > 100}" -> "value > 100")
1181    let expr = if template.starts_with("${") && template.ends_with("}") {
1182        &template[2..template.len()-1]
1183    } else if template.contains("${") {
1184        // Template with embedded expression - evaluate the whole thing
1185        // Build a JSON context for evaluate_template_string
1186        let json_context = serde_json::json!({ "value": value });
1187        match evaluate_template_string(template, &json_context, None) {
1188            Ok(result) => {
1189                // Check if result is truthy
1190                return is_truthy_string(&result);
1191            }
1192            Err(_) => return false,
1193        }
1194    } else {
1195        // Plain expression without ${} wrapper
1196        template
1197    };
1198
1199    // Evaluate the expression
1200    match evaluate_expression(expr, &context) {
1201        Ok(result) => is_truthy(&result),
1202        Err(_) => false,
1203    }
1204}
1205
1206/// Check if a string value is truthy
1207fn is_truthy_string(s: &str) -> bool {
1208    !s.is_empty() && s != "false" && s != "0" && s != "null" && s != "undefined"
1209}
1210
1211/// Check if a JSON value is truthy
1212fn is_truthy(value: &serde_json::Value) -> bool {
1213    match value {
1214        serde_json::Value::Null => false,
1215        serde_json::Value::Bool(b) => *b,
1216        serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
1217        serde_json::Value::String(s) => is_truthy_string(s),
1218        serde_json::Value::Array(arr) => !arr.is_empty(),
1219        serde_json::Value::Object(obj) => !obj.is_empty(),
1220    }
1221}
1222
1223/// Check if two JSON values match for conditional branching
1224/// Supports:
1225/// - Exact equality: Case(match: "loading"), Case(match: 42), Case(match: true), Case(match: null)
1226/// - Wildcard patterns: Case(match: "_") or Case(match: "*") matches anything
1227/// - Multiple values: Case(match: ["loading", "pending"]) matches any in the array
1228/// - Expression patterns: Case(match: "${value > 100}")
1229fn values_match(value: &serde_json::Value, pattern: &serde_json::Value) -> bool {
1230    match pattern {
1231        // Array pattern - match any value in the array
1232        serde_json::Value::Array(patterns) => {
1233            patterns.iter().any(|p| values_match(value, p))
1234        }
1235
1236        // String pattern with special syntax
1237        serde_json::Value::String(pattern_str) => {
1238            // Wildcard patterns
1239            if pattern_str == "_" || pattern_str == "*" {
1240                return true;
1241            }
1242
1243            // Expression pattern (${...})
1244            if pattern_str.contains("${") {
1245                return evaluate_expression_pattern(pattern_str, value);
1246            }
1247
1248            // Exact string equality
1249            matches!(value, serde_json::Value::String(s) if s == pattern_str)
1250        }
1251
1252        // Exact equality for non-string primitives
1253        _ => {
1254            if value == pattern {
1255                return true;
1256            }
1257
1258            // Boolean coercion for truthy/falsy matching
1259            match (value, pattern) {
1260                (serde_json::Value::Bool(b), serde_json::Value::Bool(p)) => b == p,
1261                (serde_json::Value::Null, serde_json::Value::Null) => true,
1262                _ => false,
1263            }
1264        }
1265    }
1266}
1267
1268/// Evaluate a Value against state
1269fn evaluate_value(value: &Value, state: &serde_json::Value) -> serde_json::Value {
1270    match value {
1271        Value::Static(v) => v.clone(),
1272        Value::Binding(binding) => {
1273            evaluate_binding(binding, state).unwrap_or(serde_json::Value::Null)
1274        }
1275        Value::TemplateString { template, .. } => {
1276            match evaluate_template_string(template, state, None) {
1277                Ok(result) => serde_json::Value::String(result),
1278                Err(_) => serde_json::Value::String(template.clone()),
1279            }
1280        }
1281        Value::Action(action) => serde_json::Value::String(format!("@{}", action)),
1282    }
1283}
1284
1285/// Replace item bindings in an IRNode with actual item values
1286/// Supports configurable item variable names (e.g., "todo", "user" instead of "item")
1287fn replace_ir_node_item_bindings(
1288    node: &IRNode,
1289    item: &serde_json::Value,
1290    index: usize,
1291    item_name: &str,
1292    item_key: &str,
1293) -> IRNode {
1294    match node {
1295        IRNode::Element(element) => {
1296            let mut new_element = replace_item_bindings_with_name(element, item, index, item_name);
1297            // Override key with the computed item key
1298            new_element.key = Some(item_key.to_string());
1299            IRNode::Element(new_element)
1300        }
1301        IRNode::ForEach { source, item_name: inner_item_name, key_path, template, props } => {
1302            // Note: source binding is kept as-is; nested ForEach maintains its own iteration context
1303            // Replace in props
1304            let new_props = replace_props_item_bindings(props, item, item_name);
1305
1306            // Recursively replace in template (but inner ForEach has its own item context)
1307            let new_template: Vec<IRNode> = template.iter()
1308                .map(|child| replace_ir_node_item_bindings(child, item, index, item_name, item_key))
1309                .collect();
1310
1311            IRNode::ForEach {
1312                source: source.clone(),
1313                item_name: inner_item_name.clone(),
1314                key_path: key_path.clone(),
1315                template: new_template,
1316                props: new_props,
1317            }
1318        }
1319        IRNode::Conditional { value, branches, fallback } => {
1320            // Replace in condition value
1321            let new_value = replace_value_item_bindings(value, item, item_name);
1322
1323            // Replace in branches
1324            let new_branches: Vec<ConditionalBranch> = branches.iter()
1325                .map(|branch| {
1326                    let new_pattern = replace_value_item_bindings(&branch.pattern, item, item_name);
1327                    let new_children: Vec<IRNode> = branch.children.iter()
1328                        .map(|child| replace_ir_node_item_bindings(child, item, index, item_name, item_key))
1329                        .collect();
1330                    ConditionalBranch::new(new_pattern, new_children)
1331                })
1332                .collect();
1333
1334            // Replace in fallback
1335            let new_fallback = fallback.as_ref().map(|f| {
1336                f.iter()
1337                    .map(|child| replace_ir_node_item_bindings(child, item, index, item_name, item_key))
1338                    .collect()
1339            });
1340
1341            IRNode::Conditional {
1342                value: new_value,
1343                branches: new_branches,
1344                fallback: new_fallback,
1345            }
1346        }
1347    }
1348}
1349
1350/// Replace item bindings in Props
1351fn replace_props_item_bindings(props: &Props, item: &serde_json::Value, item_name: &str) -> Props {
1352    let mut new_props = Props::new();
1353    for (key, value) in props {
1354        new_props.insert(key.clone(), replace_value_item_bindings(value, item, item_name));
1355    }
1356    new_props
1357}
1358
1359/// Replace item bindings in a Value
1360/// Uses optimized single-pass replacement to avoid O(n²) string operations.
1361/// `item_name` allows custom iteration variable names (e.g., "todo", "user" instead of "item").
1362fn replace_value_item_bindings(value: &Value, item: &serde_json::Value, item_name: &str) -> Value {
1363    match value {
1364        Value::Binding(binding) => {
1365            if binding.is_item() {
1366                if binding.path.is_empty() {
1367                    Value::Static(item.clone())
1368                } else {
1369                    let path = binding.full_path();
1370                    if let Some(val) = navigate_item_path(item, &path) {
1371                        Value::Static(val.clone())
1372                    } else {
1373                        value.clone()
1374                    }
1375                }
1376            } else {
1377                value.clone()
1378            }
1379        }
1380        Value::TemplateString { template, bindings } => {
1381            let has_item_bindings = bindings.iter().any(|b| b.is_item());
1382            if !has_item_bindings {
1383                return value.clone();
1384            }
1385
1386            // PHASE 1: Collect all replacements for explicit bindings (single pass)
1387            let mut replacements = Vec::new();
1388            for binding in bindings {
1389                if binding.is_item() {
1390                    let pattern = format!("${{{}}}", binding.full_path_with_source());
1391                    if let Some(start) = template.find(&pattern) {
1392                        let replacement = if binding.path.is_empty() {
1393                            format_value_for_replacement(item, false)
1394                        } else if let Some(val) = navigate_item_path(item, &binding.full_path()) {
1395                            format_value_for_replacement(val, false)
1396                        } else {
1397                            continue;
1398                        };
1399                        replacements.push(Replacement {
1400                            start,
1401                            end: start + pattern.len(),
1402                            text: replacement,
1403                        });
1404                    }
1405                }
1406            }
1407
1408            // PHASE 2: Apply explicit binding replacements
1409            let mut result = apply_replacements(template, replacements);
1410
1411            // PHASE 3: Replace item.xxx references in expressions (e.g., ternary operators)
1412            // Uses the configurable item_name for custom iteration variables
1413            let expr_replacements = collect_item_replacements_with_name(&result, item, true, item_name);
1414            result = apply_replacements(&result, expr_replacements);
1415
1416            // Filter out resolved item bindings, keep state bindings
1417            let remaining_bindings: Vec<_> = bindings.iter()
1418                .filter(|b| b.is_state())
1419                .cloned()
1420                .collect();
1421
1422            if remaining_bindings.is_empty() {
1423                if result.contains("${") {
1424                    match evaluate_template_string(&result, &serde_json::Value::Null, None) {
1425                        Ok(evaluated) => Value::Static(serde_json::Value::String(evaluated)),
1426                        Err(_) => Value::Static(serde_json::Value::String(result)),
1427                    }
1428                } else {
1429                    Value::Static(serde_json::Value::String(result))
1430                }
1431            } else {
1432                Value::TemplateString {
1433                    template: result,
1434                    bindings: remaining_bindings,
1435                }
1436            }
1437        }
1438        // Handle static strings containing ${item.xxx} pattern (legacy/fallback)
1439        Value::Static(serde_json::Value::String(s)) if s.contains(&format!("${{{}.", item_name)) || s.contains(&format!("${{{}}}", item_name)) => {
1440            replace_static_item_bindings_with_name(s, item, item_name)
1441        }
1442        _ => value.clone(),
1443    }
1444}
1445
1446/// Find all item.xxx references in a string with configurable item name.
1447fn collect_item_replacements_with_name(s: &str, item: &serde_json::Value, quote_strings: bool, item_name: &str) -> Vec<Replacement> {
1448    let mut replacements = Vec::new();
1449    let mut pos = 0;
1450
1451    while pos < s.len() {
1452        if let Some(rel_start) = s[pos..].find(item_name) {
1453            let abs_start = pos + rel_start;
1454            let after_item = abs_start + item_name.len();
1455
1456            if after_item < s.len() && s.as_bytes()[after_item] == b'.' {
1457                let path_start = after_item + 1;
1458                let path_end = find_path_end(s, path_start);
1459
1460                if path_end > path_start {
1461                    let path = &s[path_start..path_end];
1462                    if let Some(val) = navigate_item_path(item, path) {
1463                        let replacement = format_value_for_replacement(val, quote_strings);
1464                        replacements.push(Replacement {
1465                            start: abs_start,
1466                            end: path_end,
1467                            text: replacement,
1468                        });
1469                    }
1470                }
1471                pos = path_end.max(after_item + 1);
1472            } else {
1473                pos = after_item;
1474            }
1475        } else {
1476            break;
1477        }
1478    }
1479
1480    replacements
1481}
1482
1483/// Replace item bindings in a static string value with configurable item name
1484fn replace_static_item_bindings_with_name(s: &str, item: &serde_json::Value, item_name: &str) -> Value {
1485    let mut replacements = Vec::new();
1486    let mut pos = 0;
1487
1488    while let Some(start) = s[pos..].find("${") {
1489        let abs_start = pos + start;
1490
1491        if let Some(end) = s[abs_start..].find('}') {
1492            let abs_end = abs_start + end;
1493            let content = &s[abs_start + 2..abs_end];
1494
1495            if content == item_name {
1496                // Bare ${item}
1497                replacements.push(Replacement {
1498                    start: abs_start,
1499                    end: abs_end + 1,
1500                    text: format_value_for_replacement(item, false),
1501                });
1502                pos = abs_end + 1;
1503            } else if content.starts_with(&format!("{}.", item_name)) && is_simple_path_with_name(content, item_name) {
1504                // Simple ${item.path}
1505                let path = &content[item_name.len() + 1..];
1506                if let Some(val) = navigate_item_path(item, path) {
1507                    replacements.push(Replacement {
1508                        start: abs_start,
1509                        end: abs_end + 1,
1510                        text: format_value_for_replacement(val, false),
1511                    });
1512                }
1513                pos = abs_end + 1;
1514            } else if content.contains(&format!("{}.", item_name)) || content.contains(&format!("{} ", item_name)) {
1515                // Complex expression - replace item refs within, then evaluate
1516                let expr_replacements = collect_item_replacements_with_name(content, item, true, item_name);
1517                let substituted_content = apply_replacements(content, expr_replacements);
1518                let new_expr = format!("${{{}}}", substituted_content);
1519
1520                match evaluate_template_string(&new_expr, &serde_json::Value::Null, None) {
1521                    Ok(evaluated) => {
1522                        replacements.push(Replacement {
1523                            start: abs_start,
1524                            end: abs_end + 1,
1525                            text: evaluated,
1526                        });
1527                    }
1528                    Err(_) => {}
1529                }
1530                pos = abs_end + 1;
1531            } else {
1532                pos = abs_end + 1;
1533            }
1534        } else {
1535            break;
1536        }
1537    }
1538
1539    let result = apply_replacements(s, replacements);
1540    Value::Static(serde_json::Value::String(result))
1541}
1542
1543/// Check if a string is a simple path with configurable item name
1544fn is_simple_path_with_name(s: &str, item_name: &str) -> bool {
1545    if !s.starts_with(item_name) {
1546        return false;
1547    }
1548
1549    let after_item = &s[item_name.len()..];
1550    if after_item.is_empty() {
1551        return true;
1552    }
1553
1554    if !after_item.starts_with('.') {
1555        return false;
1556    }
1557
1558    after_item[1..].chars().all(|c| c.is_alphanumeric() || c == '_' || c == '.')
1559}
1560
1561/// Replace item bindings in an Element with a configurable item name
1562/// This is the full implementation that supports custom iteration variable names.
1563pub fn replace_item_bindings_with_name(
1564    element: &Element,
1565    item: &serde_json::Value,
1566    index: usize,
1567    item_name: &str,
1568) -> Element {
1569    let mut new_element = element.clone();
1570
1571    // Replace bindings in props using the unified Value replacement logic
1572    let mut new_props = Props::new();
1573    for (key, value) in &element.props {
1574        new_props.insert(key.clone(), replace_value_item_bindings(value, item, item_name));
1575    }
1576    new_element.props = new_props;
1577
1578    // Generate key using the item name
1579    let key = item.get("id")
1580        .and_then(|v| v.as_str().map(|s| s.to_string()).or_else(|| v.as_i64().map(|n| n.to_string())))
1581        .or_else(|| item.get("key").and_then(|v| v.as_str().map(|s| s.to_string())))
1582        .map(|id| format!("{}-{}", item_name, id))
1583        .unwrap_or_else(|| format!("{}-{}", item_name, index));
1584    new_element.key = Some(key);
1585
1586    // Recursively replace in children
1587    new_element.children = element.children.iter()
1588        .map(|child| std::sync::Arc::new(replace_item_bindings_with_name(child, item, index, item_name)))
1589        .collect();
1590
1591    new_element
1592}
1593
1594/// Reconcile an existing tree against a new IRNode
1595pub fn reconcile_ir_node(
1596    tree: &mut InstanceTree,
1597    node_id: NodeId,
1598    node: &IRNode,
1599    state: &serde_json::Value,
1600    patches: &mut Vec<Patch>,
1601    dependencies: &mut DependencyGraph,
1602) {
1603    let existing_node = tree.get(node_id).cloned();
1604    if existing_node.is_none() {
1605        return;
1606    }
1607    let existing = existing_node.unwrap();
1608
1609    match node {
1610        IRNode::Element(element) => {
1611            // Regular element reconciliation
1612            reconcile_node(tree, node_id, element, state, patches, dependencies);
1613        }
1614        IRNode::ForEach { source, item_name, key_path, template, props: _ } => {
1615            // ForEach reconciliation
1616            if !existing.is_foreach() {
1617                // Type mismatch - replace entire subtree
1618                let parent_id = existing.parent;
1619                remove_subtree(tree, node_id, patches, dependencies);
1620                create_ir_node_tree(tree, node, parent_id, state, patches, parent_id.is_none(), dependencies);
1621                return;
1622            }
1623
1624            // Re-evaluate the array
1625            let array = evaluate_binding(source, state).unwrap_or(serde_json::Value::Array(vec![]));
1626
1627            if let serde_json::Value::Array(items) = &array {
1628                let old_children = existing.children.clone();
1629                let expected_children_count = items.len() * template.len();
1630
1631                // If child count changed, rebuild
1632                if old_children.len() != expected_children_count {
1633                    // Remove old children
1634                    for &old_child_id in &old_children {
1635                        patches.push(Patch::remove(old_child_id));
1636                    }
1637
1638                    if let Some(node) = tree.get_mut(node_id) {
1639                        node.children.clear();
1640                    }
1641
1642                    // Create new children
1643                    for (index, item) in items.iter().enumerate() {
1644                        let item_key = generate_item_key(item, key_path.as_deref(), item_name, index);
1645
1646                        for child_template in template {
1647                            let child_with_item = replace_ir_node_item_bindings(
1648                                child_template,
1649                                item,
1650                                index,
1651                                item_name,
1652                                &item_key,
1653                            );
1654                            create_ir_node_tree(tree, &child_with_item, Some(node_id), state, patches, false, dependencies);
1655                        }
1656                    }
1657                } else {
1658                    // Reconcile existing children
1659                    let mut child_index = 0;
1660                    for (item_index, item) in items.iter().enumerate() {
1661                        let item_key = generate_item_key(item, key_path.as_deref(), item_name, item_index);
1662
1663                        for child_template in template {
1664                            if let Some(&old_child_id) = old_children.get(child_index) {
1665                                let child_with_item = replace_ir_node_item_bindings(
1666                                    child_template,
1667                                    item,
1668                                    item_index,
1669                                    item_name,
1670                                    &item_key,
1671                                );
1672                                reconcile_ir_node(tree, old_child_id, &child_with_item, state, patches, dependencies);
1673                            }
1674                            child_index += 1;
1675                        }
1676                    }
1677                }
1678            }
1679        }
1680        IRNode::Conditional { value, branches, fallback } => {
1681            // Conditional reconciliation
1682            if !existing.is_conditional() {
1683                // Type mismatch - replace entire subtree
1684                let parent_id = existing.parent;
1685                remove_subtree(tree, node_id, patches, dependencies);
1686                create_ir_node_tree(tree, node, parent_id, state, patches, parent_id.is_none(), dependencies);
1687                return;
1688            }
1689
1690            // Re-evaluate the condition
1691            let evaluated_value = evaluate_value(value, state);
1692            let matched_children = find_matching_branch(&evaluated_value, branches, fallback.as_deref());
1693
1694            let old_children = existing.children.clone();
1695
1696            // For simplicity, if the branch changed, we remove old children and create new ones
1697            // A more sophisticated approach would diff the branches
1698            if let Some(children) = matched_children {
1699                if old_children.len() != children.len() {
1700                    // Clear old children
1701                    for &old_child_id in &old_children {
1702                        patches.push(Patch::remove(old_child_id));
1703                    }
1704
1705                    if let Some(node) = tree.get_mut(node_id) {
1706                        node.children.clear();
1707                    }
1708
1709                    // Create new children
1710                    for child in children {
1711                        create_ir_node_tree(tree, child, Some(node_id), state, patches, false, dependencies);
1712                    }
1713                } else {
1714                    // Reconcile children
1715                    for (i, child) in children.iter().enumerate() {
1716                        if let Some(&old_child_id) = old_children.get(i) {
1717                            reconcile_ir_node(tree, old_child_id, child, state, patches, dependencies);
1718                        }
1719                    }
1720                }
1721            } else {
1722                // No matched branch - remove all children
1723                for &old_child_id in &old_children {
1724                    patches.push(Patch::remove(old_child_id));
1725                }
1726
1727                if let Some(node) = tree.get_mut(node_id) {
1728                    node.children.clear();
1729                }
1730            }
1731        }
1732    }
1733}
1734
1735/// Remove a subtree and generate Remove patches
1736fn remove_subtree(
1737    tree: &mut InstanceTree,
1738    node_id: NodeId,
1739    patches: &mut Vec<Patch>,
1740    dependencies: &mut DependencyGraph,
1741) {
1742    let ids = collect_subtree_ids(tree, node_id);
1743    for &id in &ids {
1744        patches.push(Patch::remove(id));
1745        dependencies.remove_node(id);
1746    }
1747    tree.remove(node_id);
1748}
1749
1750#[cfg(test)]
1751mod tests {
1752    use super::*;
1753    use crate::ir::Value;
1754    use serde_json::json;
1755
1756    #[test]
1757    fn test_create_simple_tree() {
1758        use crate::reactive::DependencyGraph;
1759
1760        let mut tree = InstanceTree::new();
1761        let mut patches = Vec::new();
1762        let mut dependencies = DependencyGraph::new();
1763
1764        let element = Element::new("Column")
1765            .with_child(Element::new("Text").with_prop("text", Value::Static(json!("Hello"))));
1766
1767        let state = json!({});
1768        create_tree(&mut tree, &element, None, &state, &mut patches, true, &mut dependencies);
1769
1770        // Should create 2 nodes (Column + Text) + 2 Inserts (root + child)
1771        // Create Column, Insert Column into root, Create Text, Insert Text into Column
1772        assert_eq!(patches.len(), 4);
1773
1774        // Verify root insert patch exists
1775        let root_insert = patches.iter().find(|p| {
1776            matches!(p, Patch::Insert { parent_id, .. } if parent_id == "root")
1777        });
1778        assert!(root_insert.is_some(), "Root insert patch should exist");
1779    }
1780
1781    #[test]
1782    fn test_diff_props() {
1783        let node_id = NodeId::default();
1784        let old = indexmap::indexmap! {
1785            "color".to_string() => json!("red"),
1786            "size".to_string() => json!(16),
1787        };
1788        let new = indexmap::indexmap! {
1789            "color".to_string() => json!("blue"),
1790            "size".to_string() => json!(16),
1791        };
1792
1793        let patches = diff_props(node_id, &old, &new);
1794
1795        // Only color changed
1796        assert_eq!(patches.len(), 1);
1797    }
1798
1799    #[test]
1800    fn test_longest_increasing_subsequence_basic() {
1801        // Test case: [0, 1, 2, 3] - already in order, should select all
1802        let positions = vec![Some(0), Some(1), Some(2), Some(3)];
1803        let lis = longest_increasing_subsequence(&positions);
1804        assert_eq!(lis, vec![0, 1, 2, 3]);
1805    }
1806
1807    #[test]
1808    fn test_longest_increasing_subsequence_reverse() {
1809        // Test case: [3, 2, 1, 0] - reversed, should select only one element
1810        let positions = vec![Some(3), Some(2), Some(1), Some(0)];
1811        let lis = longest_increasing_subsequence(&positions);
1812        assert_eq!(lis.len(), 1);
1813    }
1814
1815    #[test]
1816    fn test_longest_increasing_subsequence_mixed() {
1817        // Test case: [0, 3, 1, 2] - should select [0, 1, 2]
1818        let positions = vec![Some(0), Some(3), Some(1), Some(2)];
1819        let lis = longest_increasing_subsequence(&positions);
1820        assert_eq!(lis.len(), 3);
1821        // Should contain indices of 0, 1, 2 in the original array
1822        assert!(lis.contains(&0));
1823        assert!(lis.contains(&2));
1824        assert!(lis.contains(&3));
1825    }
1826
1827    #[test]
1828    fn test_longest_increasing_subsequence_with_new_items() {
1829        // Test case: [None, Some(0), None, Some(1)] - new items mixed with existing
1830        let positions = vec![None, Some(0), None, Some(1)];
1831        let lis = longest_increasing_subsequence(&positions);
1832        assert_eq!(lis, vec![1, 3]); // Only indices with existing positions
1833    }
1834
1835    #[test]
1836    fn test_longest_increasing_subsequence_empty() {
1837        let positions: Vec<Option<usize>> = vec![];
1838        let lis = longest_increasing_subsequence(&positions);
1839        assert_eq!(lis, Vec::<usize>::new());
1840    }
1841
1842    #[test]
1843    fn test_longest_increasing_subsequence_all_new() {
1844        // All new items (no old positions)
1845        let positions = vec![None, None, None];
1846        let lis = longest_increasing_subsequence(&positions);
1847        assert_eq!(lis, Vec::<usize>::new());
1848    }
1849
1850    #[test]
1851    fn test_reconcile_keyed_children_basic() {
1852        let mut tree = InstanceTree::new();
1853        let mut dependencies = DependencyGraph::new();
1854        let state = json!({});
1855
1856        // Create a parent node
1857        let parent = Element::new("Column");
1858        let parent_id = tree.create_node(&parent, &state);
1859
1860        // Create old children
1861        let mut old_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
1862        old_child_1.key = Some("item-1".to_string());
1863        let old_id_1 = tree.create_node(&old_child_1, &state);
1864
1865        let mut old_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
1866        old_child_2.key = Some("item-2".to_string());
1867        let old_id_2 = tree.create_node(&old_child_2, &state);
1868
1869        tree.add_child(parent_id, old_id_1, None);
1870        tree.add_child(parent_id, old_id_2, None);
1871
1872        // New children in reversed order
1873        let mut new_child_2 = Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
1874        new_child_2.key = Some("item-2".to_string());
1875
1876        let mut new_child_1 = Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
1877        new_child_1.key = Some("item-1".to_string());
1878
1879        let new_children = vec![new_child_2, new_child_1];
1880        let old_children = vec![old_id_1, old_id_2];
1881
1882        let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
1883
1884        // Should have move patches but no create/remove patches (children are reused)
1885        let move_count = patches.iter().filter(|p| matches!(p, Patch::Move { .. })).count();
1886        let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
1887        let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
1888
1889        assert_eq!(create_count, 0, "Should not create new children");
1890        assert_eq!(remove_count, 0, "Should not remove any children");
1891        assert!(move_count > 0, "Should have move patches for reordering");
1892    }
1893
1894    #[test]
1895    fn test_reconcile_keyed_children_add_remove() {
1896        let mut tree = InstanceTree::new();
1897        let mut dependencies = DependencyGraph::new();
1898        let state = json!({});
1899
1900        let parent = Element::new("Column");
1901        let parent_id = tree.create_node(&parent, &state);
1902
1903        // Old: item-1, item-2
1904        let mut old_child_1 = Element::new("Text");
1905        old_child_1.key = Some("item-1".to_string());
1906        let old_id_1 = tree.create_node(&old_child_1, &state);
1907
1908        let mut old_child_2 = Element::new("Text");
1909        old_child_2.key = Some("item-2".to_string());
1910        let old_id_2 = tree.create_node(&old_child_2, &state);
1911
1912        tree.add_child(parent_id, old_id_1, None);
1913        tree.add_child(parent_id, old_id_2, None);
1914
1915        // New: item-2, item-3 (removed item-1, added item-3)
1916        let mut new_child_2 = Element::new("Text");
1917        new_child_2.key = Some("item-2".to_string());
1918
1919        let mut new_child_3 = Element::new("Text");
1920        new_child_3.key = Some("item-3".to_string());
1921
1922        let new_children = vec![new_child_2, new_child_3];
1923        let old_children = vec![old_id_1, old_id_2];
1924
1925        let patches = reconcile_keyed_children(&mut tree, parent_id, &old_children, &new_children, &state, &mut dependencies);
1926
1927        let create_count = patches.iter().filter(|p| matches!(p, Patch::Create { .. })).count();
1928        let remove_count = patches.iter().filter(|p| matches!(p, Patch::Remove { .. })).count();
1929
1930        assert_eq!(create_count, 1, "Should create item-3");
1931        assert_eq!(remove_count, 1, "Should remove item-1");
1932    }
1933}