//! Deterministic assertion evaluation for the computed-metric check blocks.
//!
//! The `calibration:`, `tool_quality:`, and `grade_delta:` blocks each build a
//! metrics object and gate it with the standard assertion grammar. This helper
//! resolves an assertion's target against that object and runs the matcher,
//! reusing the same deterministic matcher dispatch the tool path uses so the
//! two cannot diverge. It lives next to the executor (rather than in `checks`)
//! so it can reach the crate-private `check_matcher`.

use serde_json::Value;

use super::check_matcher;
use crate::executor::resolve_target;
use crate::suite::{Assertion, Matcher};

/// Evaluate one deterministic assertion against a precomputed metrics object.
///
/// The check blocks expose their computed values (`ece`, `min_score`,
/// `grade_regressed`, ...) as runtime-resolved free-string targets resolved
/// against the metrics object via [`resolve_target`], then judged by the
/// existing deterministic matchers. LLM matchers are out of scope for a
/// deterministic check gate, so an `llm-jury`, `llm-judge`, or `similar`
/// matcher in a check block is a clear error rather than a panic. Returns
/// `Ok(())` on pass and a rendered failure string otherwise.
pub fn eval_metric_assertion(metrics: &Value, assertion: &Assertion) -> Result<(), String> {
    let target = resolve_target(metrics, &assertion.target);
    match &assertion.matcher {
        Matcher::LlmJury(_) | Matcher::LlmJudge(_) | Matcher::Similar(_) | Matcher::Citation(_) => {
            Err(format!(
                "check-block target `{}` cannot use an LLM matcher; check gates are deterministic",
                assertion.target
            ))
        }
        other => check_matcher(&target, other),
    }
}

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

    fn asrt(target: &str, matcher: Matcher) -> Assertion {
        Assertion {
            target: target.into(),
            matcher,
            weight: 1.0,
            name: None,
            transform: None,
        }
    }

    #[test]
    fn resolves_a_numeric_target_and_passes_a_maximum() {
        let metrics = json!({ "ece": 0.05, "brier": 0.2 });
        let a = asrt("ece", Matcher::Schema(json!({ "maximum": 0.1 })));
        assert!(eval_metric_assertion(&metrics, &a).is_ok());
    }

    #[test]
    fn fails_when_the_target_exceeds_the_maximum() {
        let metrics = json!({ "ece": 0.3 });
        let a = asrt("ece", Matcher::Schema(json!({ "maximum": 0.1 })));
        assert!(eval_metric_assertion(&metrics, &a).is_err());
    }

    #[test]
    fn rejects_an_llm_matcher_in_a_check_block() {
        let metrics = json!({ "ece": 0.05 });
        let a = asrt("ece", Matcher::LlmJury(json!({})));
        let err = eval_metric_assertion(&metrics, &a).expect_err("llm matcher rejected");
        assert!(err.contains("deterministic"), "{err}");
    }
}
