//! `eval:` block parsing for the suite loader.
//!
//! Holds the parsers for the judged-metric `eval:` block on an agent test: the
//! built-in agentic metrics (`mcp_task_completion`, `mcp_use`,
//! `argument_correctness`, `plan_quality`) and the custom `rubric` metric in
//! both forms (flat weighted `criteria` and a decision `tree`). Pulled out of
//! [`super`] so the loader stays under the module-size ceiling; all functions
//! are crate-private and [`super::parse`] drives them.

use serde_yaml::Value as YamlValue;

use super::{EvalMetric, ParseError};

/// Parse the optional `eval:` block on an agent test: a mapping of
/// metric name to config. Returns an empty vec when absent.
pub(super) fn parse_eval_metrics(
    entry: &YamlValue,
    path: &str,
) -> Result<Vec<EvalMetric>, ParseError> {
    let Some(eval) = entry.get("eval") else {
        return Ok(Vec::new());
    };
    let Some(map) = eval.as_mapping() else {
        return Err(ParseError::BadShape {
            path: format!("{path}/eval"),
            field: "eval",
            expected: "a mapping of metric name to config",
        });
    };
    let mut metrics = Vec::new();
    for (key_node, value) in map {
        let key = key_node.as_str().unwrap_or_default();
        let metric_path = format!("{path}/eval/{key}");
        match key {
            "rubric" => metrics.push(EvalMetric::Rubric(parse_rubric(value, &metric_path)?)),
            "mcp_task_completion" => {
                let (threshold, strict) = parse_judged_metric(value);
                metrics.push(EvalMetric::TaskCompletion { threshold, strict });
            }
            "mcp_use" => {
                let (threshold, strict) = parse_judged_metric(value);
                metrics.push(EvalMetric::McpUse { threshold, strict });
            }
            "argument_correctness" => {
                let (threshold, strict) = parse_judged_metric(value);
                metrics.push(EvalMetric::ArgumentCorrectness { threshold, strict });
            }
            "plan_quality" => {
                let (threshold, strict) = parse_judged_metric(value);
                metrics.push(EvalMetric::PlanQuality { threshold, strict });
            }
            "multi_turn_mcp_use" => {
                let (threshold, strict) = parse_judged_metric(value);
                metrics.push(EvalMetric::MultiTurnMcpUse { threshold, strict });
            }
            _ => {
                return Err(ParseError::BadShape {
                    path: metric_path,
                    field: "eval",
                    expected: "a known metric name (rubric, mcp_task_completion, mcp_use, \
                               argument_correctness, plan_quality, multi_turn_mcp_use)",
                })
            }
        }
    }
    Ok(metrics)
}

/// Parse the common `{ threshold?, strict? }` knobs shared by the single-score
/// judged metrics (mcp_task_completion, mcp_use). Default threshold 0.5 matches
/// the DeepEval metrics these mirror; an absent block is all-defaults.
fn parse_judged_metric(value: &YamlValue) -> (f64, bool) {
    let threshold = value
        .get("threshold")
        .and_then(|t| t.as_f64())
        .unwrap_or(0.5);
    let strict = value
        .get("strict")
        .and_then(|s| s.as_bool())
        .unwrap_or(false);
    (threshold, strict)
}

/// Parse a `rubric` eval block into a [`crate::eval::rubric::RubricConfig`].
///
/// Delegates to the shared parser in [`crate::eval::rubric_parse`] so the
/// agent-test `eval.rubric` and the top-level `evals:` test type cannot drift,
/// mapping its error onto [`ParseError::BadRubric`].
fn parse_rubric(
    value: &YamlValue,
    path: &str,
) -> Result<crate::eval::rubric::RubricConfig, ParseError> {
    crate::eval::rubric_parse::parse_rubric(value, path).map_err(|e| ParseError::BadRubric {
        path: e.path,
        detail: e.message,
    })
}
