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 child_group_instance_count(
68 &self,
69 parent_path: &[&str],
70 parent_instance: usize,
71 child_group_id: &str,
72 ) -> usize {
73 let Some(parent) = resolve_instance(&self.tree.groups, parent_path, parent_instance) else {
74 return 0;
75 };
76 parent
77 .child_groups
78 .iter()
79 .find(|g| g.group_id == child_group_id)
80 .map(|g| g.repetitions.len())
81 .unwrap_or(0)
82 }
83
84 fn find_segments_in_child_group(
85 &self,
86 segment_id: &str,
87 parent_path: &[&str],
88 parent_instance: usize,
89 child_group_id: &str,
90 child_instance: usize,
91 ) -> Vec<OwnedSegment> {
92 let Some(parent) = resolve_instance(&self.tree.groups, parent_path, parent_instance) else {
93 return Vec::new();
94 };
95 let Some(child_group) = parent
96 .child_groups
97 .iter()
98 .find(|g| g.group_id == child_group_id)
99 else {
100 return Vec::new();
101 };
102 let Some(instance) = child_group.repetitions.get(child_instance) else {
103 return Vec::new();
104 };
105 instance
106 .segments
107 .iter()
108 .enumerate()
109 .filter(|(_, s)| s.tag.eq_ignore_ascii_case(segment_id))
110 .map(|(i, s)| to_owned(s, i as u32))
111 .collect()
112 }
113
114 fn extract_value_in_group(
115 &self,
116 segment_id: &str,
117 element_index: usize,
118 component_index: usize,
119 group_path: &[&str],
120 instance_index: usize,
121 ) -> Option<String> {
122 let instance = resolve_instance(&self.tree.groups, group_path, instance_index)?;
123 let seg = instance
124 .segments
125 .iter()
126 .find(|s| s.tag.eq_ignore_ascii_case(segment_id))?;
127 seg.elements
128 .get(element_index)?
129 .get(component_index)
130 .cloned()
131 }
132}
133
134fn resolve_group<'a>(groups: &'a [AssembledGroup], path: &[&str]) -> Option<&'a AssembledGroup> {
136 if path.is_empty() {
137 return None;
138 }
139 let group = groups.iter().find(|g| g.group_id == path[0])?;
140 if path.len() == 1 {
141 return Some(group);
142 }
143 let instance = group.repetitions.first()?;
145 resolve_group(&instance.child_groups, &path[1..])
146}
147
148fn resolve_instance<'a>(
150 groups: &'a [AssembledGroup],
151 path: &[&str],
152 instance_index: usize,
153) -> Option<&'a AssembledGroupInstance> {
154 let group = resolve_group(groups, path)?;
155 group.repetitions.get(instance_index)
156}
157
158fn to_owned(seg: &AssembledSegment, segment_number: u32) -> OwnedSegment {
159 OwnedSegment {
160 id: seg.tag.clone(),
161 elements: seg.elements.clone(),
162 segment_number,
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::assembler::{
170 AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
171 };
172 use mig_types::navigator::GroupNavigator;
173
174 fn make_seg(tag: &str, elements: Vec<Vec<&str>>) -> AssembledSegment {
175 AssembledSegment {
176 tag: tag.to_string(),
177 elements: elements
178 .into_iter()
179 .map(|e| e.into_iter().map(|c| c.to_string()).collect())
180 .collect(),
181 mig_number: None,
182 }
183 }
184
185 fn tree_with_sg4_sg8() -> AssembledTree {
186 AssembledTree {
193 segments: vec![make_seg("UNH", vec![vec!["001"]])],
194 groups: vec![AssembledGroup {
195 group_id: "SG4".to_string(),
196 repetitions: vec![AssembledGroupInstance {
197 segments: vec![
198 make_seg("IDE", vec![vec!["24", "TX001"]]),
199 make_seg("STS", vec![vec!["E01"], vec![], vec!["A05"]]),
200 ],
201 child_groups: vec![AssembledGroup {
202 group_id: "SG8".to_string(),
203 repetitions: vec![
204 AssembledGroupInstance {
205 segments: vec![
206 make_seg("SEQ", vec![vec!["Z98"]]),
207 make_seg("CCI", vec![vec!["Z30"], vec![], vec!["Z07"]]),
208 ],
209 child_groups: vec![AssembledGroup {
210 group_id: "SG10".to_string(),
211 repetitions: vec![
212 AssembledGroupInstance {
213 segments: vec![
214 make_seg("CCI", vec![vec!["Z23"]]),
215 make_seg("CAV", vec![vec!["Z91", "value1"]]),
216 ],
217 child_groups: vec![],
218 entry_mig_number: None,
219 variant_mig_numbers: vec![],
220 skipped_segments: vec![],
221 },
222 AssembledGroupInstance {
223 segments: vec![
224 make_seg("CCI", vec![vec![""]]),
225 make_seg("CAV", vec![vec!["", "value2"]]),
226 ],
227 child_groups: vec![],
228 entry_mig_number: None,
229 variant_mig_numbers: vec![],
230 skipped_segments: vec![],
231 },
232 ],
233 }],
234 entry_mig_number: None,
235 variant_mig_numbers: vec![],
236 skipped_segments: vec![],
237 },
238 AssembledGroupInstance {
239 segments: vec![
240 make_seg("SEQ", vec![vec!["Z01"]]),
241 make_seg("CCI", vec![vec![""], vec![], vec!["ZC0"]]),
242 ],
243 child_groups: vec![],
244 entry_mig_number: None,
245 variant_mig_numbers: vec![],
246 skipped_segments: vec![],
247 },
248 ],
249 }],
250 entry_mig_number: None,
251 variant_mig_numbers: vec![],
252 skipped_segments: vec![],
253 }],
254 }],
255 post_group_start: 1,
256 inter_group_segments: std::collections::BTreeMap::new(),
257 }
258 }
259
260 #[test]
261 fn test_find_in_sg8_instance_0() {
262 let tree = tree_with_sg4_sg8();
263 let nav = AssembledTreeNavigator::new(&tree);
264 let segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], 0);
265 assert_eq!(segs.len(), 1);
266 assert_eq!(segs[0].get_element(0), "Z98");
267 }
268
269 #[test]
270 fn test_find_in_sg8_instance_1() {
271 let tree = tree_with_sg4_sg8();
272 let nav = AssembledTreeNavigator::new(&tree);
273 let segs = nav.find_segments_in_group("SEQ", &["SG4", "SG8"], 1);
274 assert_eq!(segs.len(), 1);
275 assert_eq!(segs[0].get_element(0), "Z01");
276 }
277
278 #[test]
279 fn test_qualifier_in_group_scoped() {
280 let tree = tree_with_sg4_sg8();
281 let nav = AssembledTreeNavigator::new(&tree);
282 let segs = nav.find_segments_with_qualifier_in_group("CCI", 2, "ZC0", &["SG4", "SG8"], 1);
283 assert_eq!(segs.len(), 1);
284 assert!(nav
286 .find_segments_with_qualifier_in_group("CCI", 2, "ZC0", &["SG4", "SG8"], 0)
287 .is_empty());
288 }
289
290 #[test]
291 fn test_group_instance_count() {
292 let tree = tree_with_sg4_sg8();
293 let nav = AssembledTreeNavigator::new(&tree);
294 assert_eq!(nav.group_instance_count(&["SG4"]), 1);
295 assert_eq!(nav.group_instance_count(&["SG4", "SG8"]), 2);
296 assert_eq!(nav.group_instance_count(&["SG4", "SG5"]), 0);
297 }
298
299 #[test]
300 fn test_find_in_sg4_directly() {
301 let tree = tree_with_sg4_sg8();
302 let nav = AssembledTreeNavigator::new(&tree);
303 let segs = nav.find_segments_in_group("STS", &["SG4"], 0);
304 assert_eq!(segs.len(), 1);
305 }
306
307 #[test]
308 fn test_invalid_path_returns_empty() {
309 let tree = tree_with_sg4_sg8();
310 let nav = AssembledTreeNavigator::new(&tree);
311 assert!(nav.find_segments_in_group("SEQ", &["SG99"], 0).is_empty());
312 assert!(nav
313 .find_segments_in_group("SEQ", &["SG4", "SG8"], 99)
314 .is_empty());
315 assert!(nav.find_segments_in_group("SEQ", &[], 0).is_empty());
316 }
317
318 #[test]
319 fn test_child_group_instance_count() {
320 let tree = tree_with_sg4_sg8();
321 let nav = AssembledTreeNavigator::new(&tree);
322 assert_eq!(
324 nav.child_group_instance_count(&["SG4", "SG8"], 0, "SG10"),
325 2
326 );
327 assert_eq!(
329 nav.child_group_instance_count(&["SG4", "SG8"], 1, "SG10"),
330 0
331 );
332 assert_eq!(
334 nav.child_group_instance_count(&["SG4", "SG8"], 0, "SG12"),
335 0
336 );
337 }
338
339 #[test]
340 fn test_find_segments_in_child_group() {
341 let tree = tree_with_sg4_sg8();
342 let nav = AssembledTreeNavigator::new(&tree);
343 let segs = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 0);
345 assert_eq!(segs.len(), 1);
346 assert_eq!(segs[0].get_element(0), "Z23");
347 let segs = nav.find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 1);
349 assert_eq!(segs.len(), 1);
350 assert_eq!(segs[0].get_element(0), "");
351 let segs = nav.find_segments_in_child_group("CAV", &["SG4", "SG8"], 0, "SG10", 0);
353 assert_eq!(segs.len(), 1);
354 }
355
356 #[test]
357 fn test_child_group_invalid_path() {
358 let tree = tree_with_sg4_sg8();
359 let nav = AssembledTreeNavigator::new(&tree);
360 assert_eq!(nav.child_group_instance_count(&["SG99"], 0, "SG10"), 0);
362 assert!(nav
364 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 99, "SG10", 0)
365 .is_empty());
366 assert!(nav
368 .find_segments_in_child_group("CCI", &["SG4", "SG8"], 0, "SG10", 99)
369 .is_empty());
370 }
371
372 #[test]
373 fn test_extract_value_in_group() {
374 let tree = tree_with_sg4_sg8();
375 let nav = AssembledTreeNavigator::new(&tree);
376 assert_eq!(
378 nav.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 0),
379 Some("Z98".to_string()),
380 );
381 assert_eq!(
383 nav.extract_value_in_group("SEQ", 0, 0, &["SG4", "SG8"], 1),
384 Some("Z01".to_string()),
385 );
386 }
387
388 #[test]
389 fn test_extract_value_missing() {
390 let tree = tree_with_sg4_sg8();
391 let nav = AssembledTreeNavigator::new(&tree);
392 assert_eq!(
394 nav.extract_value_in_group("LOC", 0, 0, &["SG4", "SG8"], 0),
395 None
396 );
397 assert_eq!(
399 nav.extract_value_in_group("SEQ", 5, 0, &["SG4", "SG8"], 0),
400 None
401 );
402 assert_eq!(
404 nav.extract_value_in_group("SEQ", 0, 5, &["SG4", "SG8"], 0),
405 None
406 );
407 assert_eq!(nav.extract_value_in_group("SEQ", 0, 0, &["SG99"], 0), None);
409 }
410}