1use std::collections::HashMap;
13
14use panproto_gat::Name;
15use panproto_schema::Edge;
16use serde::{Deserialize, Serialize};
17use smallvec::SmallVec;
18
19use crate::edit_error::EditError;
20use crate::fan::Fan;
21use crate::metadata::Node;
22use crate::value::Value;
23use crate::wtype::WInstance;
24
25#[derive(Clone, Debug, Serialize, Deserialize)]
31pub enum TreeEdit {
32 Identity,
34
35 InsertNode {
37 parent: u32,
39 child_id: u32,
41 node: Node,
43 edge: Edge,
45 },
46
47 DeleteNode {
49 id: u32,
51 },
52
53 ContractNode {
56 id: u32,
58 },
59
60 RelabelNode {
62 id: u32,
64 new_anchor: Name,
66 },
67
68 SetField {
70 node_id: u32,
72 field: Name,
74 value: Value,
76 },
77
78 RemoveField {
80 node_id: u32,
82 field: Name,
84 },
85
86 MoveSubtree {
88 node_id: u32,
90 new_parent: u32,
92 edge: Edge,
94 },
95
96 InsertFan {
98 fan: Fan,
100 },
101
102 DeleteFan {
104 hyper_edge_id: Name,
106 },
107
108 JoinFeatures {
111 primary: u32,
113 joined: Vec<u32>,
115 produce: Node,
117 },
118
119 Sequence(Vec<Self>),
121}
122
123impl TreeEdit {
124 #[must_use]
126 pub const fn identity() -> Self {
127 Self::Identity
128 }
129
130 #[must_use]
134 pub fn compose(self, other: Self) -> Self {
135 let mut steps = Vec::new();
136 flatten_into(&mut steps, self);
137 flatten_into(&mut steps, other);
138 match steps.len() {
139 0 => Self::Identity,
140 1 => steps.into_iter().next().unwrap_or(Self::Identity),
141 _ => Self::Sequence(steps),
142 }
143 }
144
145 #[must_use]
147 pub fn is_identity(&self) -> bool {
148 match self {
149 Self::Identity => true,
150 Self::Sequence(steps) => steps.iter().all(Self::is_identity),
151 _ => false,
152 }
153 }
154
155 pub fn apply(&self, instance: &mut WInstance) -> Result<(), EditError> {
166 match self {
167 Self::Identity => Ok(()),
168
169 Self::InsertNode {
170 parent,
171 child_id,
172 node,
173 edge,
174 } => apply_insert_node(instance, *parent, *child_id, node, edge),
175
176 Self::DeleteNode { id } => apply_delete_node(instance, *id),
177
178 Self::ContractNode { id } => apply_contract_node(instance, *id),
179
180 Self::RelabelNode { id, new_anchor } => {
181 let n = instance
182 .nodes
183 .get_mut(id)
184 .ok_or(EditError::NodeNotFound(*id))?;
185 n.anchor = new_anchor.clone();
186 Ok(())
187 }
188
189 Self::SetField {
190 node_id,
191 field,
192 value,
193 } => {
194 let n = instance
195 .nodes
196 .get_mut(node_id)
197 .ok_or(EditError::NodeNotFound(*node_id))?;
198 n.extra_fields.insert(field.to_string(), value.clone());
199 Ok(())
200 }
201
202 Self::RemoveField { node_id, field } => {
203 let n = instance
204 .nodes
205 .get_mut(node_id)
206 .ok_or(EditError::NodeNotFound(*node_id))?;
207 n.extra_fields.remove(field.as_ref());
208 Ok(())
209 }
210
211 Self::MoveSubtree {
212 node_id,
213 new_parent,
214 edge,
215 } => apply_move_subtree(instance, *node_id, *new_parent, edge),
216
217 Self::InsertFan { fan } => {
218 instance.fans.push(fan.clone());
219 Ok(())
220 }
221
222 Self::DeleteFan { hyper_edge_id } => {
223 let id_str = hyper_edge_id.as_ref();
224 let before = instance.fans.len();
225 instance.fans.retain(|f| f.hyper_edge_id != id_str);
226 if instance.fans.len() == before {
227 return Err(EditError::FanNotFound(id_str.to_owned()));
228 }
229 Ok(())
230 }
231
232 Self::JoinFeatures {
233 primary,
234 joined,
235 produce,
236 } => apply_join_features(instance, *primary, joined, produce),
237
238 Self::Sequence(steps) => {
239 for step in steps {
240 step.apply(instance)?;
241 }
242 Ok(())
243 }
244 }
245 }
246}
247
248fn flatten_into(out: &mut Vec<TreeEdit>, edit: TreeEdit) {
254 match edit {
255 TreeEdit::Identity => {}
256 TreeEdit::Sequence(steps) => {
257 for step in steps {
258 flatten_into(out, step);
259 }
260 }
261 other => out.push(other),
262 }
263}
264
265fn apply_insert_node(
266 instance: &mut WInstance,
267 parent: u32,
268 child_id: u32,
269 node: &Node,
270 edge: &Edge,
271) -> Result<(), EditError> {
272 if !instance.nodes.contains_key(&parent) {
273 return Err(EditError::ParentNotFound(parent));
274 }
275 if instance.nodes.contains_key(&child_id) {
276 return Err(EditError::DuplicateNodeId(child_id));
277 }
278 instance.nodes.insert(child_id, node.clone());
279 let arc = (parent, child_id, edge.clone());
280 instance.arcs.push(arc);
281 instance.parent_map.insert(child_id, parent);
282 instance
283 .children_map
284 .entry(parent)
285 .or_default()
286 .push(child_id);
287 Ok(())
288}
289
290fn apply_delete_node(instance: &mut WInstance, id: u32) -> Result<(), EditError> {
291 if !instance.nodes.contains_key(&id) {
292 return Err(EditError::NodeNotFound(id));
293 }
294 let children = instance.children_map.get(&id);
296 if children.is_some_and(|c| !c.is_empty()) {
297 return Err(EditError::ApplyFailed(format!(
298 "cannot delete non-leaf node {id}; use ContractNode to remove and reattach children"
299 )));
300 }
301 instance.nodes.remove(&id);
302 instance.arcs.retain(|&(_, child, _)| child != id);
304 if let Some(&parent_id) = instance.parent_map.get(&id) {
306 if let Some(siblings) = instance.children_map.get_mut(&parent_id) {
307 siblings.retain(|&c| c != id);
308 }
309 }
310 instance.parent_map.remove(&id);
311 instance.children_map.remove(&id);
312 instance
314 .fans
315 .retain(|f| f.parent != id && !f.children.values().any(|&c| c == id));
316 Ok(())
317}
318
319fn apply_contract_node(instance: &mut WInstance, id: u32) -> Result<(), EditError> {
320 if !instance.nodes.contains_key(&id) {
321 return Err(EditError::NodeNotFound(id));
322 }
323 if id == instance.root {
324 return Err(EditError::ApplyFailed(
325 "cannot contract the root node".to_owned(),
326 ));
327 }
328 let parent_id = instance
329 .parent_map
330 .get(&id)
331 .copied()
332 .ok_or_else(|| EditError::ApplyFailed(format!("node {id} has no parent")))?;
333
334 let children: SmallVec<u32, 4> = instance.children_map.get(&id).cloned().unwrap_or_default();
336
337 let child_edges: HashMap<u32, Edge> = instance
339 .arcs
340 .iter()
341 .filter(|&&(p, _, _)| p == id)
342 .map(|&(_, c, ref e)| (c, e.clone()))
343 .collect();
344
345 let parent_edge: Option<Edge> = instance
347 .arcs
348 .iter()
349 .find(|&&(p, c, _)| p == parent_id && c == id)
350 .map(|(_, _, e)| e.clone());
351
352 instance
354 .arcs
355 .retain(|&(p, c, _)| !((p == parent_id && c == id) || (p == id)));
356
357 for &child_id in &children {
360 let edge = if let Some(mut child_edge) = child_edges.get(&child_id).cloned() {
361 if let Some(parent_node) = instance.nodes.get(&parent_id) {
363 child_edge.src.clone_from(&parent_node.anchor);
364 }
365 child_edge
366 } else if let Some(pe) = parent_edge.clone() {
367 let mut e = pe;
369 if let Some(child_node) = instance.nodes.get(&child_id) {
370 e.tgt.clone_from(&child_node.anchor);
371 }
372 e
373 } else {
374 let src = instance
376 .nodes
377 .get(&parent_id)
378 .map_or_else(|| Name::from(&*id.to_string()), |n| n.anchor.clone());
379 let tgt = instance
380 .nodes
381 .get(&child_id)
382 .map_or_else(|| Name::from(&*child_id.to_string()), |n| n.anchor.clone());
383 Edge {
384 src,
385 tgt,
386 kind: "prop".into(),
387 name: None,
388 }
389 };
390 instance.arcs.push((parent_id, child_id, edge));
391 instance.parent_map.insert(child_id, parent_id);
392 instance
393 .children_map
394 .entry(parent_id)
395 .or_default()
396 .push(child_id);
397 }
398
399 instance.nodes.remove(&id);
401 instance.parent_map.remove(&id);
402 if let Some(siblings) = instance.children_map.get_mut(&parent_id) {
403 siblings.retain(|&c| c != id);
404 }
405 instance.children_map.remove(&id);
406
407 instance
409 .fans
410 .retain(|f| f.parent != id && !f.children.values().any(|&c| c == id));
411
412 Ok(())
413}
414
415fn apply_move_subtree(
416 instance: &mut WInstance,
417 node_id: u32,
418 new_parent: u32,
419 edge: &Edge,
420) -> Result<(), EditError> {
421 if !instance.nodes.contains_key(&node_id) {
422 return Err(EditError::NodeNotFound(node_id));
423 }
424 if !instance.nodes.contains_key(&new_parent) {
425 return Err(EditError::ParentNotFound(new_parent));
426 }
427 if is_descendant(instance, new_parent, node_id) {
429 return Err(EditError::CycleDetected {
430 node_id,
431 new_parent,
432 });
433 }
434
435 let old_parent = instance.parent_map.get(&node_id).copied();
437 instance.arcs.retain(|&(_, child, _)| child != node_id);
438 if let Some(old_p) = old_parent {
439 if let Some(siblings) = instance.children_map.get_mut(&old_p) {
440 siblings.retain(|&c| c != node_id);
441 }
442 }
443
444 instance.arcs.push((new_parent, node_id, edge.clone()));
446 instance.parent_map.insert(node_id, new_parent);
447 instance
448 .children_map
449 .entry(new_parent)
450 .or_default()
451 .push(node_id);
452
453 Ok(())
454}
455
456fn is_descendant(instance: &WInstance, candidate: u32, ancestor: u32) -> bool {
458 let mut current = candidate;
459 while let Some(&parent) = instance.parent_map.get(¤t) {
460 if parent == ancestor {
461 return true;
462 }
463 current = parent;
464 }
465 false
466}
467
468fn apply_join_features(
469 instance: &mut WInstance,
470 primary: u32,
471 joined: &[u32],
472 produce: &Node,
473) -> Result<(), EditError> {
474 if !instance.nodes.contains_key(&primary) {
475 return Err(EditError::NodeNotFound(primary));
476 }
477 for &jid in joined {
478 if !instance.nodes.contains_key(&jid) {
479 return Err(EditError::NodeNotFound(jid));
480 }
481 }
482
483 instance.nodes.insert(primary, produce.clone());
485
486 for &jid in joined {
488 instance.arcs.retain(|&(_, child, _)| child != jid);
490 instance.arcs.retain(|&(parent, _, _)| parent != jid);
491 if let Some(&parent_id) = instance.parent_map.get(&jid) {
493 if let Some(siblings) = instance.children_map.get_mut(&parent_id) {
494 siblings.retain(|&c| c != jid);
495 }
496 }
497 instance.nodes.remove(&jid);
498 instance.parent_map.remove(&jid);
499 instance.children_map.remove(&jid);
500 }
501
502 instance.fans.retain(|f| {
504 !joined.contains(&f.parent) && !f.children.values().any(|c| joined.contains(c))
505 });
506
507 Ok(())
508}
509
510#[cfg(test)]
511#[allow(clippy::unwrap_used, clippy::expect_used, clippy::redundant_clone)]
512mod tests {
513 use std::collections::HashMap;
514
515 use panproto_gat::Name;
516 use panproto_schema::Edge;
517
518 use crate::metadata::Node;
519 use crate::value::Value;
520 use crate::wtype::WInstance;
521
522 use super::TreeEdit;
523
524 fn sample_edge(src: &str, tgt: &str) -> Edge {
525 Edge {
526 src: src.into(),
527 tgt: tgt.into(),
528 kind: "prop".into(),
529 name: None,
530 }
531 }
532
533 fn two_node_instance() -> WInstance {
534 let mut nodes = HashMap::new();
535 nodes.insert(0, Node::new(0, "root"));
536 nodes.insert(1, Node::new(1, "child"));
537 let arcs = vec![(0, 1, sample_edge("root", "child"))];
538 WInstance::new(nodes, arcs, vec![], 0, Name::from("root"))
539 }
540
541 #[test]
542 fn identity_is_noop() {
543 let mut inst = two_node_instance();
544 let before = inst.nodes.len();
545 TreeEdit::identity().apply(&mut inst).unwrap();
546 assert_eq!(inst.nodes.len(), before);
547 }
548
549 #[test]
550 fn insert_then_delete_is_identity() {
551 let mut inst = two_node_instance();
552 let original_count = inst.nodes.len();
553 let node = Node::new(99, "new_child");
554 let edge = sample_edge("root", "new_child");
555
556 let insert = TreeEdit::InsertNode {
557 parent: 0,
558 child_id: 99,
559 node,
560 edge,
561 };
562 let delete = TreeEdit::DeleteNode { id: 99 };
563 let composed = insert.compose(delete);
564 composed.apply(&mut inst).unwrap();
565
566 assert_eq!(inst.nodes.len(), original_count);
567 }
568
569 #[test]
570 fn set_field_updates_value() {
571 let mut inst = two_node_instance();
572 let edit = TreeEdit::SetField {
573 node_id: 1,
574 field: Name::from("color"),
575 value: Value::Str("blue".into()),
576 };
577 edit.apply(&mut inst).unwrap();
578 assert_eq!(
579 inst.nodes[&1].extra_fields.get("color"),
580 Some(&Value::Str("blue".into()))
581 );
582 }
583
584 #[test]
585 fn remove_field() {
586 let mut inst = two_node_instance();
587 inst.nodes
588 .get_mut(&1)
589 .unwrap()
590 .extra_fields
591 .insert("color".into(), Value::Str("red".into()));
592
593 let edit = TreeEdit::RemoveField {
594 node_id: 1,
595 field: Name::from("color"),
596 };
597 edit.apply(&mut inst).unwrap();
598 assert!(!inst.nodes[&1].extra_fields.contains_key("color"));
599 }
600
601 #[test]
602 fn relabel_node() {
603 let mut inst = two_node_instance();
604 let edit = TreeEdit::RelabelNode {
605 id: 1,
606 new_anchor: Name::from("renamed_child"),
607 };
608 edit.apply(&mut inst).unwrap();
609 assert_eq!(inst.nodes[&1].anchor, Name::from("renamed_child"));
610 }
611
612 #[test]
613 fn move_subtree_reparents() {
614 let mut nodes = HashMap::new();
615 nodes.insert(0, Node::new(0, "root"));
616 nodes.insert(1, Node::new(1, "a"));
617 nodes.insert(2, Node::new(2, "b"));
618 let arcs = vec![
619 (0, 1, sample_edge("root", "a")),
620 (0, 2, sample_edge("root", "b")),
621 ];
622 let mut inst = WInstance::new(nodes, arcs, vec![], 0, Name::from("root"));
623
624 let edit = TreeEdit::MoveSubtree {
625 node_id: 2,
626 new_parent: 1,
627 edge: sample_edge("a", "b"),
628 };
629 edit.apply(&mut inst).unwrap();
630
631 assert_eq!(inst.parent_map[&2], 1);
632 assert!(inst.children_map[&1].contains(&2));
633 }
634
635 #[test]
636 fn contract_node_reattaches_children() {
637 let mut nodes = HashMap::new();
638 nodes.insert(0, Node::new(0, "root"));
639 nodes.insert(1, Node::new(1, "middle"));
640 nodes.insert(2, Node::new(2, "leaf"));
641 let arcs = vec![
642 (0, 1, sample_edge("root", "middle")),
643 (1, 2, sample_edge("middle", "leaf")),
644 ];
645 let mut inst = WInstance::new(nodes, arcs, vec![], 0, Name::from("root"));
646
647 let edit = TreeEdit::ContractNode { id: 1 };
648 edit.apply(&mut inst).unwrap();
649
650 assert!(!inst.nodes.contains_key(&1));
651 assert_eq!(inst.parent_map[&2], 0);
652 assert!(inst.children_map[&0].contains(&2));
653 }
654
655 #[test]
656 fn sequence_flattening() {
657 let e1 = TreeEdit::Identity;
658 let e2 = TreeEdit::Sequence(vec![TreeEdit::Identity, TreeEdit::Identity]);
659 let composed = e1.compose(e2);
660 assert!(composed.is_identity());
661 }
662
663 #[test]
664 fn monoid_associativity() {
665 let node_a = Node::new(10, "a");
666 let node_b = Node::new(11, "b");
667 let edge_a = sample_edge("root", "a");
668 let edge_b = sample_edge("root", "b");
669
670 let e1 = TreeEdit::InsertNode {
671 parent: 0,
672 child_id: 10,
673 node: node_a.clone(),
674 edge: edge_a.clone(),
675 };
676 let e2 = TreeEdit::InsertNode {
677 parent: 0,
678 child_id: 11,
679 node: node_b.clone(),
680 edge: edge_b.clone(),
681 };
682 let e3 = TreeEdit::DeleteNode { id: 10 };
683
684 let left = e1.clone().compose(e2.clone()).compose(e3.clone());
686 let right = e1.compose(e2.compose(e3));
688
689 let mut inst_l = two_node_instance();
690 let mut inst_r = two_node_instance();
691 left.apply(&mut inst_l).unwrap();
692 right.apply(&mut inst_r).unwrap();
693
694 assert_eq!(inst_l.nodes.len(), inst_r.nodes.len());
695 for (id, node) in &inst_l.nodes {
696 let other = inst_r.nodes.get(id).expect("node should exist in both");
697 assert_eq!(node.anchor, other.anchor);
698 }
699 }
700
701 #[test]
702 fn monoid_identity_law() {
703 let edit = TreeEdit::SetField {
704 node_id: 1,
705 field: Name::from("x"),
706 value: Value::Int(42),
707 };
708
709 let mut inst1 = two_node_instance();
710 let mut inst2 = two_node_instance();
711
712 TreeEdit::identity()
714 .compose(edit.clone())
715 .apply(&mut inst1)
716 .unwrap();
717 edit.apply(&mut inst2).unwrap();
718
719 assert_eq!(
720 inst1.nodes[&1].extra_fields.get("x"),
721 inst2.nodes[&1].extra_fields.get("x"),
722 );
723 }
724
725 #[test]
726 fn delete_nonexistent_node_fails() {
727 let mut inst = two_node_instance();
728 let err = TreeEdit::DeleteNode { id: 999 }.apply(&mut inst);
729 assert!(err.is_err());
730 }
731
732 #[test]
733 fn insert_duplicate_id_fails() {
734 let mut inst = two_node_instance();
735 let edit = TreeEdit::InsertNode {
736 parent: 0,
737 child_id: 1, node: Node::new(1, "dup"),
739 edge: sample_edge("root", "dup"),
740 };
741 assert!(edit.apply(&mut inst).is_err());
742 }
743
744 #[test]
745 fn move_creates_cycle_fails() {
746 let mut nodes = HashMap::new();
747 nodes.insert(0, Node::new(0, "root"));
748 nodes.insert(1, Node::new(1, "child"));
749 nodes.insert(2, Node::new(2, "grandchild"));
750 let arcs = vec![
751 (0, 1, sample_edge("root", "child")),
752 (1, 2, sample_edge("child", "grandchild")),
753 ];
754 let mut inst = WInstance::new(nodes, arcs, vec![], 0, Name::from("root"));
755
756 let edit = TreeEdit::MoveSubtree {
760 node_id: 1,
761 new_parent: 2,
762 edge: sample_edge("grandchild", "child"),
763 };
764 assert!(edit.apply(&mut inst).is_err());
765 }
766}