use super::diff::{create_tree, reconcile_node};
use super::{InstanceTree, Patch};
use crate::ir::{Element, NodeId};
use crate::reactive::DependencyGraph;
use indexmap::IndexMap;
type DataSources = indexmap::IndexMap<String, serde_json::Value>;
pub(crate) fn generate_item_key(
item: &serde_json::Value,
key_path: Option<&str>,
item_name: &str,
index: usize,
) -> String {
if let Some(kp) = key_path {
item.get(kp)
.map(|v| match v {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Number(n) => n.to_string(),
_ => format!("{}-{}", item_name, index),
})
.unwrap_or_else(|| format!("{}-{}", item_name, index))
} else {
format!("{}-{}", item_name, index)
}
}
pub fn reconcile_keyed_children(
tree: &mut InstanceTree,
parent_id: NodeId,
old_children: &[NodeId],
new_children: &[Element],
state: &serde_json::Value,
dependencies: &mut DependencyGraph,
data_sources: Option<&DataSources>,
) -> Vec<Patch> {
let mut patches = Vec::new();
let mut old_keyed: IndexMap<String, NodeId> = IndexMap::new();
let mut old_unkeyed: Vec<NodeId> = Vec::new();
for &child_id in old_children {
if let Some(node) = tree.get(child_id) {
if let Some(key) = &node.key {
old_keyed.insert(key.clone(), child_id);
} else {
old_unkeyed.push(child_id);
}
}
}
let mut new_child_ids: Vec<NodeId> = Vec::new();
let mut unkeyed_idx = 0;
for new_element in new_children {
let new_key = new_element.key.as_ref();
let child_id = if let Some(key) = new_key {
if let Some(&old_id) = old_keyed.get(key) {
reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies, data_sources);
old_keyed.shift_remove(key); old_id
} else {
create_tree(
tree,
new_element,
Some(parent_id),
state,
&mut patches,
false,
dependencies,
data_sources,
)
}
} else {
if let Some(&old_id) = old_unkeyed.get(unkeyed_idx) {
reconcile_node(tree, old_id, new_element, state, &mut patches, dependencies, data_sources);
unkeyed_idx += 1;
old_id
} else {
create_tree(
tree,
new_element,
Some(parent_id),
state,
&mut patches,
false,
dependencies,
data_sources,
)
}
};
new_child_ids.push(child_id);
}
let mut old_positions: Vec<Option<usize>> = Vec::new();
for &new_id in &new_child_ids {
let old_pos = old_children.iter().position(|&old_id| old_id == new_id);
old_positions.push(old_pos);
}
let lis = longest_increasing_subsequence(&old_positions);
if let Some(parent_node) = tree.get_mut(parent_id) {
parent_node.children = new_child_ids.iter().copied().collect();
}
for (new_idx, &child_id) in new_child_ids.iter().enumerate() {
if !lis.contains(&new_idx) {
let before_id = new_child_ids.get(new_idx + 1).copied();
patches.push(Patch::move_node(parent_id, child_id, before_id));
}
}
for &old_id in old_keyed.values() {
dependencies.remove_node(old_id);
tree.remove(old_id);
patches.push(Patch::remove(old_id));
}
for &old_id in &old_unkeyed[unkeyed_idx..] {
dependencies.remove_node(old_id);
tree.remove(old_id);
patches.push(Patch::remove(old_id));
}
patches
}
fn longest_increasing_subsequence(positions: &[Option<usize>]) -> Vec<usize> {
let valid: Vec<(usize, usize)> = positions
.iter()
.enumerate()
.filter_map(|(idx, &pos)| pos.map(|p| (idx, p)))
.collect();
if valid.is_empty() {
return Vec::new();
}
let n = valid.len();
let mut tails: Vec<usize> = Vec::with_capacity(n);
let mut prev: Vec<Option<usize>> = vec![None; n];
for i in 0..n {
let val = valid[i].1;
let pos = tails.partition_point(|&t| valid[t].1 < val);
if pos == tails.len() {
tails.push(i);
} else {
tails[pos] = i;
}
if pos > 0 {
prev[i] = Some(tails[pos - 1]);
}
}
let mut result = Vec::with_capacity(tails.len());
let mut idx = Some(*tails.last().unwrap());
while let Some(i) = idx {
result.push(valid[i].0);
idx = prev[i];
}
result.reverse();
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::Value;
use serde_json::json;
#[test]
fn test_longest_increasing_subsequence_basic() {
let positions = vec![Some(0), Some(1), Some(2), Some(3)];
let lis = longest_increasing_subsequence(&positions);
assert_eq!(lis, vec![0, 1, 2, 3]);
}
#[test]
fn test_longest_increasing_subsequence_reverse() {
let positions = vec![Some(3), Some(2), Some(1), Some(0)];
let lis = longest_increasing_subsequence(&positions);
assert_eq!(lis.len(), 1);
}
#[test]
fn test_longest_increasing_subsequence_mixed() {
let positions = vec![Some(0), Some(3), Some(1), Some(2)];
let lis = longest_increasing_subsequence(&positions);
assert_eq!(lis.len(), 3);
assert!(lis.contains(&0));
assert!(lis.contains(&2));
assert!(lis.contains(&3));
}
#[test]
fn test_longest_increasing_subsequence_with_new_items() {
let positions = vec![None, Some(0), None, Some(1)];
let lis = longest_increasing_subsequence(&positions);
assert_eq!(lis, vec![1, 3]); }
#[test]
fn test_longest_increasing_subsequence_empty() {
let positions: Vec<Option<usize>> = vec![];
let lis = longest_increasing_subsequence(&positions);
assert_eq!(lis, Vec::<usize>::new());
}
#[test]
fn test_longest_increasing_subsequence_all_new() {
let positions = vec![None, None, None];
let lis = longest_increasing_subsequence(&positions);
assert_eq!(lis, Vec::<usize>::new());
}
#[test]
fn test_reconcile_keyed_children_basic() {
let mut tree = InstanceTree::new();
let mut dependencies = DependencyGraph::new();
let state = json!({});
let parent = Element::new("Column");
let parent_id = tree.create_node(&parent, &state);
let mut old_child_1 =
Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
old_child_1.key = Some("item-1".to_string());
let old_id_1 = tree.create_node(&old_child_1, &state);
let mut old_child_2 =
Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
old_child_2.key = Some("item-2".to_string());
let old_id_2 = tree.create_node(&old_child_2, &state);
tree.add_child(parent_id, old_id_1, None);
tree.add_child(parent_id, old_id_2, None);
let mut new_child_2 =
Element::new("Text").with_prop("text", Value::Static(json!("Item 2")));
new_child_2.key = Some("item-2".to_string());
let mut new_child_1 =
Element::new("Text").with_prop("text", Value::Static(json!("Item 1")));
new_child_1.key = Some("item-1".to_string());
let new_children = vec![new_child_2, new_child_1];
let old_children = vec![old_id_1, old_id_2];
let patches = reconcile_keyed_children(
&mut tree,
parent_id,
&old_children,
&new_children,
&state,
&mut dependencies,
None,
);
let move_count = patches
.iter()
.filter(|p| matches!(p, Patch::Move { .. }))
.count();
let create_count = patches
.iter()
.filter(|p| matches!(p, Patch::Create { .. }))
.count();
let remove_count = patches
.iter()
.filter(|p| matches!(p, Patch::Remove { .. }))
.count();
assert_eq!(create_count, 0, "Should not create new children");
assert_eq!(remove_count, 0, "Should not remove any children");
assert!(move_count > 0, "Should have move patches for reordering");
}
#[test]
fn test_reconcile_keyed_children_add_remove() {
let mut tree = InstanceTree::new();
let mut dependencies = DependencyGraph::new();
let state = json!({});
let parent = Element::new("Column");
let parent_id = tree.create_node(&parent, &state);
let mut old_child_1 = Element::new("Text");
old_child_1.key = Some("item-1".to_string());
let old_id_1 = tree.create_node(&old_child_1, &state);
let mut old_child_2 = Element::new("Text");
old_child_2.key = Some("item-2".to_string());
let old_id_2 = tree.create_node(&old_child_2, &state);
tree.add_child(parent_id, old_id_1, None);
tree.add_child(parent_id, old_id_2, None);
let mut new_child_2 = Element::new("Text");
new_child_2.key = Some("item-2".to_string());
let mut new_child_3 = Element::new("Text");
new_child_3.key = Some("item-3".to_string());
let new_children = vec![new_child_2, new_child_3];
let old_children = vec![old_id_1, old_id_2];
let patches = reconcile_keyed_children(
&mut tree,
parent_id,
&old_children,
&new_children,
&state,
&mut dependencies,
None,
);
let create_count = patches
.iter()
.filter(|p| matches!(p, Patch::Create { .. }))
.count();
let remove_count = patches
.iter()
.filter(|p| matches!(p, Patch::Remove { .. }))
.count();
assert_eq!(create_count, 1, "Should create item-3");
assert_eq!(remove_count, 1, "Should remove item-1");
}
#[test]
fn test_lis_large_sequence() {
let positions: Vec<Option<usize>> = vec![
Some(3),
Some(1),
Some(4),
Some(1),
Some(5),
Some(9),
Some(2),
Some(6),
];
let lis = longest_increasing_subsequence(&positions);
assert_eq!(lis.len(), 4);
let values: Vec<usize> = lis.iter().map(|&i| positions[i].unwrap()).collect();
for w in values.windows(2) {
assert!(w[0] < w[1], "LIS must be strictly increasing: {:?}", values);
}
}
}