use std::time::Instant;

/// Collects per-operation durations (ns) for tail-latency diagnostics.
pub struct Probe {
    samples: Vec<u64>,
}

impl Probe {
    pub fn with_capacity(n: usize) -> Self {
        Probe {
            samples: Vec::with_capacity(n),
        }
    }

    /// Time a single operation and record its duration in nanoseconds.
    #[inline]
    pub fn record<R>(&mut self, op: impl FnOnce() -> R) -> R {
        let start = Instant::now();
        let r = op();
        self.samples.push(start.elapsed().as_nanos() as u64);
        r
    }

    pub fn len(&self) -> usize {
        self.samples.len()
    }

    pub fn is_empty(&self) -> bool {
        self.samples.is_empty()
    }

    /// Sort, compute summary, and print `p50/p99/p99.9/max/ops_sec` to stdout.
    pub fn report(mut self, label: &str) {
        if self.samples.is_empty() {
            println!("[latency] {label}: no samples");
            return;
        }
        self.samples.sort_unstable();
        let n = self.samples.len();
        let total_ns: u128 = self.samples.iter().map(|&s| s as u128).sum();

        let pct = |p: f64| -> u64 {
            // nearest-rank on the sorted slice
            let idx = ((p / 100.0) * (n as f64 - 1.0)).round() as usize;
            self.samples[idx]
        };
        let p50 = pct(50.0);
        let p99 = pct(99.0);
        let p999 = pct(99.9);
        let max = self.samples[n - 1];

        // percentiles are monotonic on a sorted slice — guard the math.
        debug_assert!(p50 <= p99 && p99 <= p999 && p999 <= max);

        let ops_sec = if total_ns == 0 {
            f64::INFINITY
        } else {
            (n as f64) * 1_000_000_000.0 / (total_ns as f64)
        };

        println!(
            "[latency] {label}: n={n} p50={p50}ns p99={p99}ns p99.9={p999}ns max={max}ns ops/sec={ops_sec:.0}"
        );
    }
}
