//! The [`Matcher`] enum: the parsed form of every `matcher:` key.
//!
//! Split out of [`super::types`] so the matcher vocabulary lives in one
//! cohesive module (it grows a variant per new matcher) and the types
//! module stays under the size ceiling. The enum is re-exported from
//! [`crate::suite`], so the public path `crate::suite::Matcher` is unchanged.

use serde_json::Value;

/// Supported matcher variants. The first four are evaluated inline by
/// the executor; the trailing three (`Schema`, `Snapshot`, `LlmJudge`)
/// are parsed today but the executor reports a deferred-evaluation
/// failure until the live runner routes
/// them through `mcptest_core::matchers`. Carrying them as distinct
/// variants means a future contributor adding `schema` evaluation only
/// has to edit one arm; the compiler enforces full coverage.
#[derive(Debug, Clone, PartialEq)]
pub enum Matcher {
    /// Exact JSON equality. The value at `target` must equal the
    /// supplied [`Value`].
    Exact(Value),
    /// Structural containment: sub-object for objects, multiset
    /// membership for arrays, and deep equality for scalars. There is no
    /// string-substring mode here; for that reach for `icontains`,
    /// `contains-all`, or `regex`.
    Contains(Value),
    /// Full regex match on the string at `target`.
    Regex(String),
    /// Object subset match: every key in the expected fragment must be
    /// present and equal in the actual value. Extra keys are ignored.
    Subset(Value),
    /// JSON Schema fragment validating the value at `target`. Carried
    /// as a raw [`Value`] for now so the YAML round-trips losslessly;
    /// the live runner is responsible for compiling it into a
    /// `jsonschema::JSONSchema`.
    Schema(Value),
    /// Snapshot key referenced by the suite. The payload is the key
    /// the runner uses to look up (or write) the snapshot artifact.
    Snapshot(String),
    /// LLM-judge block. Carried verbatim as a [`Value`] so the YAML
    /// round-trips; the executor will route this through the typed
    /// llm-judge shape in `mcptest_core::matchers` once the live
    /// runner wires it.
    LlmJudge(Value),
    /// LLM-jury block. Carries an N-judge panel plus a
    /// quorum threshold. The executor dispatches each judge through
    /// the same provider trait as `LlmJudge` and folds the verdicts.
    LlmJury(Value),
    /// `contains-all`: the string value contains every needle,
    /// or the array value contains every needle element. The payload is
    /// the needle array.
    ContainsAll(Value),
    /// `contains-any`: the string value contains at least one
    /// needle, or the array value contains at least one needle element.
    ContainsAny(Value),
    /// `icontains`: case-insensitive substring containment. The
    /// payload is the needle string.
    IContains(String),
    /// `starts-with`: the string value starts with this prefix.
    StartsWith(String),
    /// `is-json`: the string value parses as JSON. The payload
    /// is null (parse only) or `{ schema: {...} }` to also validate the
    /// parsed document against an inline JSON Schema.
    IsJson(Value),
    /// `is-valid-tools-call`: the value is a well-formed MCP
    /// tool call. The payload is null (shape check only) or
    /// `{ schema: {...} }` to validate the call's `arguments`.
    IsValidToolsCall(Value),
    /// `is-xml`: the string value parses as well-formed XML. The
    /// payload is null (well-formedness only) or `{ root: "name" }` to also
    /// assert the root element name.
    IsXml(Value),
    /// `is-sql`: the string value parses as SQL against the generic
    /// dialect. The payload is null; the matcher takes no options.
    IsSql(Value),
    /// `similar`: embedding cosine similarity to a reference. The
    /// payload is `{ value: "...", threshold: 0..1, model: "..." }`. Async like
    /// `llm-judge`: it needs a provider embedding call, so it runs through the
    /// async evaluator and is not wrapped by `not:`.
    Similar(Value),
    /// `citation`: fetch the response's cited URLs and judge whether each
    /// source supports the claim. The payload is
    /// `{ claim: "...", model: "...", threshold: 0..1, require_citation: bool }`.
    /// Async like `llm-judge`: it needs a provider judge call (and a network
    /// fetch), so it runs through the async evaluator and is not wrapped by
    /// `not:`.
    Citation(Value),
    /// `levenshtein`: edit distance from the stringified value
    /// to a reference is within a threshold. The payload is
    /// `{ value: "...", max: N }`.
    Levenshtein(Value),
    /// `cel`: a CEL (Common Expression Language) boolean predicate over
    /// the value. The payload is the expression source. The resolved
    /// target is bound into the context as the variable `value`, so
    /// `cel: "value.content.size() > 0 && !value.isError"` asserts over
    /// the value at `target`. Deterministic and in-process (no model, no
    /// IO), so it negates under `not:` like the other deterministic
    /// matchers. CEL is the same predicate language SBproxy uses, which
    /// is why we chose it over a second jq surface: jq stays `transform:`
    /// (reshape), CEL is `cel:` (boolean predicate).
    Cel(String),
    /// `not`: universal negation. Carries another matcher and
    /// inverts its pass/fail. We chose a single `not:` wrapper over
    /// promptfoo's per-type `not-<type>` prefixes because it composes over
    /// every deterministic matcher through one executor code path.
    Not(Box<Matcher>),
    /// `oneOf`: composition wrapper. Carries a list of inner
    /// matchers and passes when exactly one passes. Mirrors JSON Schema
    /// 2020-12 `oneOf` at the matcher level so suites can compose any
    /// matchers, not just schemas.
    OneOf(Vec<Matcher>),
    /// `anyOf`: composition wrapper. Carries a list of inner
    /// matchers and passes when at least one passes. Mirrors JSON Schema
    /// 2020-12 `anyOf` at the matcher level.
    AnyOf(Vec<Matcher>),
    /// `allOf`: composition wrapper. Carries a list of inner
    /// matchers and passes when every one passes. Mirrors JSON Schema
    /// 2020-12 `allOf` at the matcher level.
    AllOf(Vec<Matcher>),
}
