//! Pure filtering and debounce logic for notification suppression.
//!
//! Contains constants and predicates that decide whether a notification
//! should be sent, without performing any I/O.

use std::time::{Duration, Instant};

/// Minimum time between rate-limit notifications for the same agent key.
pub const RATE_LIMIT_DEBOUNCE: Duration = Duration::from_secs(60);

/// Window during which rapid stage transitions for the same item are coalesced
/// into a single notification (only the final stage is announced).
pub const STAGE_TRANSITION_DEBOUNCE: Duration = Duration::from_millis(200);

/// Window during which repeated agent-status events for the same story are
/// coalesced into a single notification (only the latest is sent).
pub const AGENT_EVENT_DEBOUNCE: Duration = Duration::from_secs(5);

/// Window during which new-work-item creation events are coalesced into a
/// single combined chat message listing every item created in the burst
/// (story 1201, AC3). Unlike [`AGENT_EVENT_DEBOUNCE`], every item is kept —
/// none are dropped in favor of "latest wins".
pub const NEW_ITEM_COALESCE_WINDOW: Duration = Duration::from_millis(300);

/// Returns `true` if a rate-limit notification should be sent.
///
/// `last_notified` is the [`Instant`] of the last sent notification for this
/// agent, or `None` if no notification has been sent yet.
pub fn should_send_rate_limit(last_notified: Option<Instant>, now: Instant) -> bool {
    match last_notified {
        None => true,
        Some(last) => now.duration_since(last) >= RATE_LIMIT_DEBOUNCE,
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // ── should_send_rate_limit ────────────────────────────────────────────────

    #[test]
    fn should_send_when_never_notified() {
        let now = Instant::now();
        assert!(should_send_rate_limit(None, now));
    }

    #[test]
    fn should_not_send_within_debounce_window() {
        let now = Instant::now();
        // Pretend last notification was 10 seconds ago — inside the 60s window.
        let last = now - Duration::from_secs(10);
        assert!(!should_send_rate_limit(Some(last), now));
    }

    #[test]
    fn should_send_after_debounce_window_expires() {
        let now = Instant::now();
        // Pretend last notification was 61 seconds ago — outside the 60s window.
        let last = now - Duration::from_secs(61);
        assert!(should_send_rate_limit(Some(last), now));
    }

    #[test]
    fn should_not_send_at_exactly_debounce_boundary() {
        let now = Instant::now();
        // Exactly at the boundary: duration_since == RATE_LIMIT_DEBOUNCE (>=, so allowed).
        let last = now - RATE_LIMIT_DEBOUNCE;
        assert!(should_send_rate_limit(Some(last), now));
    }

    // ── constants ─────────────────────────────────────────────────────────────

    #[test]
    fn rate_limit_debounce_is_one_minute() {
        assert_eq!(RATE_LIMIT_DEBOUNCE, Duration::from_secs(60));
    }

    #[test]
    fn stage_transition_debounce_is_200ms() {
        assert_eq!(STAGE_TRANSITION_DEBOUNCE, Duration::from_millis(200));
    }
}
