use crate::models::event::Event;
use crate::models::event_type::EventType;
use crate::models::location::Location;
use chrono::{DateTime, Local};
#[derive(Debug, Clone)]
pub struct Pair {
pub in_event: Event,
pub out_event: Option<Event>,
pub duration_minutes: i64,
pub lunch_minutes: i64,
pub position: Location,
pub work_gap: bool,
}
#[derive(Debug, Clone)]
pub struct Gap {
pub start: DateTime<Local>,
pub end: DateTime<Local>,
pub duration_minutes: i64,
pub is_work_gap: bool, }
#[derive(Debug, Default, Clone)]
pub struct Timeline {
pub events: Vec<Event>,
pub pairs: Vec<Pair>,
pub gaps: Vec<Gap>,
pub total_worked_minutes: i64,
}
pub fn build_timeline(events: &[Event]) -> Timeline {
if events.is_empty() {
return Timeline::default();
}
let mut sorted = events.to_vec();
sorted.sort_by_key(|e| e.timestamp());
let mut pairs = Vec::new();
let mut gaps = Vec::new();
let mut total = 0;
let mut i = 0;
while i < sorted.len() {
let ev = &sorted[i];
if ev.kind == EventType::In {
if i + 1 < sorted.len() && sorted[i + 1].kind == EventType::Out {
let in_ev = ev.clone();
let out_ev = sorted[i + 1].clone();
let lunch_minutes = match (in_ev.lunch, out_ev.lunch) {
(Some(l1), Some(l2)) => l1.max(l2) as i64,
(Some(l1), None) => l1 as i64,
(None, Some(l2)) => l2 as i64,
_ => 0,
};
let raw_minutes = (out_ev.timestamp() - in_ev.timestamp()).num_minutes();
let worked_minutes = raw_minutes - lunch_minutes;
total += worked_minutes;
pairs.push(Pair {
in_event: in_ev.clone(),
out_event: Some(out_ev.clone()),
duration_minutes: worked_minutes,
lunch_minutes,
position: in_ev.location,
work_gap: out_ev.work_gap,
});
i += 2;
continue;
}
let in_ev = ev.clone();
pairs.push(Pair {
in_event: in_ev.clone(),
out_event: None,
duration_minutes: 0,
lunch_minutes: in_ev.lunch.unwrap_or(0) as i64,
position: in_ev.location,
work_gap: false,
});
}
i += 1;
}
for w in pairs.windows(2) {
let p1 = &w[0];
let p2 = &w[1];
if let Some(out1) = &p1.out_event {
let start = out1.timestamp();
let end = p2.in_event.timestamp();
if end > start {
gaps.push(Gap {
start,
end,
duration_minutes: (end - start).num_minutes(),
is_work_gap: out1.work_gap,
});
}
}
}
Timeline {
events: sorted,
pairs,
gaps,
total_worked_minutes: total,
}
}