use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::expander::ExpandedEvent;
use crate::freebusy::{self, FreeSlot};
#[derive(Debug, Clone)]
pub struct EventStream {
pub stream_id: String,
pub events: Vec<ExpandedEvent>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum PrivacyLevel {
Full,
#[default]
Opaque,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BusyBlock {
pub start: DateTime<Utc>,
pub end: DateTime<Utc>,
pub source_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnifiedAvailability {
pub busy: Vec<BusyBlock>,
pub free: Vec<FreeSlot>,
pub window_start: DateTime<Utc>,
pub window_end: DateTime<Utc>,
pub privacy: PrivacyLevel,
}
pub fn merge_availability(
streams: &[EventStream],
window_start: DateTime<Utc>,
window_end: DateTime<Utc>,
privacy: PrivacyLevel,
) -> UnifiedAvailability {
if streams.is_empty() || window_start >= window_end {
let free = if window_start < window_end {
vec![FreeSlot {
start: window_start,
end: window_end,
duration_minutes: (window_end - window_start).num_minutes(),
}]
} else {
vec![]
};
return UnifiedAvailability {
busy: vec![],
free,
window_start,
window_end,
privacy,
};
}
let all_events: Vec<ExpandedEvent> = streams
.iter()
.flat_map(|s| s.events.iter().cloned())
.collect();
let merged_intervals = freebusy::merge_busy_periods(&all_events, window_start, window_end);
let busy: Vec<BusyBlock> = if privacy == PrivacyLevel::Full {
compute_busy_blocks_with_sources(streams, &merged_intervals, window_start, window_end)
} else {
merged_intervals
.iter()
.map(|(start, end)| BusyBlock {
start: *start,
end: *end,
source_count: 0,
})
.collect()
};
let free = freebusy::find_free_slots(&all_events, window_start, window_end);
UnifiedAvailability {
busy,
free,
window_start,
window_end,
privacy,
}
}
pub fn find_first_free_across(
streams: &[EventStream],
window_start: DateTime<Utc>,
window_end: DateTime<Utc>,
min_duration_minutes: i64,
) -> Option<FreeSlot> {
let all_events: Vec<ExpandedEvent> = streams
.iter()
.flat_map(|s| s.events.iter().cloned())
.collect();
freebusy::find_first_free_slot(&all_events, window_start, window_end, min_duration_minutes)
}
fn compute_busy_blocks_with_sources(
streams: &[EventStream],
merged_intervals: &[(DateTime<Utc>, DateTime<Utc>)],
window_start: DateTime<Utc>,
window_end: DateTime<Utc>,
) -> Vec<BusyBlock> {
merged_intervals
.iter()
.map(|(interval_start, interval_end)| {
let source_count = streams
.iter()
.filter(|stream| {
stream.events.iter().any(|event| {
let ev_start = event.start.max(window_start);
let ev_end = event.end.min(window_end);
ev_start < *interval_end && ev_end > *interval_start
})
})
.count();
BusyBlock {
start: *interval_start,
end: *interval_end,
source_count,
}
})
.collect()
}