infiniloom_engine/
newtypes.rs

1//! Type-safe wrappers for primitive types
2//!
3//! This module provides newtype wrappers that prevent accidentally mixing
4//! different kinds of values (e.g., token counts vs line counts).
5//!
6//! # Example
7//!
8//! ```rust
9//! use infiniloom_engine::newtypes::{TokenCount, LineNumber, ByteOffset};
10//!
11//! let tokens = TokenCount::new(1000);
12//! let line = LineNumber::new(42);
13//!
14//! // These are different types and can't be accidentally mixed
15//! // tokens + line; // This would be a compile error
16//! ```
17
18use serde::{Deserialize, Serialize};
19use std::fmt;
20use std::ops::{Add, AddAssign, Sub, SubAssign};
21
22/// A count of tokens (for LLM context budgeting)
23#[derive(
24    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
25)]
26#[repr(transparent)]
27pub struct TokenCount(u32);
28
29impl TokenCount {
30    /// Create a new token count
31    #[inline]
32    pub const fn new(count: u32) -> Self {
33        Self(count)
34    }
35
36    /// Create a zero token count
37    #[inline]
38    pub const fn zero() -> Self {
39        Self(0)
40    }
41
42    /// Get the inner value
43    #[inline]
44    pub const fn get(self) -> u32 {
45        self.0
46    }
47
48    /// Check if this is zero
49    #[inline]
50    pub const fn is_zero(self) -> bool {
51        self.0 == 0
52    }
53
54    /// Saturating subtraction
55    #[inline]
56    pub const fn saturating_sub(self, rhs: Self) -> Self {
57        Self(self.0.saturating_sub(rhs.0))
58    }
59
60    /// Saturating addition
61    #[inline]
62    pub const fn saturating_add(self, rhs: Self) -> Self {
63        Self(self.0.saturating_add(rhs.0))
64    }
65
66    /// Calculate percentage of another token count
67    #[inline]
68    pub fn percentage_of(self, total: Self) -> f32 {
69        if total.0 == 0 {
70            0.0
71        } else {
72            (self.0 as f32 / total.0 as f32) * 100.0
73        }
74    }
75}
76
77impl Add for TokenCount {
78    type Output = Self;
79
80    #[inline]
81    fn add(self, rhs: Self) -> Self::Output {
82        Self(self.0 + rhs.0)
83    }
84}
85
86impl AddAssign for TokenCount {
87    #[inline]
88    fn add_assign(&mut self, rhs: Self) {
89        self.0 += rhs.0;
90    }
91}
92
93impl Sub for TokenCount {
94    type Output = Self;
95
96    #[inline]
97    fn sub(self, rhs: Self) -> Self::Output {
98        Self(self.0 - rhs.0)
99    }
100}
101
102impl SubAssign for TokenCount {
103    #[inline]
104    fn sub_assign(&mut self, rhs: Self) {
105        self.0 -= rhs.0;
106    }
107}
108
109impl From<u32> for TokenCount {
110    #[inline]
111    fn from(value: u32) -> Self {
112        Self(value)
113    }
114}
115
116impl From<TokenCount> for u32 {
117    #[inline]
118    fn from(value: TokenCount) -> Self {
119        value.0
120    }
121}
122
123impl fmt::Display for TokenCount {
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        write!(f, "{} tokens", self.0)
126    }
127}
128
129impl std::iter::Sum for TokenCount {
130    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
131        iter.fold(Self::zero(), |acc, x| acc + x)
132    }
133}
134
135/// A 1-indexed line number in source code
136#[derive(
137    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
138)]
139#[repr(transparent)]
140pub struct LineNumber(u32);
141
142impl LineNumber {
143    /// Create a new line number (1-indexed)
144    #[inline]
145    pub const fn new(line: u32) -> Self {
146        Self(line)
147    }
148
149    /// Get the first line (line 1)
150    #[inline]
151    pub const fn first() -> Self {
152        Self(1)
153    }
154
155    /// Get the inner value
156    #[inline]
157    pub const fn get(self) -> u32 {
158        self.0
159    }
160
161    /// Check if this is a valid line number (> 0)
162    #[inline]
163    pub const fn is_valid(self) -> bool {
164        self.0 > 0
165    }
166
167    /// Convert to 0-indexed offset
168    #[inline]
169    pub const fn to_zero_indexed(self) -> u32 {
170        self.0.saturating_sub(1)
171    }
172
173    /// Create from 0-indexed offset
174    #[inline]
175    pub const fn from_zero_indexed(offset: u32) -> Self {
176        Self(offset + 1)
177    }
178
179    /// Calculate line count between this and another line (inclusive)
180    #[inline]
181    pub const fn lines_to(self, end: Self) -> u32 {
182        if end.0 >= self.0 {
183            end.0 - self.0 + 1
184        } else {
185            1
186        }
187    }
188}
189
190impl From<u32> for LineNumber {
191    #[inline]
192    fn from(value: u32) -> Self {
193        Self(value)
194    }
195}
196
197impl From<LineNumber> for u32 {
198    #[inline]
199    fn from(value: LineNumber) -> Self {
200        value.0
201    }
202}
203
204impl fmt::Display for LineNumber {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        write!(f, "L{}", self.0)
207    }
208}
209
210/// A byte offset in a file or string
211#[derive(
212    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
213)]
214#[repr(transparent)]
215pub struct ByteOffset(usize);
216
217impl ByteOffset {
218    /// Create a new byte offset
219    #[inline]
220    pub const fn new(offset: usize) -> Self {
221        Self(offset)
222    }
223
224    /// Create a zero offset
225    #[inline]
226    pub const fn zero() -> Self {
227        Self(0)
228    }
229
230    /// Get the inner value
231    #[inline]
232    pub const fn get(self) -> usize {
233        self.0
234    }
235}
236
237impl From<usize> for ByteOffset {
238    #[inline]
239    fn from(value: usize) -> Self {
240        Self(value)
241    }
242}
243
244impl From<ByteOffset> for usize {
245    #[inline]
246    fn from(value: ByteOffset) -> Self {
247        value.0
248    }
249}
250
251impl fmt::Display for ByteOffset {
252    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
253        write!(f, "@{}", self.0)
254    }
255}
256
257/// A unique identifier for a symbol in the index
258#[derive(
259    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
260)]
261#[repr(transparent)]
262pub struct SymbolId(u32);
263
264impl SymbolId {
265    /// Create a new symbol ID
266    #[inline]
267    pub const fn new(id: u32) -> Self {
268        Self(id)
269    }
270
271    /// Create an invalid/unknown symbol ID
272    #[inline]
273    pub const fn unknown() -> Self {
274        Self(0)
275    }
276
277    /// Get the inner value
278    #[inline]
279    pub const fn get(self) -> u32 {
280        self.0
281    }
282
283    /// Check if this is a valid symbol ID
284    #[inline]
285    pub const fn is_valid(self) -> bool {
286        self.0 > 0
287    }
288}
289
290impl From<u32> for SymbolId {
291    #[inline]
292    fn from(value: u32) -> Self {
293        Self(value)
294    }
295}
296
297impl From<SymbolId> for u32 {
298    #[inline]
299    fn from(value: SymbolId) -> Self {
300        value.0
301    }
302}
303
304impl fmt::Display for SymbolId {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        write!(f, "#{}", self.0)
307    }
308}
309
310/// File size in bytes
311#[derive(
312    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
313)]
314#[repr(transparent)]
315pub struct FileSize(u64);
316
317impl FileSize {
318    /// Create a new file size
319    #[inline]
320    pub const fn new(bytes: u64) -> Self {
321        Self(bytes)
322    }
323
324    /// Create a zero size
325    #[inline]
326    pub const fn zero() -> Self {
327        Self(0)
328    }
329
330    /// Get the inner value in bytes
331    #[inline]
332    pub const fn bytes(self) -> u64 {
333        self.0
334    }
335
336    /// Get size in kilobytes
337    #[inline]
338    pub const fn kilobytes(self) -> u64 {
339        self.0 / 1024
340    }
341
342    /// Get size in megabytes
343    #[inline]
344    pub const fn megabytes(self) -> u64 {
345        self.0 / (1024 * 1024)
346    }
347
348    /// Check if this exceeds a limit
349    #[inline]
350    pub const fn exceeds(self, limit: Self) -> bool {
351        self.0 > limit.0
352    }
353}
354
355impl From<u64> for FileSize {
356    #[inline]
357    fn from(value: u64) -> Self {
358        Self(value)
359    }
360}
361
362impl From<FileSize> for u64 {
363    #[inline]
364    fn from(value: FileSize) -> Self {
365        value.0
366    }
367}
368
369impl fmt::Display for FileSize {
370    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
371        if self.0 >= 1024 * 1024 {
372            write!(f, "{:.1} MB", self.0 as f64 / (1024.0 * 1024.0))
373        } else if self.0 >= 1024 {
374            write!(f, "{:.1} KB", self.0 as f64 / 1024.0)
375        } else {
376            write!(f, "{} bytes", self.0)
377        }
378    }
379}
380
381/// Importance score (0.0 to 1.0)
382#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
383#[repr(transparent)]
384pub struct ImportanceScore(f32);
385
386impl ImportanceScore {
387    /// Create a new importance score, clamping to valid range
388    #[inline]
389    pub fn new(score: f32) -> Self {
390        Self(score.clamp(0.0, 1.0))
391    }
392
393    /// Create a zero importance score
394    #[inline]
395    pub const fn zero() -> Self {
396        Self(0.0)
397    }
398
399    /// Create maximum importance score
400    #[inline]
401    pub const fn max() -> Self {
402        Self(1.0)
403    }
404
405    /// Create default importance score
406    #[inline]
407    pub const fn default_score() -> Self {
408        Self(0.5)
409    }
410
411    /// Get the inner value
412    #[inline]
413    pub const fn get(self) -> f32 {
414        self.0
415    }
416
417    /// Check if this is considered high importance (> 0.7)
418    #[inline]
419    pub fn is_high(self) -> bool {
420        self.0 > 0.7
421    }
422
423    /// Check if this is considered low importance (< 0.3)
424    #[inline]
425    pub fn is_low(self) -> bool {
426        self.0 < 0.3
427    }
428}
429
430impl From<f32> for ImportanceScore {
431    #[inline]
432    fn from(value: f32) -> Self {
433        Self::new(value)
434    }
435}
436
437impl From<ImportanceScore> for f32 {
438    #[inline]
439    fn from(value: ImportanceScore) -> Self {
440        value.0
441    }
442}
443
444impl fmt::Display for ImportanceScore {
445    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446        write!(f, "{:.2}", self.0)
447    }
448}
449
450impl Eq for ImportanceScore {}
451
452impl PartialOrd for ImportanceScore {
453    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
454        Some(self.cmp(other))
455    }
456}
457
458impl Ord for ImportanceScore {
459    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
460        // Use f32's partial_cmp directly, treating NaN as equal (shouldn't happen with clamped values)
461        self.0
462            .partial_cmp(&other.0)
463            .unwrap_or(std::cmp::Ordering::Equal)
464    }
465}
466
467impl std::hash::Hash for ImportanceScore {
468    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
469        self.0.to_bits().hash(state);
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    use super::*;
476    use std::collections::HashSet;
477
478    // ==========================================================================
479    // TokenCount tests
480    // ==========================================================================
481
482    #[test]
483    fn test_token_count_operations() {
484        let a = TokenCount::new(100);
485        let b = TokenCount::new(50);
486
487        assert_eq!((a + b).get(), 150);
488        assert_eq!((a - b).get(), 50);
489        assert_eq!(a.saturating_sub(TokenCount::new(200)).get(), 0);
490    }
491
492    #[test]
493    fn test_token_count_percentage() {
494        let part = TokenCount::new(25);
495        let total = TokenCount::new(100);
496
497        assert!((part.percentage_of(total) - 25.0).abs() < 0.01);
498        assert_eq!(part.percentage_of(TokenCount::zero()), 0.0);
499    }
500
501    #[test]
502    fn test_token_count_sum() {
503        let counts = vec![TokenCount::new(10), TokenCount::new(20), TokenCount::new(30)];
504        let sum: TokenCount = counts.into_iter().sum();
505        assert_eq!(sum.get(), 60);
506    }
507
508    #[test]
509    fn test_token_count_zero() {
510        let zero = TokenCount::zero();
511        assert_eq!(zero.get(), 0);
512        assert!(zero.is_zero());
513    }
514
515    #[test]
516    fn test_token_count_is_zero() {
517        assert!(TokenCount::new(0).is_zero());
518        assert!(!TokenCount::new(1).is_zero());
519        assert!(!TokenCount::new(100).is_zero());
520    }
521
522    #[test]
523    fn test_token_count_saturating_add() {
524        let a = TokenCount::new(u32::MAX - 10);
525        let b = TokenCount::new(20);
526        let result = a.saturating_add(b);
527        assert_eq!(result.get(), u32::MAX);
528    }
529
530    #[test]
531    fn test_token_count_add_assign() {
532        let mut count = TokenCount::new(100);
533        count += TokenCount::new(50);
534        assert_eq!(count.get(), 150);
535    }
536
537    #[test]
538    fn test_token_count_sub_assign() {
539        let mut count = TokenCount::new(100);
540        count -= TokenCount::new(30);
541        assert_eq!(count.get(), 70);
542    }
543
544    #[test]
545    fn test_token_count_from_u32() {
546        let count: TokenCount = 42u32.into();
547        assert_eq!(count.get(), 42);
548    }
549
550    #[test]
551    fn test_token_count_into_u32() {
552        let count = TokenCount::new(42);
553        let value: u32 = count.into();
554        assert_eq!(value, 42);
555    }
556
557    #[test]
558    fn test_token_count_default() {
559        let count = TokenCount::default();
560        assert_eq!(count.get(), 0);
561        assert!(count.is_zero());
562    }
563
564    #[test]
565    fn test_token_count_clone_copy() {
566        let a = TokenCount::new(100);
567        let b = a; // Copy
568        let c = a.clone(); // Clone
569        assert_eq!(a.get(), b.get());
570        assert_eq!(a.get(), c.get());
571    }
572
573    #[test]
574    fn test_token_count_ord() {
575        let a = TokenCount::new(100);
576        let b = TokenCount::new(200);
577        assert!(a < b);
578        assert!(b > a);
579        assert_eq!(a.cmp(&a), std::cmp::Ordering::Equal);
580    }
581
582    #[test]
583    fn test_token_count_hash() {
584        let mut set = HashSet::new();
585        set.insert(TokenCount::new(100));
586        set.insert(TokenCount::new(200));
587        set.insert(TokenCount::new(100)); // Duplicate
588        assert_eq!(set.len(), 2);
589    }
590
591    #[test]
592    fn test_token_count_debug() {
593        let count = TokenCount::new(42);
594        let debug_str = format!("{:?}", count);
595        assert!(debug_str.contains("42"));
596    }
597
598    #[test]
599    fn test_token_count_sum_empty() {
600        let counts: Vec<TokenCount> = vec![];
601        let sum: TokenCount = counts.into_iter().sum();
602        assert_eq!(sum.get(), 0);
603    }
604
605    // ==========================================================================
606    // LineNumber tests
607    // ==========================================================================
608
609    #[test]
610    fn test_line_number_indexing() {
611        let line = LineNumber::new(10);
612
613        assert_eq!(line.to_zero_indexed(), 9);
614        assert_eq!(LineNumber::from_zero_indexed(9).get(), 10);
615    }
616
617    #[test]
618    fn test_line_number_range() {
619        let start = LineNumber::new(5);
620        let end = LineNumber::new(10);
621
622        assert_eq!(start.lines_to(end), 6);
623        assert_eq!(end.lines_to(start), 1); // Invalid range returns 1
624    }
625
626    #[test]
627    fn test_line_number_first() {
628        let first = LineNumber::first();
629        assert_eq!(first.get(), 1);
630    }
631
632    #[test]
633    fn test_line_number_is_valid() {
634        assert!(!LineNumber::new(0).is_valid());
635        assert!(LineNumber::new(1).is_valid());
636        assert!(LineNumber::new(100).is_valid());
637    }
638
639    #[test]
640    fn test_line_number_to_zero_indexed_edge() {
641        assert_eq!(LineNumber::new(0).to_zero_indexed(), 0); // Saturating
642        assert_eq!(LineNumber::new(1).to_zero_indexed(), 0);
643    }
644
645    #[test]
646    fn test_line_number_from_zero_indexed() {
647        assert_eq!(LineNumber::from_zero_indexed(0).get(), 1);
648        assert_eq!(LineNumber::from_zero_indexed(99).get(), 100);
649    }
650
651    #[test]
652    fn test_line_number_from_u32() {
653        let line: LineNumber = 42u32.into();
654        assert_eq!(line.get(), 42);
655    }
656
657    #[test]
658    fn test_line_number_into_u32() {
659        let line = LineNumber::new(42);
660        let value: u32 = line.into();
661        assert_eq!(value, 42);
662    }
663
664    #[test]
665    fn test_line_number_default() {
666        let line = LineNumber::default();
667        assert_eq!(line.get(), 0);
668    }
669
670    #[test]
671    fn test_line_number_clone_copy() {
672        let a = LineNumber::new(10);
673        let b = a;
674        let c = a.clone();
675        assert_eq!(a.get(), b.get());
676        assert_eq!(a.get(), c.get());
677    }
678
679    #[test]
680    fn test_line_number_ord() {
681        let a = LineNumber::new(10);
682        let b = LineNumber::new(20);
683        assert!(a < b);
684        assert!(b > a);
685    }
686
687    #[test]
688    fn test_line_number_hash() {
689        let mut set = HashSet::new();
690        set.insert(LineNumber::new(10));
691        set.insert(LineNumber::new(20));
692        set.insert(LineNumber::new(10)); // Duplicate
693        assert_eq!(set.len(), 2);
694    }
695
696    #[test]
697    fn test_line_number_debug() {
698        let line = LineNumber::new(42);
699        let debug_str = format!("{:?}", line);
700        assert!(debug_str.contains("42"));
701    }
702
703    #[test]
704    fn test_line_number_lines_to_same() {
705        let line = LineNumber::new(5);
706        assert_eq!(line.lines_to(line), 1);
707    }
708
709    // ==========================================================================
710    // ByteOffset tests
711    // ==========================================================================
712
713    #[test]
714    fn test_byte_offset() {
715        let offset = ByteOffset::new(1024);
716        assert_eq!(offset.get(), 1024);
717        assert_eq!(ByteOffset::zero().get(), 0);
718    }
719
720    #[test]
721    fn test_byte_offset_from_usize() {
722        let offset: ByteOffset = 1024usize.into();
723        assert_eq!(offset.get(), 1024);
724    }
725
726    #[test]
727    fn test_byte_offset_into_usize() {
728        let offset = ByteOffset::new(1024);
729        let value: usize = offset.into();
730        assert_eq!(value, 1024);
731    }
732
733    #[test]
734    fn test_byte_offset_default() {
735        let offset = ByteOffset::default();
736        assert_eq!(offset.get(), 0);
737    }
738
739    #[test]
740    fn test_byte_offset_clone_copy() {
741        let a = ByteOffset::new(1024);
742        let b = a;
743        let c = a.clone();
744        assert_eq!(a.get(), b.get());
745        assert_eq!(a.get(), c.get());
746    }
747
748    #[test]
749    fn test_byte_offset_ord() {
750        let a = ByteOffset::new(100);
751        let b = ByteOffset::new(200);
752        assert!(a < b);
753        assert!(b > a);
754    }
755
756    #[test]
757    fn test_byte_offset_hash() {
758        let mut set = HashSet::new();
759        set.insert(ByteOffset::new(100));
760        set.insert(ByteOffset::new(200));
761        set.insert(ByteOffset::new(100)); // Duplicate
762        assert_eq!(set.len(), 2);
763    }
764
765    #[test]
766    fn test_byte_offset_debug() {
767        let offset = ByteOffset::new(1024);
768        let debug_str = format!("{:?}", offset);
769        assert!(debug_str.contains("1024"));
770    }
771
772    #[test]
773    fn test_byte_offset_display() {
774        let offset = ByteOffset::new(1024);
775        assert_eq!(offset.to_string(), "@1024");
776    }
777
778    // ==========================================================================
779    // SymbolId tests
780    // ==========================================================================
781
782    #[test]
783    fn test_symbol_id_validity() {
784        assert!(!SymbolId::unknown().is_valid());
785        assert!(SymbolId::new(1).is_valid());
786        assert!(!SymbolId::new(0).is_valid());
787    }
788
789    #[test]
790    fn test_symbol_id_unknown() {
791        let unknown = SymbolId::unknown();
792        assert_eq!(unknown.get(), 0);
793        assert!(!unknown.is_valid());
794    }
795
796    #[test]
797    fn test_symbol_id_from_u32() {
798        let id: SymbolId = 42u32.into();
799        assert_eq!(id.get(), 42);
800    }
801
802    #[test]
803    fn test_symbol_id_into_u32() {
804        let id = SymbolId::new(42);
805        let value: u32 = id.into();
806        assert_eq!(value, 42);
807    }
808
809    #[test]
810    fn test_symbol_id_default() {
811        let id = SymbolId::default();
812        assert_eq!(id.get(), 0);
813        assert!(!id.is_valid());
814    }
815
816    #[test]
817    fn test_symbol_id_clone_copy() {
818        let a = SymbolId::new(42);
819        let b = a;
820        let c = a.clone();
821        assert_eq!(a.get(), b.get());
822        assert_eq!(a.get(), c.get());
823    }
824
825    #[test]
826    fn test_symbol_id_ord() {
827        let a = SymbolId::new(10);
828        let b = SymbolId::new(20);
829        assert!(a < b);
830        assert!(b > a);
831    }
832
833    #[test]
834    fn test_symbol_id_hash() {
835        let mut set = HashSet::new();
836        set.insert(SymbolId::new(10));
837        set.insert(SymbolId::new(20));
838        set.insert(SymbolId::new(10)); // Duplicate
839        assert_eq!(set.len(), 2);
840    }
841
842    #[test]
843    fn test_symbol_id_debug() {
844        let id = SymbolId::new(42);
845        let debug_str = format!("{:?}", id);
846        assert!(debug_str.contains("42"));
847    }
848
849    #[test]
850    fn test_symbol_id_display() {
851        let id = SymbolId::new(42);
852        assert_eq!(id.to_string(), "#42");
853    }
854
855    // ==========================================================================
856    // FileSize tests
857    // ==========================================================================
858
859    #[test]
860    fn test_file_size_conversions() {
861        let size = FileSize::new(1024 * 1024 + 512 * 1024); // 1.5 MB
862
863        assert_eq!(size.kilobytes(), 1536);
864        assert_eq!(size.megabytes(), 1);
865    }
866
867    #[test]
868    fn test_file_size_display() {
869        assert_eq!(FileSize::new(500).to_string(), "500 bytes");
870        assert_eq!(FileSize::new(2048).to_string(), "2.0 KB");
871        assert_eq!(FileSize::new(1024 * 1024).to_string(), "1.0 MB");
872    }
873
874    #[test]
875    fn test_file_size_zero() {
876        let zero = FileSize::zero();
877        assert_eq!(zero.bytes(), 0);
878    }
879
880    #[test]
881    fn test_file_size_bytes() {
882        let size = FileSize::new(12345);
883        assert_eq!(size.bytes(), 12345);
884    }
885
886    #[test]
887    fn test_file_size_exceeds() {
888        let size = FileSize::new(1000);
889        let limit = FileSize::new(500);
890        assert!(size.exceeds(limit));
891        assert!(!limit.exceeds(size));
892        assert!(!size.exceeds(size)); // Equal doesn't exceed
893    }
894
895    #[test]
896    fn test_file_size_from_u64() {
897        let size: FileSize = 1024u64.into();
898        assert_eq!(size.bytes(), 1024);
899    }
900
901    #[test]
902    fn test_file_size_into_u64() {
903        let size = FileSize::new(1024);
904        let value: u64 = size.into();
905        assert_eq!(value, 1024);
906    }
907
908    #[test]
909    fn test_file_size_default() {
910        let size = FileSize::default();
911        assert_eq!(size.bytes(), 0);
912    }
913
914    #[test]
915    fn test_file_size_clone_copy() {
916        let a = FileSize::new(1024);
917        let b = a;
918        let c = a.clone();
919        assert_eq!(a.bytes(), b.bytes());
920        assert_eq!(a.bytes(), c.bytes());
921    }
922
923    #[test]
924    fn test_file_size_ord() {
925        let a = FileSize::new(100);
926        let b = FileSize::new(200);
927        assert!(a < b);
928        assert!(b > a);
929    }
930
931    #[test]
932    fn test_file_size_hash() {
933        let mut set = HashSet::new();
934        set.insert(FileSize::new(100));
935        set.insert(FileSize::new(200));
936        set.insert(FileSize::new(100)); // Duplicate
937        assert_eq!(set.len(), 2);
938    }
939
940    #[test]
941    fn test_file_size_debug() {
942        let size = FileSize::new(1024);
943        let debug_str = format!("{:?}", size);
944        assert!(debug_str.contains("1024"));
945    }
946
947    #[test]
948    fn test_file_size_display_edge_cases() {
949        // Just under 1KB
950        assert_eq!(FileSize::new(1023).to_string(), "1023 bytes");
951        // Exactly 1KB
952        assert_eq!(FileSize::new(1024).to_string(), "1.0 KB");
953        // Just under 1MB
954        let just_under_mb = 1024 * 1024 - 1;
955        assert!(FileSize::new(just_under_mb).to_string().contains("KB"));
956    }
957
958    // ==========================================================================
959    // ImportanceScore tests
960    // ==========================================================================
961
962    #[test]
963    fn test_importance_score_clamping() {
964        assert_eq!(ImportanceScore::new(-0.5).get(), 0.0);
965        assert_eq!(ImportanceScore::new(1.5).get(), 1.0);
966        assert_eq!(ImportanceScore::new(0.5).get(), 0.5);
967    }
968
969    #[test]
970    fn test_importance_score_classification() {
971        assert!(ImportanceScore::new(0.8).is_high());
972        assert!(!ImportanceScore::new(0.5).is_high());
973        assert!(ImportanceScore::new(0.2).is_low());
974        assert!(!ImportanceScore::new(0.5).is_low());
975    }
976
977    #[test]
978    fn test_importance_score_zero() {
979        let zero = ImportanceScore::zero();
980        assert_eq!(zero.get(), 0.0);
981        assert!(zero.is_low());
982    }
983
984    #[test]
985    fn test_importance_score_max() {
986        let max = ImportanceScore::max();
987        assert_eq!(max.get(), 1.0);
988        assert!(max.is_high());
989    }
990
991    #[test]
992    fn test_importance_score_default_score() {
993        let default = ImportanceScore::default_score();
994        assert_eq!(default.get(), 0.5);
995        assert!(!default.is_high());
996        assert!(!default.is_low());
997    }
998
999    #[test]
1000    fn test_importance_score_from_f32() {
1001        let score: ImportanceScore = 0.75f32.into();
1002        assert_eq!(score.get(), 0.75);
1003    }
1004
1005    #[test]
1006    fn test_importance_score_from_f32_clamped() {
1007        let score: ImportanceScore = 2.0f32.into();
1008        assert_eq!(score.get(), 1.0);
1009    }
1010
1011    #[test]
1012    fn test_importance_score_into_f32() {
1013        let score = ImportanceScore::new(0.75);
1014        let value: f32 = score.into();
1015        assert_eq!(value, 0.75);
1016    }
1017
1018    #[test]
1019    fn test_importance_score_default() {
1020        let score = ImportanceScore::default();
1021        assert_eq!(score.get(), 0.0);
1022    }
1023
1024    #[test]
1025    fn test_importance_score_clone_copy() {
1026        let a = ImportanceScore::new(0.5);
1027        let b = a;
1028        let c = a.clone();
1029        assert_eq!(a.get(), b.get());
1030        assert_eq!(a.get(), c.get());
1031    }
1032
1033    #[test]
1034    fn test_importance_score_ord() {
1035        let a = ImportanceScore::new(0.3);
1036        let b = ImportanceScore::new(0.7);
1037        assert!(a < b);
1038        assert!(b > a);
1039        assert_eq!(a.cmp(&a), std::cmp::Ordering::Equal);
1040    }
1041
1042    #[test]
1043    fn test_importance_score_partial_ord() {
1044        let a = ImportanceScore::new(0.3);
1045        let b = ImportanceScore::new(0.7);
1046        assert!(a.partial_cmp(&b) == Some(std::cmp::Ordering::Less));
1047        assert!(b.partial_cmp(&a) == Some(std::cmp::Ordering::Greater));
1048    }
1049
1050    #[test]
1051    fn test_importance_score_eq() {
1052        let a = ImportanceScore::new(0.5);
1053        let b = ImportanceScore::new(0.5);
1054        let c = ImportanceScore::new(0.6);
1055        assert_eq!(a, b);
1056        assert_ne!(a, c);
1057    }
1058
1059    #[test]
1060    fn test_importance_score_hash() {
1061        let mut set = HashSet::new();
1062        set.insert(ImportanceScore::new(0.3));
1063        set.insert(ImportanceScore::new(0.7));
1064        set.insert(ImportanceScore::new(0.3)); // Duplicate
1065        assert_eq!(set.len(), 2);
1066    }
1067
1068    #[test]
1069    fn test_importance_score_debug() {
1070        let score = ImportanceScore::new(0.5);
1071        let debug_str = format!("{:?}", score);
1072        assert!(debug_str.contains("0.5"));
1073    }
1074
1075    #[test]
1076    fn test_importance_score_display() {
1077        let score = ImportanceScore::new(0.5);
1078        assert_eq!(score.to_string(), "0.50");
1079    }
1080
1081    #[test]
1082    fn test_importance_score_boundary_high() {
1083        // Exactly at boundary
1084        assert!(!ImportanceScore::new(0.7).is_high()); // Not strictly > 0.7
1085        assert!(ImportanceScore::new(0.71).is_high());
1086    }
1087
1088    #[test]
1089    fn test_importance_score_boundary_low() {
1090        // Exactly at boundary
1091        assert!(!ImportanceScore::new(0.3).is_low()); // Not strictly < 0.3
1092        assert!(ImportanceScore::new(0.29).is_low());
1093    }
1094
1095    // ==========================================================================
1096    // Display tests
1097    // ==========================================================================
1098
1099    #[test]
1100    fn test_display_formatting() {
1101        assert_eq!(TokenCount::new(100).to_string(), "100 tokens");
1102        assert_eq!(LineNumber::new(42).to_string(), "L42");
1103        assert_eq!(ByteOffset::new(1000).to_string(), "@1000");
1104        assert_eq!(SymbolId::new(5).to_string(), "#5");
1105    }
1106
1107    #[test]
1108    fn test_importance_score_display_precision() {
1109        assert_eq!(ImportanceScore::new(0.123).to_string(), "0.12");
1110        assert_eq!(ImportanceScore::new(0.999).to_string(), "1.00"); // Clamped
1111        assert_eq!(ImportanceScore::zero().to_string(), "0.00");
1112    }
1113
1114    // ==========================================================================
1115    // Serde tests
1116    // ==========================================================================
1117
1118    #[test]
1119    fn test_token_count_serde() {
1120        let count = TokenCount::new(100);
1121        let json = serde_json::to_string(&count).unwrap();
1122        let parsed: TokenCount = serde_json::from_str(&json).unwrap();
1123        assert_eq!(count, parsed);
1124    }
1125
1126    #[test]
1127    fn test_line_number_serde() {
1128        let line = LineNumber::new(42);
1129        let json = serde_json::to_string(&line).unwrap();
1130        let parsed: LineNumber = serde_json::from_str(&json).unwrap();
1131        assert_eq!(line, parsed);
1132    }
1133
1134    #[test]
1135    fn test_byte_offset_serde() {
1136        let offset = ByteOffset::new(1024);
1137        let json = serde_json::to_string(&offset).unwrap();
1138        let parsed: ByteOffset = serde_json::from_str(&json).unwrap();
1139        assert_eq!(offset, parsed);
1140    }
1141
1142    #[test]
1143    fn test_symbol_id_serde() {
1144        let id = SymbolId::new(42);
1145        let json = serde_json::to_string(&id).unwrap();
1146        let parsed: SymbolId = serde_json::from_str(&json).unwrap();
1147        assert_eq!(id, parsed);
1148    }
1149
1150    #[test]
1151    fn test_file_size_serde() {
1152        let size = FileSize::new(1024);
1153        let json = serde_json::to_string(&size).unwrap();
1154        let parsed: FileSize = serde_json::from_str(&json).unwrap();
1155        assert_eq!(size, parsed);
1156    }
1157
1158    #[test]
1159    fn test_importance_score_serde() {
1160        let score = ImportanceScore::new(0.75);
1161        let json = serde_json::to_string(&score).unwrap();
1162        let parsed: ImportanceScore = serde_json::from_str(&json).unwrap();
1163        assert_eq!(score, parsed);
1164    }
1165}