use std::collections::BTreeMap;
use chronix_core::ShardId;
#[derive(Debug, Clone)]
pub struct RetentionResult {
pub shards_dropped: usize,
pub segments_deleted: usize,
pub bytes_freed: u64,
}
#[must_use]
pub fn shards_to_drop(
shard_bounds: &BTreeMap<ShardId, (i64, i64)>,
cutoff_ts: i64,
) -> Vec<ShardId> {
shard_bounds
.iter()
.filter(|&(_, &(_, max_ts))| max_ts < cutoff_ts)
.map(|(&shard_id, _)| shard_id)
.collect()
}
#[must_use]
pub fn retention_cutoff(now_ns: i64, retention_ns: i64) -> i64 {
now_ns.saturating_sub(retention_ns).max(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn no_shards_to_drop_when_all_recent() {
let mut bounds = BTreeMap::new();
bounds.insert(ShardId(1), (100, 200));
bounds.insert(ShardId(2), (200, 300));
let cutoff = 50; assert!(shards_to_drop(&bounds, cutoff).is_empty());
}
#[test]
fn drops_old_shards() {
let mut bounds = BTreeMap::new();
bounds.insert(ShardId(1), (100, 200));
bounds.insert(ShardId(2), (300, 400));
bounds.insert(ShardId(3), (500, 600));
let cutoff = 450; let dropped = shards_to_drop(&bounds, cutoff);
assert_eq!(dropped.len(), 2);
assert!(dropped.contains(&ShardId(1)));
assert!(dropped.contains(&ShardId(2)));
}
#[test]
fn boundary_shard_not_dropped() {
let mut bounds = BTreeMap::new();
bounds.insert(ShardId(1), (100, 200));
assert!(shards_to_drop(&bounds, 200).is_empty());
}
#[test]
fn retention_cutoff_basic() {
assert_eq!(retention_cutoff(1_000_000, 500_000), 500_000);
}
#[test]
fn retention_cutoff_no_underflow() {
assert_eq!(retention_cutoff(100, 1_000_000), 0);
}
}