use std::time::Duration;
#[derive(Debug, Clone)]
pub struct Output {
pub boundaries: Vec<Boundary>,
pub metadata: ProcessingMetadata,
}
#[derive(Debug, Clone)]
pub struct Boundary {
pub offset: usize,
pub char_offset: usize,
}
#[derive(Debug, Clone)]
pub struct ProcessingMetadata {
pub duration: Duration,
pub strategy_used: String,
pub chunks_processed: usize,
pub stats: ProcessingStats,
}
#[derive(Debug, Clone)]
pub struct ProcessingStats {
pub bytes_processed: usize,
pub chars_processed: usize,
pub sentence_count: usize,
pub avg_sentence_length: f32,
}
impl Output {
pub(crate) fn from_delta_stack_result(
result: crate::application::DeltaStackResult,
text: &str,
duration: Duration,
) -> Self {
let (char_boundaries, total_chars) = Self::calculate_char_offsets(text, &result.boundaries);
let boundaries = result
.boundaries
.into_iter()
.zip(char_boundaries)
.map(|(offset, char_offset)| Boundary {
offset,
char_offset,
})
.collect::<Vec<_>>();
let sentence_count = boundaries.len();
let avg_sentence_length = if sentence_count > 0 {
total_chars as f32 / sentence_count as f32
} else {
0.0
};
let strategy_used = if result.thread_count > 1 {
format!("parallel ({} threads)", result.thread_count)
} else {
"sequential".to_string()
};
Self {
boundaries,
metadata: ProcessingMetadata {
duration,
strategy_used,
chunks_processed: result.chunk_count,
stats: ProcessingStats {
bytes_processed: text.len(),
chars_processed: total_chars,
sentence_count,
avg_sentence_length,
},
},
}
}
fn calculate_char_offsets(text: &str, byte_offsets: &[usize]) -> (Vec<usize>, usize) {
let mut char_offsets = Vec::with_capacity(byte_offsets.len());
let mut chars = 0usize;
let mut prev = 0usize;
for &off in byte_offsets {
chars += text[prev..off].chars().count();
char_offsets.push(chars);
prev = off;
}
let total = chars + text[prev..].chars().count();
(char_offsets, total)
}
}