1use std::collections::{HashMap, HashSet};
2
3use crate::element::SysmlElement;
4use crate::relationship::SysmlRelationship;
5
6struct ImportEdge {
7 target_namespace: String,
8 wildcard: bool,
9 segments: Vec<String>,
10}
11
12fn build_namespace_map(elements: &[SysmlElement]) -> HashMap<String, String> {
13 let mut map = HashMap::new();
14 for elem in elements {
15 if !elem.qualified_name.contains("::")
16 && (elem.kind == "package_definition" || elem.kind == "library_package")
17 {
18 map.insert(
19 elem.qualified_name.clone(),
20 elem.file_path.to_string_lossy().to_string(),
21 );
22 }
23 }
24 map
25}
26
27fn build_export_table(elements: &[SysmlElement]) -> HashMap<String, Vec<(String, String)>> {
28 let mut by_file: HashMap<String, Vec<&SysmlElement>> = HashMap::new();
29 for elem in elements {
30 let file = elem.file_path.to_string_lossy().to_string();
31 by_file.entry(file).or_default().push(elem);
32 }
33
34 let mut table = HashMap::new();
35 for (file, elems) in &by_file {
36 let ns = elems
37 .iter()
38 .find(|e| {
39 !e.qualified_name.contains("::")
40 && (e.kind == "package_definition" || e.kind == "library_package")
41 })
42 .map(|e| e.qualified_name.as_str())
43 .unwrap_or("");
44
45 let mut exported = Vec::new();
46 for elem in elems {
47 let qname = &elem.qualified_name;
48 let short = qname.rsplit("::").next().unwrap_or(qname);
49 let is_top_level = if ns.is_empty() {
50 !qname.contains("::")
51 } else {
52 let prefix = format!("{}::", ns);
53 qname.starts_with(&prefix) && !qname[prefix.len()..].contains("::")
54 };
55 if is_top_level && qname.contains("::") {
56 exported.push((short.to_string(), qname.clone()));
57 }
58 }
59 table.insert(file.clone(), exported);
60 }
61 table
62}
63
64fn build_import_graph(relationships: &[SysmlRelationship]) -> HashMap<String, Vec<ImportEdge>> {
65 let mut graph: HashMap<String, Vec<ImportEdge>> = HashMap::new();
66 for rel in relationships {
67 if rel.kind.eq_ignore_ascii_case("import") {
68 if let Some(edge) = parse_import_target(&rel.target) {
69 let file = rel.file_path.to_string_lossy().to_string();
70 graph.entry(file).or_default().push(edge);
71 }
72 }
73 }
74 graph
75}
76
77fn parse_import_target(target: &str) -> Option<ImportEdge> {
78 if target.is_empty() {
79 return None;
80 }
81 let wildcard = target.ends_with("::*");
82 let path = if wildcard {
83 &target[..target.len() - 3]
84 } else {
85 target
86 };
87 let segments: Vec<String> = path.split("::").map(|s| s.to_string()).collect();
88 if segments.is_empty() {
89 return None;
90 }
91 Some(ImportEdge {
92 target_namespace: segments[0].clone(),
93 wildcard,
94 segments,
95 })
96}
97
98fn resolve_file_imports(
99 file: &str,
100 import_graph: &HashMap<String, Vec<ImportEdge>>,
101 namespace_map: &HashMap<String, String>,
102 exports: &HashMap<String, Vec<(String, String)>>,
103 visible: &mut HashMap<String, String>,
104 visited: &mut HashSet<String>,
105 elements: &[SysmlElement],
106) {
107 if visited.contains(file) {
108 return;
109 }
110 visited.insert(file.to_string());
111
112 let edges = match import_graph.get(file) {
113 Some(e) => e,
114 None => return,
115 };
116
117 for edge in edges {
118 let target_file = match namespace_map.get(&edge.target_namespace) {
119 Some(f) => f.clone(),
120 None => continue,
121 };
122
123 if edge.segments.len() == 1 && edge.wildcard {
124 resolve_file_imports(
125 &target_file,
126 import_graph,
127 namespace_map,
128 exports,
129 &mut HashMap::new(),
130 visited,
131 elements,
132 );
133
134 if let Some(target_exports) = exports.get(&target_file) {
135 for (name, qname) in target_exports {
136 visible.insert(name.clone(), qname.clone());
137 }
138 }
139
140 let mut transitive = HashMap::new();
141 let mut tv = visited.clone();
142 resolve_file_imports(
143 &target_file,
144 import_graph,
145 namespace_map,
146 exports,
147 &mut transitive,
148 &mut tv,
149 elements,
150 );
151 for (name, qname) in transitive {
152 visible.entry(name).or_insert(qname);
153 }
154 } else if edge.segments.len() == 1 {
155 continue;
156 } else {
157 let remainder = &edge.segments[1..];
158 resolve_multi_segment(
159 &target_file,
160 remainder,
161 edge.wildcard,
162 exports,
163 elements,
164 visible,
165 );
166 }
167 }
168}
169
170fn resolve_multi_segment(
171 file: &str,
172 segments: &[String],
173 wildcard: bool,
174 exports: &HashMap<String, Vec<(String, String)>>,
175 elements: &[SysmlElement],
176 visible: &mut HashMap<String, String>,
177) {
178 let file_elements: Vec<&SysmlElement> = elements
179 .iter()
180 .filter(|e| e.file_path.to_string_lossy() == file)
181 .collect();
182
183 if segments.len() == 1 && !wildcard {
184 let name = &segments[0];
185 if let Some(file_exports) = exports.get(file) {
186 for (ename, qname) in file_exports {
187 if ename == name {
188 visible.insert(name.clone(), qname.clone());
189 return;
190 }
191 }
192 }
193 } else if segments.len() == 1 && wildcard {
194 let container_name = &segments[0];
195 for elem in &file_elements {
196 let short = elem
197 .qualified_name
198 .rsplit("::")
199 .next()
200 .unwrap_or(&elem.qualified_name);
201 if short == container_name {
202 for member_qname in &elem.members {
203 let member_name = member_qname.rsplit("::").next().unwrap_or(member_qname);
204 visible.insert(member_name.to_string(), member_qname.clone());
205 }
206 return;
207 }
208 }
209 } else if segments.len() >= 2 {
210 let container_name = &segments[0];
211 let sub_segments = &segments[1..];
212
213 for elem in &file_elements {
214 let short = elem
215 .qualified_name
216 .rsplit("::")
217 .next()
218 .unwrap_or(&elem.qualified_name);
219 if short == container_name && sub_segments.len() == 1 && !wildcard {
220 let target_name = &sub_segments[0];
221 for member_qname in &elem.members {
222 let member_name = member_qname.rsplit("::").next().unwrap_or(member_qname);
223 if member_name == target_name {
224 visible.insert(target_name.clone(), member_qname.clone());
225 return;
226 }
227 }
228 }
229 }
230 }
231}
232
233fn qualify_reference(reference: &str, visible: &HashMap<String, String>) -> String {
234 if reference.contains("::") {
235 return reference.to_string();
236 }
237 visible
238 .get(reference)
239 .cloned()
240 .unwrap_or_else(|| reference.to_string())
241}
242
243pub fn resolve_imports(elements: &[SysmlElement], relationships: &mut [SysmlRelationship]) {
244 let namespace_map = build_namespace_map(elements);
245 let exports = build_export_table(elements);
246 let import_graph = build_import_graph(relationships);
247
248 let mut files: HashSet<String> = HashSet::new();
249 for elem in elements {
250 files.insert(elem.file_path.to_string_lossy().to_string());
251 }
252
253 let mut resolved: HashMap<String, HashMap<String, String>> = HashMap::new();
254 for file in &files {
255 let mut visible = HashMap::new();
256 let mut visited = HashSet::new();
257 resolve_file_imports(
258 file,
259 &import_graph,
260 &namespace_map,
261 &exports,
262 &mut visible,
263 &mut visited,
264 elements,
265 );
266 if !visible.is_empty() {
267 resolved.insert(file.clone(), visible);
268 }
269 }
270
271 for rel in relationships.iter_mut() {
272 let file = rel.file_path.to_string_lossy().to_string();
273 if let Some(visible) = resolved.get(&file) {
274 rel.source = qualify_reference(&rel.source, visible);
275 rel.target = qualify_reference(&rel.target, visible);
276 }
277 }
278}