// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
//
// SPDX-License-Identifier: GPL-2.0-or-later

//! Shared retention and synchronization policy for decoder histories.

use std::collections::VecDeque;
use std::sync::{Mutex, MutexGuard};
use std::time::{Duration, Instant};

pub(crate) const HISTORY_RETENTION: Duration = Duration::from_secs(24 * 60 * 60);

/// Maximum entries per decoder history queue. Oldest entries are evicted on
/// busy channels independently of time-based pruning.
pub(crate) const MAX_HISTORY_ENTRIES: usize = 10_000;

pub(crate) fn current_timestamp_ms() -> i64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|duration| duration.as_millis() as i64)
        .unwrap_or(0)
}

pub(crate) fn lock_or_recover<'a, T>(mutex: &'a Mutex<T>, label: &str) -> MutexGuard<'a, T> {
    mutex.lock().unwrap_or_else(|error| {
        tracing::warn!(
            "Mutex for {} was poisoned (prior panic); recovering with potentially inconsistent data",
            label
        );
        error.into_inner()
    })
}

pub(crate) fn enforce_capacity<T>(deque: &mut VecDeque<T>, max: usize) {
    while deque.len() > max {
        deque.pop_front();
    }
}

/// Drop entries older than `retention` from an ordered history queue.
pub(crate) fn prune_by_age<T>(
    deque: &mut VecDeque<(Instant, T)>,
    retention: Duration,
    now: Instant,
) {
    let Some(cutoff) = now.checked_sub(retention) else {
        return;
    };
    while let Some((timestamp, _)) = deque.front() {
        if *timestamp < cutoff {
            deque.pop_front();
        } else {
            break;
        }
    }
}
