//! Typed errors for `mcptest-core`.
//!
//! Why a single enum at the crate root? Every public function in this crate
//! returns the same error type so callers can match on a small, stable
//! surface instead of juggling per-module errors. The CLI converts these to
//! `anyhow::Error` at the top with `?`.

use thiserror::Error;

/// Errors raised by `mcptest-core` operations.
///
/// New variants must be added to the bottom of the enum and tagged
/// `#[non_exhaustive]` by virtue of the enum-level attribute, so adding a
/// variant is not a breaking change for downstream matchers on `_`.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error {
    /// A matcher could not be evaluated because of a structural problem
    /// (for example, `regex` matched against a non-string field).
    #[error("matcher input invalid: {0}")]
    InvalidMatcherInput(String),

    /// The user-supplied regex did not compile.
    #[error("regex compile failed: {0}")]
    RegexCompile(#[from] regex::Error),

    /// The JSON Schema document supplied to the `schema` matcher is not a
    /// valid schema (not the same as "value did not validate against
    /// schema", which is a successful assertion failure, not an error).
    #[error("schema document invalid: {0}")]
    InvalidSchema(String),

    /// A `serde_json` operation failed (deserialization of a snapshot file,
    /// for example).
    #[error("json error: {0}")]
    Json(#[from] serde_json::Error),

    /// I/O while reading or writing a snapshot, schema reference, or other
    /// matcher-side file.
    #[error("io error: {0}")]
    Io(#[from] std::io::Error),

    /// A feature is reserved for a later milestone and not implemented in
    /// the current binary. Not currently produced: llm-judge shipped and no
    /// longer routes through it.
    #[error("not available until {milestone}: {feature}")]
    NotYetAvailable {
        /// The matcher or feature name the user requested.
        feature: &'static str,
        /// The milestone where it ships (for example `"v1.1"`).
        milestone: &'static str,
    },

    /// A YAML document failed to parse. Carries the underlying serde
    /// message so the caller can surface it without losing line and column
    /// context.
    #[error("yaml error: {0}")]
    Yaml(#[from] serde_yaml::Error),

    /// A compliance baseline document is well-formed YAML but does not
    /// match the expected shape (for example a duplicate `rule_id`).
    #[error("baseline invalid: {0}")]
    InvalidBaseline(String),

    /// A tokenizer backend (for example `tiktoken-rs`) failed to load
    /// or initialize its BPE table. In practice this only fires under
    /// catastrophic environment failures since the tables ship with the
    /// crate.
    #[error("tokenizer error: {0}")]
    Tokenizer(String),

    /// A model-compatibility operation failed (capture, diff, or
    /// invariant evaluation). Carries the underlying typed error so
    /// callers can match on the specific failure mode.
    #[error("model-compat: {0}")]
    ModelCompat(#[from] crate::model_compat::ModelCompatError),

    /// The schema document carries a `$ref` URI that points outside the
    /// document (any scheme other than the empty-document `#...` form).
    /// We refuse these at compile time so the validator never reaches the
    /// network. .
    #[error(
        "schema document contains external $ref `{uri}`; external $ref dereference is refused"
    )]
    SchemaExternalRef {
        /// The offending URI as it appeared in the schema.
        uri: String,
    },

    /// The schema document is nested deeper than the configured cap.
    /// Pathologically deep schemas can stall compile and validation;
    /// we refuse them before handing the schema to the validator. .
    #[error("schema document depth {depth} exceeds the cap {max}")]
    SchemaTooDeep {
        /// Observed nesting depth.
        depth: usize,
        /// The configured maximum.
        max: usize,
    },

    /// Validation took longer than the per-call wall-clock budget. A
    /// large enough value plus a complex enough schema can in principle
    /// take arbitrary time; the budget keeps a suite test from running
    /// away. .
    #[error("schema validation exceeded the {budget_ms} ms budget")]
    SchemaValidationTimedOut {
        /// The budget the call was allowed.
        budget_ms: u64,
    },
}
