//! Typed validation error enum returned from all MCP write-tool input validation.

use serde::{Deserialize, Serialize};
use std::fmt;

/// Structured error from input validation.
///
/// Each variant carries exactly the data a caller needs to act on the error.
/// Serialises to serde's default externally-tagged form, e.g.
/// `{"FieldTooLong":{"field":"description","max":200,"actual":287}}`.
/// Callers can pattern-match on the JSON tag without parsing prose.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ValidationError {
    /// A required field was absent from the input.
    FieldMissing { field: String },
    /// A field value is empty (or whitespace-only) after trimming.
    EmptyAfterTrim { field: String },
    /// A field value exceeds the maximum allowed length.
    FieldTooLong {
        field: String,
        max: usize,
        actual: usize,
    },
    /// A field value contains a character outside the allowed set.
    InvalidCharacter {
        field: String,
        ch: char,
        position: usize,
    },
    /// A field value contains a tool-call grammar fragment that must be rejected.
    AntiGrammarToken { field: String, token: String },
    /// A field value matches the unfilled "As a …, I want …, so that …" boilerplate template.
    BoilerplateUserStory { field: String },
    /// A numeric field value is outside its allowed range.
    OutOfRange {
        field: String,
        min: i64,
        max: i64,
        actual: i64,
    },
    /// A list field has fewer items than the minimum.
    TooFewItems {
        field: String,
        min: usize,
        actual: usize,
    },
    /// A list field has more items than the maximum.
    TooManyItems {
        field: String,
        max: usize,
        actual: usize,
    },
    /// A field value is not valid UTF-8.
    InvalidUtf8 { field: String },
    /// A `depends_on` entry references the same item being created or updated.
    SelfReference { field: String },
    /// A field's value is not one of the accepted choices.
    InvalidValue {
        /// Field name.
        field: String,
        /// The value that was supplied.
        actual: String,
        /// The complete set of accepted values.
        allowed: Vec<String>,
    },
}

impl fmt::Display for ValidationError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::FieldMissing { field } => {
                write!(f, "field '{field}' is required but was not provided")
            }
            Self::EmptyAfterTrim { field } => {
                write!(f, "field '{field}' must not be empty or whitespace-only")
            }
            Self::FieldTooLong { field, max, actual } => {
                write!(f, "field '{field}' is too long ({actual} chars, max {max})")
            }
            Self::InvalidCharacter {
                field,
                ch,
                position,
            } => {
                write!(
                    f,
                    "field '{field}' contains invalid character {ch:?} at position {position}"
                )
            }
            Self::AntiGrammarToken { field, token } => {
                write!(
                    f,
                    "field '{field}' contains a tool-call grammar fragment: {token:?}"
                )
            }
            Self::BoilerplateUserStory { field } => {
                write!(
                    f,
                    "field '{field}' looks like the unfilled 'As a …, I want …, so that …' \
                     template. Rephrase it as a concrete user story in plain language, \
                     describing the actual actor, action, and benefit. If you cannot \
                     determine the right phrasing, ask the commissioning user."
                )
            }
            Self::OutOfRange {
                field,
                min,
                max,
                actual,
            } => {
                write!(
                    f,
                    "field '{field}' value {actual} is out of allowed range [{min}, {max}]"
                )
            }
            Self::TooFewItems { field, min, actual } => {
                write!(
                    f,
                    "field '{field}' has too few items ({actual}; minimum {min})"
                )
            }
            Self::TooManyItems { field, max, actual } => {
                write!(
                    f,
                    "field '{field}' has too many items ({actual}; maximum {max})"
                )
            }
            Self::InvalidUtf8 { field } => {
                write!(f, "field '{field}' contains invalid UTF-8")
            }
            Self::SelfReference { field } => {
                write!(
                    f,
                    "field '{field}' contains a self-reference (depends on itself)"
                )
            }
            Self::InvalidValue {
                field,
                actual,
                allowed,
            } => {
                write!(
                    f,
                    "field '{field}' value {actual:?} is not one of the allowed values: {}",
                    allowed.join(", ")
                )
            }
        }
    }
}

/// Serialise a slice of validation errors as a pretty-printed JSON string.
///
/// Used to turn `Vec<ValidationError>` into the `Err(String)` value returned by
/// MCP tool handlers.
pub fn format_errors_as_json(errors: &[ValidationError]) -> String {
    serde_json::to_string_pretty(errors).unwrap_or_else(|_| format!("{errors:?}"))
}
