1use crate::area::AreaId;
8use std::collections::HashMap;
9
10pub struct PageNumberResolver {
12 id_to_page: HashMap<String, usize>,
14
15 id_to_format: HashMap<String, String>,
17
18 id_to_area: HashMap<String, AreaId>,
20
21 citations: Vec<(AreaId, String)>,
23
24 current_page: usize,
26
27 current_format: String,
29
30 current_grouping_separator: Option<char>,
32
33 current_grouping_size: Option<usize>,
35}
36
37impl PageNumberResolver {
38 pub fn new() -> Self {
40 Self {
41 id_to_page: HashMap::new(),
42 id_to_format: HashMap::new(),
43 id_to_area: HashMap::new(),
44 citations: Vec::new(),
45 current_page: 1,
46 current_format: "1".to_string(),
47 current_grouping_separator: None,
48 current_grouping_size: None,
49 }
50 }
51
52 pub fn set_current_page(&mut self, page: usize) {
54 self.current_page = page;
55 }
56
57 pub fn current_page(&self) -> usize {
59 self.current_page
60 }
61
62 pub fn set_current_format(&mut self, format: String) {
64 self.current_format = format;
65 }
66
67 pub fn current_format(&self) -> &str {
69 &self.current_format
70 }
71
72 pub fn register_element(&mut self, id: String, area_id: AreaId) {
74 self.id_to_page.insert(id.clone(), self.current_page);
75 self.id_to_format
76 .insert(id.clone(), self.current_format.clone());
77 self.id_to_area.insert(id, area_id);
78 }
79
80 pub fn get_format_for_id(&self, id: &str) -> Option<&str> {
82 self.id_to_format.get(id).map(|s| s.as_str())
83 }
84
85 pub fn set_current_grouping_separator(&mut self, sep: Option<char>) {
87 self.current_grouping_separator = sep;
88 }
89
90 pub fn current_grouping_separator(&self) -> Option<char> {
92 self.current_grouping_separator
93 }
94
95 pub fn set_current_grouping_size(&mut self, size: Option<usize>) {
97 self.current_grouping_size = size;
98 }
99
100 pub fn current_grouping_size(&self) -> Option<usize> {
102 self.current_grouping_size
103 }
104
105 pub fn register_citation(&mut self, area_id: AreaId, ref_id: String) {
107 self.citations.push((area_id, ref_id));
108 }
109
110 pub fn get_page_number(&self, ref_id: &str) -> Option<usize> {
112 self.id_to_page.get(ref_id).copied()
113 }
114
115 pub fn get_citations(&self) -> &[(AreaId, String)] {
117 &self.citations
118 }
119
120 pub fn can_resolve_all(&self) -> bool {
122 self.citations
123 .iter()
124 .all(|(_, ref_id)| self.id_to_page.contains_key(ref_id))
125 }
126
127 pub fn unresolved_citations(&self) -> Vec<String> {
129 self.citations
130 .iter()
131 .filter_map(|(_, ref_id)| {
132 if !self.id_to_page.contains_key(ref_id) {
133 Some(ref_id.clone())
134 } else {
135 None
136 }
137 })
138 .collect()
139 }
140
141 pub fn clear(&mut self) {
143 self.id_to_page.clear();
144 self.id_to_format.clear();
145 self.id_to_area.clear();
146 self.citations.clear();
147 self.current_page = 1;
148 self.current_format = "1".to_string();
149 self.current_grouping_separator = None;
150 self.current_grouping_size = None;
151 }
152}
153
154impl Default for PageNumberResolver {
155 fn default() -> Self {
156 Self::new()
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 #[test]
165 fn test_resolver_creation() {
166 let resolver = PageNumberResolver::new();
167 assert_eq!(resolver.current_page(), 1);
168 assert!(resolver.can_resolve_all());
169 }
170
171 #[test]
172 fn test_register_element() {
173 let mut resolver = PageNumberResolver::new();
174 resolver.set_current_page(5);
175
176 let area_id = AreaId::from_index(10);
177 resolver.register_element("chapter1".to_string(), area_id);
178
179 assert_eq!(resolver.get_page_number("chapter1"), Some(5));
180 }
181
182 #[test]
183 fn test_register_citation() {
184 let mut resolver = PageNumberResolver::new();
185 let area_id = AreaId::from_index(20);
186
187 resolver.register_citation(area_id, "chapter1".to_string());
188
189 assert_eq!(resolver.get_citations().len(), 1);
190 assert_eq!(
191 resolver.get_citations()[0],
192 (area_id, "chapter1".to_string())
193 );
194 }
195
196 #[test]
197 fn test_can_resolve_all() {
198 let mut resolver = PageNumberResolver::new();
199 let area_id = AreaId::from_index(10);
200
201 resolver.register_citation(area_id, "chapter1".to_string());
203 assert!(!resolver.can_resolve_all());
204
205 resolver.register_element("chapter1".to_string(), area_id);
207 assert!(resolver.can_resolve_all());
208 }
209
210 #[test]
211 fn test_unresolved_citations() {
212 let mut resolver = PageNumberResolver::new();
213 let area_id = AreaId::from_index(10);
214
215 resolver.register_citation(area_id, "chapter1".to_string());
216 resolver.register_citation(area_id, "chapter2".to_string());
217
218 resolver.register_element("chapter1".to_string(), area_id);
220
221 let unresolved = resolver.unresolved_citations();
222 assert_eq!(unresolved.len(), 1);
223 assert_eq!(unresolved[0], "chapter2");
224 }
225
226 #[test]
227 fn test_clear() {
228 let mut resolver = PageNumberResolver::new();
229 let area_id = AreaId::from_index(10);
230
231 resolver.register_element("chapter1".to_string(), area_id);
232 resolver.register_citation(area_id, "chapter1".to_string());
233 resolver.set_current_page(5);
234
235 resolver.clear();
236
237 assert_eq!(resolver.current_page(), 1);
238 assert_eq!(resolver.get_page_number("chapter1"), None);
239 assert_eq!(resolver.get_citations().len(), 0);
240 }
241}
242
243#[cfg(test)]
244mod extended_tests {
245 use super::*;
246
247 #[test]
248 fn test_resolver_default_same_as_new() {
249 let r1 = PageNumberResolver::new();
250 let r2 = PageNumberResolver::default();
251 assert_eq!(r1.current_page(), r2.current_page());
252 assert_eq!(r1.current_format(), r2.current_format());
253 }
254
255 #[test]
256 fn test_set_current_page() {
257 let mut resolver = PageNumberResolver::new();
258 resolver.set_current_page(10);
259 assert_eq!(resolver.current_page(), 10);
260 }
261
262 #[test]
263 fn test_register_element_tracks_format() {
264 let mut resolver = PageNumberResolver::new();
265 resolver.set_current_page(3);
266 resolver.set_current_format("i".to_string());
267
268 let area_id = AreaId::from_index(5);
269 resolver.register_element("section-2".to_string(), area_id);
270
271 assert_eq!(resolver.get_format_for_id("section-2"), Some("i"));
273 }
274
275 #[test]
276 fn test_get_format_for_unregistered_id_returns_none() {
277 let resolver = PageNumberResolver::new();
278 assert_eq!(resolver.get_format_for_id("nonexistent"), None);
279 }
280
281 #[test]
282 fn test_register_element_multiple_pages() {
283 let mut resolver = PageNumberResolver::new();
284
285 let area1 = AreaId::from_index(1);
286 let area2 = AreaId::from_index(2);
287
288 resolver.set_current_page(1);
289 resolver.register_element("sec-1".to_string(), area1);
290
291 resolver.set_current_page(5);
292 resolver.register_element("sec-5".to_string(), area2);
293
294 assert_eq!(resolver.get_page_number("sec-1"), Some(1));
295 assert_eq!(resolver.get_page_number("sec-5"), Some(5));
296 }
297
298 #[test]
299 fn test_get_page_number_for_unregistered_returns_none() {
300 let resolver = PageNumberResolver::new();
301 assert_eq!(resolver.get_page_number("missing"), None);
302 }
303
304 #[test]
305 fn test_can_resolve_all_empty_citations() {
306 let resolver = PageNumberResolver::new();
307 assert!(resolver.can_resolve_all());
309 }
310
311 #[test]
312 fn test_can_resolve_all_with_unresolved() {
313 let mut resolver = PageNumberResolver::new();
314 let area_id = AreaId::from_index(1);
315 resolver.register_citation(area_id, "unknown-id".to_string());
316 assert!(!resolver.can_resolve_all());
317 }
318
319 #[test]
320 fn test_unresolved_citations_empty_when_all_resolved() {
321 let mut resolver = PageNumberResolver::new();
322 let area_id = AreaId::from_index(1);
323 resolver.register_element("para-1".to_string(), area_id);
324 resolver.register_citation(area_id, "para-1".to_string());
325
326 let unresolved = resolver.unresolved_citations();
327 assert!(unresolved.is_empty());
328 }
329
330 #[test]
331 fn test_clear_resets_format_to_default() {
332 let mut resolver = PageNumberResolver::new();
333 resolver.set_current_format("I".to_string());
334 resolver.clear();
335 assert_eq!(resolver.current_format(), "1");
336 }
337
338 #[test]
339 fn test_clear_resets_grouping() {
340 let mut resolver = PageNumberResolver::new();
341 resolver.set_current_grouping_separator(Some(','));
342 resolver.set_current_grouping_size(Some(3));
343 resolver.clear();
344 assert_eq!(resolver.current_grouping_separator(), None);
345 assert_eq!(resolver.current_grouping_size(), None);
346 }
347
348 #[test]
349 fn test_grouping_separator_get_set() {
350 let mut resolver = PageNumberResolver::new();
351 assert_eq!(resolver.current_grouping_separator(), None);
352 resolver.set_current_grouping_separator(Some('.'));
353 assert_eq!(resolver.current_grouping_separator(), Some('.'));
354 }
355
356 #[test]
357 fn test_grouping_size_get_set() {
358 let mut resolver = PageNumberResolver::new();
359 assert_eq!(resolver.current_grouping_size(), None);
360 resolver.set_current_grouping_size(Some(3));
361 assert_eq!(resolver.current_grouping_size(), Some(3));
362 }
363
364 #[test]
365 fn test_multiple_citations_some_resolved_some_not() {
366 let mut resolver = PageNumberResolver::new();
367 let area_id = AreaId::from_index(10);
368
369 resolver.register_element("known".to_string(), area_id);
370 resolver.register_citation(area_id, "known".to_string());
371 resolver.register_citation(area_id, "unknown-a".to_string());
372 resolver.register_citation(area_id, "unknown-b".to_string());
373
374 assert!(!resolver.can_resolve_all());
375
376 let unresolved = resolver.unresolved_citations();
377 assert_eq!(unresolved.len(), 2);
378 assert!(unresolved.contains(&"unknown-a".to_string()));
379 assert!(unresolved.contains(&"unknown-b".to_string()));
380 }
381
382 #[test]
383 fn test_register_citation_count() {
384 let mut resolver = PageNumberResolver::new();
385 let area_id = AreaId::from_index(1);
386
387 resolver.register_citation(area_id, "ref-1".to_string());
388 resolver.register_citation(area_id, "ref-2".to_string());
389 resolver.register_citation(area_id, "ref-3".to_string());
390
391 assert_eq!(resolver.get_citations().len(), 3);
392 }
393
394 #[test]
395 fn test_clear_removes_all_citations() {
396 let mut resolver = PageNumberResolver::new();
397 let area_id = AreaId::from_index(1);
398 resolver.register_citation(area_id, "ref-1".to_string());
399 resolver.register_citation(area_id, "ref-2".to_string());
400
401 resolver.clear();
402 assert_eq!(resolver.get_citations().len(), 0);
403 assert!(resolver.can_resolve_all());
404 }
405}