1use crate::assembler::{AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree};
4use mig_types::navigator::GroupNavigator;
5use mig_types::segment::OwnedSegment;
6
7pub struct AssembledTreeNavigator<'a> {
9 tree: &'a AssembledTree,
10}
11
12impl<'a> AssembledTreeNavigator<'a> {
13 pub fn new(tree: &'a AssembledTree) -> Self {
14 Self { tree }
15 }
16}
17
18impl GroupNavigator for AssembledTreeNavigator<'_> {
19 fn find_segments_in_group(
20 &self,
21 segment_id: &str,
22 group_path: &[&str],
23 instance_index: usize,
24 ) -> Vec<OwnedSegment> {
25 let Some(instance) = resolve_instance(&self.tree.groups, group_path, instance_index) else {
26 return Vec::new();
27 };
28 instance
29 .segments
30 .iter()
31 .enumerate()
32 .filter(|(_, s)| s.tag.eq_ignore_ascii_case(segment_id))
33 .map(|(i, s)| to_owned(s, i as u32))
34 .collect()
35 }
36
37 fn find_segments_with_qualifier_in_group(
38 &self,
39 segment_id: &str,
40 element_index: usize,
41 qualifier: &str,
42 group_path: &[&str],
43 instance_index: usize,
44 ) -> Vec<OwnedSegment> {
45 self.find_segments_in_group(segment_id, group_path, instance_index)
46 .into_iter()
47 .filter(|s| {
48 s.elements
49 .get(element_index)
50 .and_then(|e| e.first())
51 .is_some_and(|v| v == qualifier)
52 })
53 .collect()
54 }
55
56 fn group_instance_count(&self, group_path: &[&str]) -> usize {
57 resolve_group(&self.tree.groups, group_path)
58 .map(|g| g.repetitions.len())
59 .unwrap_or(0)
60 }
61
62 fn has_any_segment_in_group(&self, group_path: &[&str], instance_index: usize) -> bool {
63 resolve_instance(&self.tree.groups, group_path, instance_index)
64 .is_some_and(|inst| !inst.segments.is_empty())
65 }
66
67 fn instance_has_mig_number(
68 &self,
69 group_path: &[&str],
70 instance_index: usize,
71 mig_number: &str,
72 ) -> bool {
73 let Some(inst) = resolve_instance(&self.tree.groups, group_path, instance_index) else {
74 return true;
75 };
76 if !inst.variant_mig_numbers.is_empty() {
77 return inst.variant_mig_numbers.iter().any(|n| n == mig_number);
78 }
79 has_mig_in_instance(inst, mig_number)
80 }
81
82 fn child_group_instance_count(
83 &self,
84 parent_path: &[&str],
85 parent_instance: usize,
86 child_group_id: &str,
87 ) -> usize {
88 let Some(parent) = resolve_instance(&self.tree.groups, parent_path, parent_instance) else {
89 return 0;
90 };
91 parent
92 .child_groups
93 .iter()
94 .find(|g| g.group_id == child_group_id)
95 .map(|g| g.repetitions.len())
96 .unwrap_or(0)
97 }
98
99 fn find_segments_in_child_group(
100 &self,
101 segment_id: &str,
102 parent_path: &[&str],
103 parent_instance: usize,
104 child_group_id: &str,
105 child_instance: usize,
106 ) -> Vec<OwnedSegment> {
107 let Some(parent) = resolve_instance(&self.tree.groups, parent_path, parent_instance) else {
108 return Vec::new();
109 };
110 let Some(child_group) = parent
111 .child_groups
112 .iter()
113 .find(|g| g.group_id == child_group_id)
114 else {
115 return Vec::new();
116 };
117 let Some(instance) = child_group.repetitions.get(child_instance) else {
118 return Vec::new();
119 };
120 instance
121 .segments
122 .iter()
123 .enumerate()
124 .filter(|(_, s)| s.tag.eq_ignore_ascii_case(segment_id))
125 .map(|(i, s)| to_owned(s, i as u32))
126 .collect()
127 }
128
129 fn extract_value_in_group(
130 &self,
131 segment_id: &str,
132 element_index: usize,
133 component_index: usize,
134 group_path: &[&str],
135 instance_index: usize,
136 ) -> Option<String> {
137 let instance = resolve_instance(&self.tree.groups, group_path, instance_index)?;
138 let seg = instance
139 .segments
140 .iter()
141 .find(|s| s.tag.eq_ignore_ascii_case(segment_id))?;
142 seg.elements
143 .get(element_index)?
144 .get(component_index)
145 .cloned()
146 }
147}
148
149fn resolve_group<'a>(groups: &'a [AssembledGroup], path: &[&str]) -> Option<&'a AssembledGroup> {
151 if path.is_empty() {
152 return None;
153 }
154 let group = groups.iter().find(|g| g.group_id == path[0])?;
155 if path.len() == 1 {
156 return Some(group);
157 }
158 let instance = group.repetitions.first()?;
160 resolve_group(&instance.child_groups, &path[1..])
161}
162
163fn resolve_instance<'a>(
165 groups: &'a [AssembledGroup],
166 path: &[&str],
167 instance_index: usize,
168) -> Option<&'a AssembledGroupInstance> {
169 let group = resolve_group(groups, path)?;
170 group.repetitions.get(instance_index)
171}
172
173fn has_mig_in_instance(inst: &AssembledGroupInstance, mig_number: &str) -> bool {
176 if inst
177 .segments
178 .iter()
179 .any(|s| s.mig_number.as_deref() == Some(mig_number))
180 {
181 return true;
182 }
183 inst.child_groups.iter().any(|g| {
184 g.repetitions
185 .iter()
186 .any(|r| has_mig_in_instance(r, mig_number))
187 })
188}
189
190fn to_owned(seg: &AssembledSegment, segment_number: u32) -> OwnedSegment {
191 OwnedSegment {
192 id: seg.tag.clone(),
193 elements: seg.elements.clone(),
194 segment_number,
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201 use crate::assembler::{
202 AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
203 };
204 use mig_types::navigator::GroupNavigator;
205
206 fn make_seg(tag: &str, elements: Vec<Vec<&str>>) -> AssembledSegment {
207 AssembledSegment {
208 tag: tag.to_string(),
209 elements: elements
210 .into_iter()
211 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
212 .collect(),
213 mig_number: None,
214 segment_number: None,
215 }
216 }
217
218 fn tree_with_sg4_sg8() -> AssembledTree {
219 AssembledTree {
226 segments: vec![make_seg("UNH", vec![vec!["001"]])],
227 groups: vec![AssembledGroup {
228 group_id: "SG4".to_string(),
229 repetitions: vec![AssembledGroupInstance {
230 segments: vec![
231 make_seg("IDE", vec![vec!["24", "TX001"]]),
232 make_seg("STS", vec![vec!["E01"], vec![], vec!["A05"]]),
233 ],
234 child_groups: vec![AssembledGroup {
235 group_id: "SG8".to_string(),
236 repetitions: vec![
237 AssembledGroupInstance {
238 segments: vec![
239 make_seg("SEQ", vec![vec!["Z98"]]),
240 make_seg("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
241 ],
242 child_groups: vec![AssembledGroup {
243 group_id: "SG10".to_string(),
244 repetitions: vec![
245 AssembledGroupInstance {
246 segments: vec![
247 make_seg("CCI", vec![vec!["Z23"]]),
248 make_seg("CAV", vec![vec!["Z91", "value1"]]),
249 ],
250 child_groups: vec![],
251 entry_mig_number: None,
252 variant_mig_numbers: vec![],
253 skipped_segments: vec![],
254 skipped_positions: Vec::new(),
255 },
256 AssembledGroupInstance {
257 segments: vec![
258 make_seg("CCI", vec![vec![""]]),
259 make_seg("CAV", vec![vec!["", "value2"]]),
260 ],
261 child_groups: vec![],
262 entry_mig_number: None,
263 variant_mig_numbers: vec![],
264 skipped_segments: vec![],
265 skipped_positions: Vec::new(),
266 },
267 ],
268 }],
269 entry_mig_number: None,
270 variant_mig_numbers: vec![],
271 skipped_segments: vec![],
272 skipped_positions: Vec::new(),
273 },
274 AssembledGroupInstance {
275 segments: vec![
276 make_seg("SEQ", vec![vec!["Z01"]]),
277 make_seg("CCI", vec![vec![""], vec![], vec!["ZC0"]]),
278 ],
279 child_groups: vec![],
280 entry_mig_number: None,
281 variant_mig_numbers: vec![],
282 skipped_segments: vec![],
283 skipped_positions: Vec::new(),
284 },
285 ],
286 }],
287 entry_mig_number: None,
288 variant_mig_numbers: vec![],
289 skipped_segments: vec![],
290 skipped_positions: Vec::new(),
291 }],
292 }],
293 post_group_start: 1,
294 inter_group_segments: std::collections::BTreeMap::new(),
295 }
296 }
297
298 #[test]
299 fn test_find_in_sg8_instance_0() {
300 let tree = tree_with_sg4_sg8();
301 let nav = AssembledTreeNavigator::new(&tree);
302 let segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
303 assert_eq!(segs.len(), 1);
304 assert_eq!(segs[0].get_element(0), "Z98");
305 }
306
307 #[test]
308 fn test_find_in_sg8_instance_1() {
309 let tree = tree_with_sg4_sg8();
310 let nav = AssembledTreeNavigator::new(&tree);
311 let segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], 1);
312 assert_eq!(segs.len(), 1);
313 assert_eq!(segs[0].get_element(0), "Z01");
314 }
315
316 #[test]
317 fn test_qualifier_in_group_scoped() {
318 let tree = tree_with_sg4_sg8();
319 let nav = AssembledTreeNavigator::new(&tree);
320 let segs = nav.find_segments_with_qualifier_in_group("CCI", 2, "ZC0", &["SG4", "SG8"], 1);
321 assert_eq!(segs.len(), 1);
322 assert!(nav
324 .find_segments_with_qualifier_in_group("CCI", 2, "ZC0", &["SG4", "SG8"], 0)
325 .is_empty());
326 }
327
328 #[test]
329 fn test_group_instance_count() {
330 let tree = tree_with_sg4_sg8();
331 let nav = AssembledTreeNavigator::new(&tree);
332 assert_eq!(nav.group_instance_count(&["SG4"]), 1);
333 assert_eq!(nav.group_instance_count(&["SG4", "SG8"]), 2);
334 assert_eq!(nav.group_instance_count(&["SG4", "SG5"]), 0);
335 }
336
337 #[test]
338 fn test_find_in_sg4_directly() {
339 let tree = tree_with_sg4_sg8();
340 let nav = AssembledTreeNavigator::new(&tree);
341 let segs = nav.find_segments_in_group("STS", &["SG4"], 0);
342 assert_eq!(segs.len(), 1);
343 }
344
345 #[test]
346 fn test_invalid_path_returns_empty() {
347 let tree = tree_with_sg4_sg8();
348 let nav = AssembledTreeNavigator::new(&tree);
349 assert!(nav.find_segments_in_group("SEQ", &["SG99"], 0).is_empty());
350 assert!(nav
351 .find_segments_in_group("SEQ", &["SG4", "SG8"], 99)
352 .is_empty());
353 assert!(nav.find_segments_in_group("SEQ", &[], 0).is_empty());
354 }
355
356 #[test]
357 fn test_child_group_instance_count() {
358 let tree = tree_with_sg4_sg8();
359 let nav = AssembledTreeNavigator::new(&tree);
360 assert_eq!(
362 nav.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
363 2
364 );
365 assert_eq!(
367 nav.child_group_instance_count(&["SG4", "SG8"], 1, "SG10"),
368 0
369 );
370 assert_eq!(
372 nav.child_group_instance_count(&["SG4", "SG8"], 0, "SG12"),
373 0
374 );
375 }
376
377 #[test]
378 fn test_find_segments_in_child_group() {
379 let tree = tree_with_sg4_sg8();
380 let nav = AssembledTreeNavigator::new(&tree);
381 let segs = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0);
383 assert_eq!(segs.len(), 1);
384 assert_eq!(segs[0].get_element(0), "Z23");
385 let segs = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 1);
387 assert_eq!(segs.len(), 1);
388 assert_eq!(segs[0].get_element(0), "");
389 let segs = nav.find_segments_in_child_group("CAV", &["SG4", "SG8"], 0, "SG10", 0);
391 assert_eq!(segs.len(), 1);
392 }
393
394 #[test]
395 fn test_child_group_invalid_path() {
396 let tree = tree_with_sg4_sg8();
397 let nav = AssembledTreeNavigator::new(&tree);
398 assert_eq!(nav.child_group_instance_count(&["SG99"], 0, "SG10"), 0);
400 assert!(nav
402 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 99, "SG10", 0)
403 .is_empty());
404 assert!(nav
406 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 99)
407 .is_empty());
408 }
409
410 #[test]
411 fn test_extract_value_in_group() {
412 let tree = tree_with_sg4_sg8();
413 let nav = AssembledTreeNavigator::new(&tree);
414 assert_eq!(
416 nav.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
417 Some("Z98".to_string()),
418 );
419 assert_eq!(
421 nav.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 1),
422 Some("Z01".to_string()),
423 );
424 }
425
426 #[test]
427 fn test_extract_value_missing() {
428 let tree = tree_with_sg4_sg8();
429 let nav = AssembledTreeNavigator::new(&tree);
430 assert_eq!(
432 nav.extract_value_in_group("LOC", 0, 0, &["SG4", "SG8"], 0),
433 None
434 );
435 assert_eq!(
437 nav.extract_value_in_group("SEQ", 5, 0, &["SG4", "SG8"], 0),
438 None
439 );
440 assert_eq!(
442 nav.extract_value_in_group("SEQ", 0, 5, &["SG4", "SG8"], 0),
443 None
444 );
445 assert_eq!(nav.extract_value_in_group("SEQ", 0, 0, &["SG99"], 0), None);
447 }
448}