//! Async matcher helpers that need a provider: `similar` and `citation`.
//!
//! Extracted from the executor module to keep it under the module-size ceiling.
//! Both run on the executor's async assertion path (not the synchronous
//! `check_matcher` dispatch) because they call an external model; `citation`
//! also makes a network fetch.

use serde_json::Value;

use crate::eval::provider::LlmProvider;
use crate::matchers::{citation, citation_fetch, similar};

use super::matcher_result::from_assertion;

/// Evaluate a `similar` assertion through the embedding provider.
///
/// Like `check_llm_jury`, this needs a provider and runs on the async path. The
/// matcher embeds the reference and the actual value, then compares them by
/// cosine similarity. A provider error (including an unsupported-embeddings
/// provider) renders as a failure string so the runner reports it like any
/// other assertion failure.
pub(super) async fn check_similar(
    actual: &Value,
    raw: &Value,
    provider: Option<&dyn LlmProvider>,
) -> Result<(), String> {
    let Some(provider) = provider else {
        return Err(
            "matcher `similar` requires an LlmProvider; build McpExecutor with `with_provider`"
                .to_string(),
        );
    };
    let config = similar::parse_config(raw).map_err(|e| e.to_string())?;
    let outcome = similar::evaluate(&config, actual, provider)
        .await
        .map_err(|e| format!("similar provider error: {e}"))?;
    from_assertion(outcome)
}

/// Evaluate a `citation` matcher: fetch the response's cited sources behind an
/// SSRF guard and judge whether each supports the claim.
///
/// Like [`check_similar`], this needs a provider and runs on the async path. It
/// builds the production [`crate::matchers::citation_fetch::ReqwestFetcher`] and
/// hands it to the matcher; a provider error renders as a failure string. The
/// network fetch is the opt-in cost of this matcher; a suite that does not want
/// live fetches simply does not use `citation:`.
pub(super) async fn check_citation(
    actual: &Value,
    raw: &Value,
    provider: Option<&dyn LlmProvider>,
) -> Result<(), String> {
    let Some(provider) = provider else {
        return Err(
            "matcher `citation` requires an LlmProvider; build McpExecutor with `with_provider`"
                .to_string(),
        );
    };
    let config = citation::parse_config(raw).map_err(|e| e.to_string())?;
    let fetcher = citation_fetch::ReqwestFetcher::new()
        .map_err(|e| format!("citation fetcher init failed: {e}"))?;
    let outcome = citation::evaluate(&config, actual, provider, &fetcher)
        .await
        .map_err(|e| format!("citation provider error: {e}"))?;
    from_assertion(outcome)
}
