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 }
215 }
216
217 fn tree_with_sg4_sg8() -> AssembledTree {
218 AssembledTree {
225 segments: vec![make_seg("UNH", vec![vec!["001"]])],
226 groups: vec![AssembledGroup {
227 group_id: "SG4".to_string(),
228 repetitions: vec![AssembledGroupInstance {
229 segments: vec![
230 make_seg("IDE", vec![vec!["24", "TX001"]]),
231 make_seg("STS", vec![vec!["E01"], vec![], vec!["A05"]]),
232 ],
233 child_groups: vec![AssembledGroup {
234 group_id: "SG8".to_string(),
235 repetitions: vec![
236 AssembledGroupInstance {
237 segments: vec![
238 make_seg("SEQ", vec![vec!["Z98"]]),
239 make_seg("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
240 ],
241 child_groups: vec![AssembledGroup {
242 group_id: "SG10".to_string(),
243 repetitions: vec![
244 AssembledGroupInstance {
245 segments: vec![
246 make_seg("CCI", vec![vec!["Z23"]]),
247 make_seg("CAV", vec![vec!["Z91", "value1"]]),
248 ],
249 child_groups: vec![],
250 entry_mig_number: None,
251 variant_mig_numbers: vec![],
252 skipped_segments: vec![],
253 skipped_positions: Vec::new(),
254 },
255 AssembledGroupInstance {
256 segments: vec![
257 make_seg("CCI", vec![vec![""]]),
258 make_seg("CAV", vec![vec!["", "value2"]]),
259 ],
260 child_groups: vec![],
261 entry_mig_number: None,
262 variant_mig_numbers: vec![],
263 skipped_segments: vec![],
264 skipped_positions: Vec::new(),
265 },
266 ],
267 }],
268 entry_mig_number: None,
269 variant_mig_numbers: vec![],
270 skipped_segments: vec![],
271 skipped_positions: Vec::new(),
272 },
273 AssembledGroupInstance {
274 segments: vec![
275 make_seg("SEQ", vec![vec!["Z01"]]),
276 make_seg("CCI", vec![vec![""], vec![], vec!["ZC0"]]),
277 ],
278 child_groups: vec![],
279 entry_mig_number: None,
280 variant_mig_numbers: vec![],
281 skipped_segments: vec![],
282 skipped_positions: Vec::new(),
283 },
284 ],
285 }],
286 entry_mig_number: None,
287 variant_mig_numbers: vec![],
288 skipped_segments: vec![],
289 skipped_positions: Vec::new(),
290 }],
291 }],
292 post_group_start: 1,
293 inter_group_segments: std::collections::BTreeMap::new(),
294 }
295 }
296
297 #[test]
298 fn test_find_in_sg8_instance_0() {
299 let tree = tree_with_sg4_sg8();
300 let nav = AssembledTreeNavigator::new(&tree);
301 let segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
302 assert_eq!(segs.len(), 1);
303 assert_eq!(segs[0].get_element(0), "Z98");
304 }
305
306 #[test]
307 fn test_find_in_sg8_instance_1() {
308 let tree = tree_with_sg4_sg8();
309 let nav = AssembledTreeNavigator::new(&tree);
310 let segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], 1);
311 assert_eq!(segs.len(), 1);
312 assert_eq!(segs[0].get_element(0), "Z01");
313 }
314
315 #[test]
316 fn test_qualifier_in_group_scoped() {
317 let tree = tree_with_sg4_sg8();
318 let nav = AssembledTreeNavigator::new(&tree);
319 let segs = nav.find_segments_with_qualifier_in_group("CCI", 2, "ZC0", &["SG4", "SG8"], 1);
320 assert_eq!(segs.len(), 1);
321 assert!(nav
323 .find_segments_with_qualifier_in_group("CCI", 2, "ZC0", &["SG4", "SG8"], 0)
324 .is_empty());
325 }
326
327 #[test]
328 fn test_group_instance_count() {
329 let tree = tree_with_sg4_sg8();
330 let nav = AssembledTreeNavigator::new(&tree);
331 assert_eq!(nav.group_instance_count(&["SG4"]), 1);
332 assert_eq!(nav.group_instance_count(&["SG4", "SG8"]), 2);
333 assert_eq!(nav.group_instance_count(&["SG4", "SG5"]), 0);
334 }
335
336 #[test]
337 fn test_find_in_sg4_directly() {
338 let tree = tree_with_sg4_sg8();
339 let nav = AssembledTreeNavigator::new(&tree);
340 let segs = nav.find_segments_in_group("STS", &["SG4"], 0);
341 assert_eq!(segs.len(), 1);
342 }
343
344 #[test]
345 fn test_invalid_path_returns_empty() {
346 let tree = tree_with_sg4_sg8();
347 let nav = AssembledTreeNavigator::new(&tree);
348 assert!(nav.find_segments_in_group("SEQ", &["SG99"], 0).is_empty());
349 assert!(nav
350 .find_segments_in_group("SEQ", &["SG4", "SG8"], 99)
351 .is_empty());
352 assert!(nav.find_segments_in_group("SEQ", &[], 0).is_empty());
353 }
354
355 #[test]
356 fn test_child_group_instance_count() {
357 let tree = tree_with_sg4_sg8();
358 let nav = AssembledTreeNavigator::new(&tree);
359 assert_eq!(
361 nav.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
362 2
363 );
364 assert_eq!(
366 nav.child_group_instance_count(&["SG4", "SG8"], 1, "SG10"),
367 0
368 );
369 assert_eq!(
371 nav.child_group_instance_count(&["SG4", "SG8"], 0, "SG12"),
372 0
373 );
374 }
375
376 #[test]
377 fn test_find_segments_in_child_group() {
378 let tree = tree_with_sg4_sg8();
379 let nav = AssembledTreeNavigator::new(&tree);
380 let segs = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0);
382 assert_eq!(segs.len(), 1);
383 assert_eq!(segs[0].get_element(0), "Z23");
384 let segs = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 1);
386 assert_eq!(segs.len(), 1);
387 assert_eq!(segs[0].get_element(0), "");
388 let segs = nav.find_segments_in_child_group("CAV", &["SG4", "SG8"], 0, "SG10", 0);
390 assert_eq!(segs.len(), 1);
391 }
392
393 #[test]
394 fn test_child_group_invalid_path() {
395 let tree = tree_with_sg4_sg8();
396 let nav = AssembledTreeNavigator::new(&tree);
397 assert_eq!(nav.child_group_instance_count(&["SG99"], 0, "SG10"), 0);
399 assert!(nav
401 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 99, "SG10", 0)
402 .is_empty());
403 assert!(nav
405 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 99)
406 .is_empty());
407 }
408
409 #[test]
410 fn test_extract_value_in_group() {
411 let tree = tree_with_sg4_sg8();
412 let nav = AssembledTreeNavigator::new(&tree);
413 assert_eq!(
415 nav.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
416 Some("Z98".to_string()),
417 );
418 assert_eq!(
420 nav.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 1),
421 Some("Z01".to_string()),
422 );
423 }
424
425 #[test]
426 fn test_extract_value_missing() {
427 let tree = tree_with_sg4_sg8();
428 let nav = AssembledTreeNavigator::new(&tree);
429 assert_eq!(
431 nav.extract_value_in_group("LOC", 0, 0, &["SG4", "SG8"], 0),
432 None
433 );
434 assert_eq!(
436 nav.extract_value_in_group("SEQ", 5, 0, &["SG4", "SG8"], 0),
437 None
438 );
439 assert_eq!(
441 nav.extract_value_in_group("SEQ", 0, 5, &["SG4", "SG8"], 0),
442 None
443 );
444 assert_eq!(nav.extract_value_in_group("SEQ", 0, 0, &["SG99"], 0), None);
446 }
447}