1use aviation_wx_core::{ finalize_issues, issue_from_legacy,
2 DecodeResponse, DetailLevel, MessageType, NotamNormalized, NotamParsed,
3};
4use regex::Regex;
5
6pub fn parse_notam(raw: &str) -> (NotamParsed, Vec<String>) {
7 let mut warnings = Vec::new();
8 let normalized_raw = raw.replace("\r\n", "\n").replace('\r', "\n");
9 let raw_lines: Vec<String> = normalized_raw
10 .lines()
11 .map(|line| line.trim().to_string())
12 .filter(|line| !line.is_empty())
13 .collect();
14
15 let upper = normalized_raw.to_ascii_uppercase();
16 let tag_re = Regex::new(r"(Q\)|A\)|B\)|C\)|D\)|E\)|F\)|G\))").unwrap();
17 let mut positions: Vec<(usize, String)> = tag_re
18 .find_iter(&upper)
19 .map(|m| (m.start(), m.as_str().to_string()))
20 .collect();
21 positions.sort_by_key(|(pos, _)| *pos);
22
23 let mut q_line = None;
24 let mut a = None;
25 let mut b = None;
26 let mut c = None;
27 let mut d = None;
28 let mut e = None;
29 let mut f = None;
30 let mut g = None;
31
32 if positions.is_empty() {
33 warnings.push("No NOTAM tags found; parsed as raw lines.".to_string());
34 }
35
36 for idx in 0..positions.len() {
37 let (start, tag) = &positions[idx];
38 let tag_len = tag.len();
39 let end = if idx + 1 < positions.len() {
40 positions[idx + 1].0
41 } else {
42 upper.len()
43 };
44 let content = normalized_raw
45 .get(start + tag_len..end)
46 .unwrap_or("")
47 .trim()
48 .to_string();
49 match tag.as_str() {
50 "Q)" => q_line = if content.is_empty() { None } else { Some(content) },
51 "A)" => a = if content.is_empty() { None } else { Some(content) },
52 "B)" => b = if content.is_empty() { None } else { Some(content) },
53 "C)" => c = if content.is_empty() { None } else { Some(content) },
54 "D)" => d = if content.is_empty() { None } else { Some(content) },
55 "E)" => e = if content.is_empty() { None } else { Some(content) },
56 "F)" => f = if content.is_empty() { None } else { Some(content) },
57 "G)" => g = if content.is_empty() { None } else { Some(content) },
58 _ => {}
59 }
60 }
61
62 (
63 NotamParsed {
64 q_line,
65 a,
66 b,
67 c,
68 d,
69 e,
70 f,
71 g,
72 raw_lines,
73 },
74 warnings,
75 )
76}
77
78pub fn normalize_notam(parsed: &NotamParsed) -> NotamNormalized {
79 NotamNormalized {
80 q_line: parsed.q_line.clone(),
81 a: parsed.a.clone(),
82 b: parsed.b.clone(),
83 c: parsed.c.clone(),
84 d: parsed.d.clone(),
85 e: parsed.e.clone(),
86 f: parsed.f.clone(),
87 g: parsed.g.clone(),
88 }
89}
90
91pub fn translate_notam(normalized: &NotamNormalized, detail: DetailLevel, lang: &str) -> String {
92 if lang != "zh-CN" {
93 return "Translation only supports zh-CN for now.".to_string();
94 }
95
96 let mut parts = Vec::new();
97 if let Some(a) = &normalized.a {
98 parts.push(format!("地点 {}", a));
99 }
100 if let Some(b) = &normalized.b {
101 parts.push(format!("生效 {}", b));
102 }
103 if let Some(c) = &normalized.c {
104 parts.push(format!("终止 {}", c));
105 }
106 if let Some(e) = &normalized.e {
107 parts.push(format!("内容 {}", e));
108 }
109 if detail == DetailLevel::Full {
110 if let Some(q) = &normalized.q_line {
111 parts.push(format!("Q 行 {}", q));
112 }
113 if let Some(d) = &normalized.d {
114 parts.push(format!("D {}", d));
115 }
116 if let Some(f) = &normalized.f {
117 parts.push(format!("F {}", f));
118 }
119 if let Some(g) = &normalized.g {
120 parts.push(format!("G {}", g));
121 }
122 }
123
124 if parts.is_empty() {
125 "未提取到常见 NOTAM 字段。".to_string()
126 } else {
127 parts.join(",")
128 }
129}
130
131pub fn decode_notam(raw: &str, detail: DetailLevel, lang: &str) -> DecodeResponse {
132 let (parsed, warnings_legacy_raw) = parse_notam(raw);
133 let normalized = normalize_notam(&parsed);
134 let explain = translate_notam(&normalized, detail, lang);
135
136 let mut warnings: Vec<aviation_wx_core::Issue> =
137 warnings_legacy_raw.iter().map(|item| issue_from_legacy(item)).collect();
138 let mut errors = Vec::new();
139 let (warnings_legacy, errors_legacy) = finalize_issues(&mut warnings, &mut errors);
140
141 DecodeResponse {
142 schema_version: "1.0".to_string(),
143 message_type: MessageType::Notam,
144 requested_type: MessageType::Notam,
145 detected_type: MessageType::Notam,
146 final_type: MessageType::Notam,
147 raw: raw.trim().to_string(),
148 parsed: Some(aviation_wx_core::ParsedMessage::Notam(parsed)),
149 normalized: Some(aviation_wx_core::NormalizedMessage::Notam(normalized)),
150 explain: Some(explain),
151 warnings,
152 errors,
153 warnings_legacy,
154 errors_legacy,
155 }
156}
157