//! Three-valued verdict for a non-deterministic probe (WOR-1240).
//!
//! AgentAssay (arXiv:2603.02601) grounds a stochastic regression verdict in
//! hypothesis testing: a probe run N times yields PASS, FAIL, or INCONCLUSIVE
//! rather than a forced binary. This module is the deterministic decision rule.
//! Given the number of passing trials out of N and a confidence policy, it
//! builds a Wald confidence interval around the pass rate and decides:
//!
//! - PASS when the whole interval sits above 0.5 (the probe reliably passes),
//! - FAIL when it sits below 0.5 (the probe reliably fails),
//! - INCONCLUSIVE when the interval straddles 0.5, or there were too few trials.
//!
//! No randomness here: the same counts and policy always yield the same verdict.

/// The three-valued verdict.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Verdict {
    /// The probe reliably passed.
    Pass,
    /// The probe reliably failed.
    Fail,
    /// Neither confirmed nor refuted at the configured confidence.
    Inconclusive,
}

/// Confidence policy for the decision.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct VerdictPolicy {
    /// Minimum trials before a definite verdict is allowed. Fewer is always
    /// inconclusive.
    pub min_trials: usize,
    /// Two-sided confidence for the interval, in `[0.80, 0.999]`. Higher widens
    /// the interval, so more runs land inconclusive.
    pub confidence: f64,
}

impl Default for VerdictPolicy {
    fn default() -> Self {
        Self {
            min_trials: 5,
            confidence: 0.95,
        }
    }
}

/// Decide a three-valued verdict from `passes` of `trials`.
pub fn decide(passes: usize, trials: usize, policy: &VerdictPolicy) -> Verdict {
    if trials == 0 || trials < policy.min_trials {
        return Verdict::Inconclusive;
    }
    let n = trials as f64;
    let p = passes as f64 / n;
    let z = z_for(policy.confidence);
    let half_width = z * (p * (1.0 - p) / n).sqrt();
    let lower = p - half_width;
    let upper = p + half_width;
    if lower > 0.5 {
        Verdict::Pass
    } else if upper < 0.5 {
        Verdict::Fail
    } else {
        Verdict::Inconclusive
    }
}

/// The z-score for a two-sided confidence, picking the smallest tabulated
/// standard confidence at or above the request (conservative: a wider interval
/// errs toward inconclusive). Deterministic, no stats dependency.
fn z_for(confidence: f64) -> f64 {
    const TABLE: &[(f64, f64)] = &[
        (0.80, 1.2816),
        (0.90, 1.6449),
        (0.95, 1.9600),
        (0.975, 2.2414),
        (0.99, 2.5758),
        (0.999, 3.2905),
    ];
    for (c, z) in TABLE {
        if confidence <= *c {
            return *z;
        }
    }
    3.2905
}

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

    fn policy(min_trials: usize) -> VerdictPolicy {
        VerdictPolicy {
            min_trials,
            confidence: 0.95,
        }
    }

    #[test]
    fn too_few_trials_is_inconclusive() {
        assert_eq!(decide(2, 2, &policy(5)), Verdict::Inconclusive);
        assert_eq!(decide(0, 0, &policy(1)), Verdict::Inconclusive);
    }

    #[test]
    fn an_all_pass_run_passes() {
        assert_eq!(decide(20, 20, &policy(5)), Verdict::Pass);
    }

    #[test]
    fn an_all_fail_run_fails() {
        assert_eq!(decide(0, 20, &policy(5)), Verdict::Fail);
    }

    #[test]
    fn a_near_split_run_is_inconclusive() {
        // 6 of 10: the 95% interval around 0.6 straddles 0.5.
        assert_eq!(decide(6, 10, &policy(5)), Verdict::Inconclusive);
    }

    #[test]
    fn a_strong_majority_over_many_trials_passes() {
        // 90 of 100: the interval clears 0.5 comfortably.
        assert_eq!(decide(90, 100, &policy(5)), Verdict::Pass);
    }

    #[test]
    fn higher_confidence_widens_toward_inconclusive() {
        // 8 of 10 passes at 80% confidence, but a strict 99.9% policy on the
        // same counts is no longer sure.
        let lenient = VerdictPolicy {
            min_trials: 5,
            confidence: 0.80,
        };
        let strict = VerdictPolicy {
            min_trials: 5,
            confidence: 0.999,
        };
        assert_eq!(decide(8, 10, &lenient), Verdict::Pass);
        assert_eq!(decide(8, 10, &strict), Verdict::Inconclusive);
    }
}
