//! Type-safe schema definitions for MCP elicitation requests.
//!
//! This module provides strongly-typed schema definitions for elicitation requests
//! that comply with the MCP 2025-06-18 specification. Elicitation schemas must be
//! objects with primitive-typed properties.
//!
//! # Example
//!
//! ```rust
//! use rmcp::model::*;
//!
//! let schema = ElicitationSchema::builder()
//!     .required_email("email")
//!     .required_integer("age", 0, 150)
//!     .optional_bool("newsletter", false)
//!     .build();
//! ```

use std::{borrow::Cow, collections::BTreeMap, marker::PhantomData};

use serde::{Deserialize, Serialize};

use crate::{const_string, model::ConstString};

// =============================================================================
// CONST TYPES FOR JSON SCHEMA TYPE FIELD
// =============================================================================

const_string!(ObjectTypeConst = "object");
const_string!(StringTypeConst = "string");
const_string!(NumberTypeConst = "number");
const_string!(IntegerTypeConst = "integer");
const_string!(BooleanTypeConst = "boolean");
const_string!(EnumTypeConst = "string");
const_string!(ArrayTypeConst = "array");

// =============================================================================
// PRIMITIVE SCHEMA DEFINITIONS
// =============================================================================

/// Primitive schema definition for elicitation properties.
///
/// According to MCP 2025-06-18 specification, elicitation schemas must have
/// properties of primitive types only (string, number, integer, boolean, enum).
///
/// Note: Put Enum as the first variant to avoid ambiguity during deserialization.
/// This is due to the fact that EnumSchema can contain StringSchema internally and serde
/// uses first match wins strategy when deserializing untagged enums.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
#[non_exhaustive]
pub enum PrimitiveSchemaDefinition {
    /// Enum property (explicit enum schema)
    Enum(EnumSchema),
    /// String property (with optional enum constraint)
    String(StringSchema),
    /// Number property (with optional enum constraint)
    Number(NumberSchema),
    /// Integer property (with optional enum constraint)
    Integer(IntegerSchema),
    /// Boolean property
    Boolean(BooleanSchema),
}

#[deprecated(since = "2.0.0", note = "Renamed to PrimitiveSchemaDefinition")]
pub type PrimitiveSchema = PrimitiveSchemaDefinition;

// =============================================================================
// STRING SCHEMA
// =============================================================================

/// String format types allowed by the MCP specification.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub enum StringFormat {
    /// Email address format
    Email,
    /// URI format
    Uri,
    /// Date format (YYYY-MM-DD)
    Date,
    /// Date-time format (ISO 8601)
    DateTime,
}

/// Schema definition for string properties.
///
/// Compliant with MCP 2025-06-18 specification for elicitation schemas.
/// Supports only the fields allowed by the MCP spec:
/// - format limited to: "email", "uri", "date", "date-time"
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct StringSchema {
    /// Type discriminator
    #[serde(rename = "type")]
    pub type_: StringTypeConst,

    /// Optional title for the schema
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<Cow<'static, str>>,

    /// Human-readable description
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<Cow<'static, str>>,

    /// Minimum string length
    #[serde(skip_serializing_if = "Option::is_none")]
    pub min_length: Option<u32>,

    /// Maximum string length
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_length: Option<u32>,

    /// String format - limited to: "email", "uri", "date", "date-time"
    #[serde(skip_serializing_if = "Option::is_none")]
    pub format: Option<StringFormat>,

    /// Default value
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default: Option<String>,
}

impl Default for StringSchema {
    fn default() -> Self {
        Self {
            type_: StringTypeConst,
            title: None,
            description: None,
            min_length: None,
            max_length: None,
            format: None,
            default: None,
        }
    }
}

impl StringSchema {
    /// Create a new string schema
    pub fn new() -> Self {
        Self::default()
    }

    /// Create an email string schema
    pub fn email() -> Self {
        Self {
            format: Some(StringFormat::Email),
            ..Default::default()
        }
    }

    /// Create a URI string schema
    pub fn uri() -> Self {
        Self {
            format: Some(StringFormat::Uri),
            ..Default::default()
        }
    }

    /// Create a date string schema
    pub fn date() -> Self {
        Self {
            format: Some(StringFormat::Date),
            ..Default::default()
        }
    }

    /// Create a date-time string schema
    pub fn date_time() -> Self {
        Self {
            format: Some(StringFormat::DateTime),
            ..Default::default()
        }
    }

    /// Set title
    pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
        self.title = Some(title.into());
        self
    }

    /// Set description
    pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
        self.description = Some(description.into());
        self
    }

    /// Set minimum and maximum length
    pub fn with_length(mut self, min: u32, max: u32) -> Result<Self, &'static str> {
        if min > max {
            return Err("min_length must be <= max_length");
        }
        self.min_length = Some(min);
        self.max_length = Some(max);
        Ok(self)
    }

    /// Set minimum and maximum length (panics on invalid input)
    pub fn length(mut self, min: u32, max: u32) -> Self {
        assert!(min <= max, "min_length must be <= max_length");
        self.min_length = Some(min);
        self.max_length = Some(max);
        self
    }

    /// Set minimum length
    pub fn min_length(mut self, min: u32) -> Self {
        self.min_length = Some(min);
        self
    }

    /// Set maximum length
    pub fn max_length(mut self, max: u32) -> Self {
        self.max_length = Some(max);
        self
    }

    /// Set format (limited to: "email", "uri", "date", "date-time")
    pub fn format(mut self, format: StringFormat) -> Self {
        self.format = Some(format);
        self
    }

    /// Set default value
    pub fn with_default(mut self, default: impl Into<String>) -> Self {
        self.default = Some(default.into());
        self
    }
}

// =============================================================================
// NUMBER SCHEMA
// =============================================================================

/// Schema definition for number properties (floating-point).
///
/// Compliant with MCP 2025-06-18 specification for elicitation schemas.
/// Supports only the fields allowed by the MCP spec.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct NumberSchema {
    /// Type discriminator
    #[serde(rename = "type")]
    pub type_: NumberTypeConst,

    /// Optional title for the schema
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<Cow<'static, str>>,

    /// Human-readable description
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<Cow<'static, str>>,

    /// Minimum value (inclusive)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub minimum: Option<f64>,

    /// Maximum value (inclusive)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub maximum: Option<f64>,

    /// Default value
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default: Option<f64>,
}

impl Default for NumberSchema {
    fn default() -> Self {
        Self {
            type_: NumberTypeConst,
            title: None,
            description: None,
            minimum: None,
            maximum: None,
            default: None,
        }
    }
}

impl NumberSchema {
    /// Create a new number schema
    pub fn new() -> Self {
        Self::default()
    }

    /// Set minimum and maximum (inclusive)
    pub fn with_range(mut self, min: f64, max: f64) -> Result<Self, &'static str> {
        if min > max {
            return Err("minimum must be <= maximum");
        }
        self.minimum = Some(min);
        self.maximum = Some(max);
        Ok(self)
    }

    /// Set minimum and maximum (panics on invalid input)
    pub fn range(mut self, min: f64, max: f64) -> Self {
        assert!(min <= max, "minimum must be <= maximum");
        self.minimum = Some(min);
        self.maximum = Some(max);
        self
    }

    /// Set minimum (inclusive)
    pub fn minimum(mut self, min: f64) -> Self {
        self.minimum = Some(min);
        self
    }

    /// Set maximum (inclusive)
    pub fn maximum(mut self, max: f64) -> Self {
        self.maximum = Some(max);
        self
    }

    /// Set title
    pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
        self.title = Some(title.into());
        self
    }

    /// Set description
    pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
        self.description = Some(description.into());
        self
    }

    /// Set default value
    pub fn with_default(mut self, default: f64) -> Self {
        self.default = Some(default);
        self
    }
}

// =============================================================================
// INTEGER SCHEMA
// =============================================================================

/// Schema definition for integer properties.
///
/// Compliant with MCP 2025-06-18 specification for elicitation schemas.
/// Supports only the fields allowed by the MCP spec.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct IntegerSchema {
    /// Type discriminator
    #[serde(rename = "type")]
    pub type_: IntegerTypeConst,

    /// Optional title for the schema
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<Cow<'static, str>>,

    /// Human-readable description
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<Cow<'static, str>>,

    /// Minimum value (inclusive)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub minimum: Option<i64>,

    /// Maximum value (inclusive)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub maximum: Option<i64>,

    /// Default value
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default: Option<i64>,
}

impl Default for IntegerSchema {
    fn default() -> Self {
        Self {
            type_: IntegerTypeConst,
            title: None,
            description: None,
            minimum: None,
            maximum: None,
            default: None,
        }
    }
}

impl IntegerSchema {
    /// Create a new integer schema
    pub fn new() -> Self {
        Self::default()
    }

    /// Set minimum and maximum (inclusive)
    pub fn with_range(mut self, min: i64, max: i64) -> Result<Self, &'static str> {
        if min > max {
            return Err("minimum must be <= maximum");
        }
        self.minimum = Some(min);
        self.maximum = Some(max);
        Ok(self)
    }

    /// Set minimum and maximum (panics on invalid input)
    pub fn range(mut self, min: i64, max: i64) -> Self {
        assert!(min <= max, "minimum must be <= maximum");
        self.minimum = Some(min);
        self.maximum = Some(max);
        self
    }

    /// Set minimum (inclusive)
    pub fn minimum(mut self, min: i64) -> Self {
        self.minimum = Some(min);
        self
    }

    /// Set maximum (inclusive)
    pub fn maximum(mut self, max: i64) -> Self {
        self.maximum = Some(max);
        self
    }

    /// Set title
    pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
        self.title = Some(title.into());
        self
    }

    /// Set description
    pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
        self.description = Some(description.into());
        self
    }

    /// Set default value
    pub fn with_default(mut self, default: i64) -> Self {
        self.default = Some(default);
        self
    }
}

// =============================================================================
// BOOLEAN SCHEMA
// =============================================================================

/// Schema definition for boolean properties.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct BooleanSchema {
    /// Type discriminator
    #[serde(rename = "type")]
    pub type_: BooleanTypeConst,

    /// Optional title for the schema
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<Cow<'static, str>>,

    /// Human-readable description
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<Cow<'static, str>>,

    /// Default value
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default: Option<bool>,
}

impl Default for BooleanSchema {
    fn default() -> Self {
        Self {
            type_: BooleanTypeConst,
            title: None,
            description: None,
            default: None,
        }
    }
}

impl BooleanSchema {
    /// Create a new boolean schema
    pub fn new() -> Self {
        Self::default()
    }

    /// Set title
    pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
        self.title = Some(title.into());
        self
    }

    /// Set description
    pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
        self.description = Some(description.into());
        self
    }

    /// Set default value
    pub fn with_default(mut self, default: bool) -> Self {
        self.default = Some(default);
        self
    }
}

// =============================================================================
// ENUM SCHEMA
// =============================================================================

/// Schema definition for enum properties.
///
/// Represent single entry for titled item
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct ConstTitle {
    #[serde(rename = "const")]
    pub const_: String,
    pub title: String,
}

impl ConstTitle {
    /// Create a new ConstTitle.
    pub fn new(const_: impl Into<String>, title: impl Into<String>) -> Self {
        Self {
            const_: const_.into(),
            title: title.into(),
        }
    }
}

/// Legacy enum schema, keep for backward compatibility
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct LegacyEnumSchema {
    #[serde(rename = "type")]
    pub type_: StringTypeConst,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<Cow<'static, str>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<Cow<'static, str>>,
    #[serde(rename = "enum")]
    pub enum_: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub enum_names: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default: Option<String>,
}

impl LegacyEnumSchema {
    pub fn new(enum_values: Vec<String>) -> Self {
        Self {
            type_: StringTypeConst,
            title: None,
            description: None,
            enum_: enum_values,
            enum_names: None,
            default: None,
        }
    }
}

/// Untitled single-select
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct UntitledSingleSelectEnumSchema {
    #[serde(rename = "type")]
    pub type_: StringTypeConst,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<Cow<'static, str>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<Cow<'static, str>>,
    #[serde(rename = "enum")]
    pub enum_: Vec<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default: Option<String>,
}

/// Titled single-select
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct TitledSingleSelectEnumSchema {
    #[serde(rename = "type")]
    pub type_: StringTypeConst,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<Cow<'static, str>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<Cow<'static, str>>,
    #[serde(rename = "oneOf")]
    pub one_of: Vec<ConstTitle>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default: Option<String>,
}

impl TitledSingleSelectEnumSchema {
    /// Create a new TitledSingleSelectEnumSchema.
    pub fn new(one_of: Vec<ConstTitle>) -> Self {
        Self {
            type_: StringTypeConst,
            title: None,
            description: None,
            one_of,
            default: None,
        }
    }
}

/// Combined single-select
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
#[non_exhaustive]
pub enum SingleSelectEnumSchema {
    Untitled(UntitledSingleSelectEnumSchema),
    Titled(TitledSingleSelectEnumSchema),
}

/// Items for untitled multi-select options
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct UntitledItems {
    #[serde(rename = "type")]
    pub type_: StringTypeConst,
    #[serde(rename = "enum")]
    pub enum_: Vec<String>,
}

impl UntitledItems {
    pub fn new(enum_values: Vec<String>) -> Self {
        Self {
            type_: StringTypeConst,
            enum_: enum_values,
        }
    }
}

/// Items for titled multi-select options
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[non_exhaustive]
pub struct TitledItems {
    // MCP spec requires "anyOf" for multi-select enums (allows any combination)
    // Alias "oneOf" for compatibility with schemars
    #[serde(rename = "anyOf", alias = "oneOf")]
    pub any_of: Vec<ConstTitle>,
}

impl TitledItems {
    /// Create a new TitledItems.
    pub fn new(any_of: Vec<ConstTitle>) -> Self {
        Self { any_of }
    }
}

/// Multi-select untitled options
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct UntitledMultiSelectEnumSchema {
    #[serde(rename = "type")]
    pub type_: ArrayTypeConst,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<Cow<'static, str>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<Cow<'static, str>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub min_items: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_items: Option<u64>,
    pub items: UntitledItems,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default: Option<Vec<String>>,
}

/// Multi-select titled options
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct TitledMultiSelectEnumSchema {
    #[serde(rename = "type")]
    pub type_: ArrayTypeConst,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<Cow<'static, str>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<Cow<'static, str>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub min_items: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_items: Option<u64>,
    pub items: TitledItems,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub default: Option<Vec<String>>,
}

impl TitledMultiSelectEnumSchema {
    /// Create a new TitledMultiSelectEnumSchema.
    pub fn new(items: TitledItems) -> Self {
        Self {
            type_: ArrayTypeConst,
            title: None,
            description: None,
            min_items: None,
            max_items: None,
            items,
            default: None,
        }
    }

    /// Set the title.
    pub fn with_title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
        self.title = Some(title.into());
        self
    }

    /// Set the description.
    pub fn with_description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
        self.description = Some(description.into());
        self
    }

    /// Set the minimum number of items.
    pub fn with_min_items(mut self, min_items: u64) -> Self {
        self.min_items = Some(min_items);
        self
    }

    /// Set the maximum number of items.
    pub fn with_max_items(mut self, max_items: u64) -> Self {
        self.max_items = Some(max_items);
        self
    }

    /// Set the default values.
    pub fn with_default(mut self, default: Vec<String>) -> Self {
        self.default = Some(default);
        self
    }
}

/// Multi-select enum options
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
#[non_exhaustive]
pub enum MultiSelectEnumSchema {
    Untitled(UntitledMultiSelectEnumSchema),
    Titled(TitledMultiSelectEnumSchema),
}

/// Compliant with MCP 2025-06-18 specification for elicitation schemas.
/// Enums must have string type for values and can optionally include human-readable names.
///
/// # Example
///
/// ```rust
/// use rmcp::model::*;
///
/// let enum_schema = EnumSchema::builder(vec!["US".to_string(), "UK".to_string()])
///    .multiselect()
///    .min_items(1u64).expect("Min items should be correct value")
///    .max_items(4u64).expect("Max items should be correct value")
///    .description("Country code")
///    .build();
/// ```
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
#[non_exhaustive]
pub enum EnumSchema {
    Single(SingleSelectEnumSchema),
    Multi(MultiSelectEnumSchema),
    Legacy(LegacyEnumSchema),
}

/// Marker type for single-select enum builder
#[derive(Debug)]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct SingleSelect;
/// Marker type for multi-select enum builder
#[derive(Debug)]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct MultiSelect;
/// Builder for EnumSchema
/// Allows to create various enum schema types (single/multi select, titled/untitled)
/// with validation of provided parameters
///
/// # Example
/// ```rust
/// use rmcp::model::*;
/// let enum_schema = EnumSchema::builder(vec!["Red".to_string(), "Green".to_string(), "Blue".to_string()])
///  .multiselect()
///  .enum_titles(vec!["Red Color".to_string(), "Green Color".to_string(), "Blue Color".to_string()])
///  .expect("Number of titles should match number of values")
///  .min_items(1u64).expect("Min items should be correct value")
///  .max_items(3u64).expect("Max items should be correct value")
///  .description("Select your favorite colors")
///  .build();
/// ```
#[derive(Debug)]
pub struct EnumSchemaBuilder<T> {
    /// Enum values
    enum_values: Vec<String>,
    /// If true generate Titled EnumSchema, UnTitled otherwise
    titled: bool,
    /// Title of EnumSchema
    title: Option<Cow<'static, str>>,
    /// Description of EnumSchema
    description: Option<Cow<'static, str>>,
    /// Titles of given enum values
    enum_titles: Vec<String>,
    /// Minimum number of items to choose for MultiSelect
    min_items: Option<u64>,
    /// Maximum number of items to choose for MultiSelect
    max_items: Option<u64>,
    /// Default values for enum
    default: Vec<String>,
    select_type: PhantomData<T>,
}

/// Default implementation for single-select enum builder
impl Default for EnumSchemaBuilder<SingleSelect> {
    fn default() -> Self {
        Self {
            title: None,
            description: None,
            titled: false,
            enum_titles: Vec::new(),
            enum_values: Vec::new(),
            min_items: None,
            max_items: None,
            default: Vec::new(),
            select_type: PhantomData,
        }
    }
}

/// Common enum schema builder methods
impl<T> EnumSchemaBuilder<T> {
    /// Set title of enum schema
    pub fn title(mut self, value: impl Into<Cow<'static, str>>) -> Self {
        self.title = Some(value.into());
        self
    }

    /// Set description of enum schema
    pub fn description(mut self, value: impl Into<Cow<'static, str>>) -> Self {
        self.description = Some(value.into());
        self
    }

    /// Set enum as untitled
    /// Clears any previously set titles
    pub fn untitled(mut self) -> Self {
        self.enum_titles = Vec::new();
        self.titled = false;
        self
    }

    /// Set titles to enum values. Also, implicitly set this enum schema as titled
    pub fn enum_titles(mut self, titles: Vec<String>) -> Result<EnumSchemaBuilder<T>, String> {
        if titles.len() != self.enum_values.len() {
            return Err(format!(
                "Provided number of titles do not match number of values: expected {}, but got {}",
                self.enum_values.len(),
                titles.len()
            ));
        }
        self.titled = true;
        self.enum_titles = titles;
        Ok(self)
    }
}

/// Enum selection builder for single-select enums
impl EnumSchemaBuilder<SingleSelect> {
    pub fn new(values: Vec<String>) -> EnumSchemaBuilder<SingleSelect> {
        EnumSchemaBuilder {
            enum_values: values,
            ..Default::default()
        }
    }

    /// Transition to multi-select enum builder.
    ///
    /// Clears any previously set default values and resets min/max items.
    /// After this transition, you can use `min_items()`, `max_items()`, and
    /// `with_default()` for multi-select semantics.
    pub fn multiselect(self) -> EnumSchemaBuilder<MultiSelect> {
        EnumSchemaBuilder {
            enum_values: self.enum_values,
            titled: self.titled,
            title: self.title,
            description: self.description,
            enum_titles: self.enum_titles,
            min_items: None,
            max_items: None,
            default: Vec::new(), // Clear default for multi-select
            select_type: PhantomData,
        }
    }

    /// Set default value
    pub fn with_default(
        mut self,
        default_value: impl Into<String>,
    ) -> Result<EnumSchemaBuilder<SingleSelect>, String> {
        let value: String = default_value.into();
        if !self.enum_values.contains(&value) {
            return Err("Provided default value is not in enum values".to_string());
        }
        self.default = vec![value];
        Ok(self)
    }

    /// Build enum schema
    pub fn build(mut self) -> EnumSchema {
        match self.titled {
            false => EnumSchema::Single(SingleSelectEnumSchema::Untitled(
                UntitledSingleSelectEnumSchema {
                    type_: StringTypeConst,
                    title: self.title,
                    description: self.description,
                    enum_: self.enum_values,
                    default: self.default.pop(),
                },
            )),
            true => EnumSchema::Single(SingleSelectEnumSchema::Titled(
                TitledSingleSelectEnumSchema {
                    type_: StringTypeConst,
                    title: self.title,
                    description: self.description,
                    one_of: self
                        .enum_titles
                        .into_iter()
                        .zip(self.enum_values)
                        .map(|(title, const_)| ConstTitle { const_, title })
                        .collect(),
                    default: self.default.pop(),
                },
            )),
        }
    }
}

/// Enum selection builder for multi-select enums
impl EnumSchemaBuilder<MultiSelect> {
    /// Set enum as single-select
    /// If it was multi-select, clear default values
    pub fn single_select(self) -> EnumSchemaBuilder<SingleSelect> {
        EnumSchemaBuilder {
            enum_values: self.enum_values,
            titled: self.titled,
            title: self.title,
            description: self.description,
            enum_titles: self.enum_titles,
            min_items: None,
            max_items: None,
            default: Vec::new(), // Clear default for single-select
            select_type: PhantomData,
        }
    }

    /// Set default values
    pub fn with_default(
        mut self,
        default_values: Vec<String>,
    ) -> Result<EnumSchemaBuilder<MultiSelect>, String> {
        for value in &default_values {
            if !self.enum_values.contains(value) {
                return Err("One of the provided default values is not in enum values".to_string());
            }
        }
        if let Some(min) = self.min_items {
            if (default_values.len() as u64) < min {
                return Err("Number of provided default values is less than min_items".to_string());
            }
        }
        if let Some(max) = self.max_items {
            if (default_values.len() as u64) > max {
                return Err(
                    "Number of provided default values is greater than max_items".to_string(),
                );
            }
        }
        self.default = default_values;
        Ok(self)
    }

    /// Set minimal number of items for multi-select enum options
    pub fn min_items(mut self, value: u64) -> Result<EnumSchemaBuilder<MultiSelect>, String> {
        if let Some(max) = self.max_items
            && value > max
        {
            return Err("Provided value is greater than max_items".to_string());
        }
        self.min_items = Some(value);
        Ok(self)
    }

    /// Set maximal number of items for multi-select enum options
    pub fn max_items(mut self, value: u64) -> Result<EnumSchemaBuilder<MultiSelect>, String> {
        if let Some(min) = self.min_items
            && value < min
        {
            return Err("Provided value is less than min_items".to_string());
        }
        self.max_items = Some(value);
        Ok(self)
    }

    /// Build enum schema
    pub fn build(self) -> EnumSchema {
        match self.titled {
            false => EnumSchema::Multi(MultiSelectEnumSchema::Untitled(
                UntitledMultiSelectEnumSchema {
                    type_: ArrayTypeConst,
                    title: self.title,
                    description: self.description,
                    min_items: self.min_items,
                    max_items: self.max_items,
                    items: UntitledItems {
                        type_: StringTypeConst,
                        enum_: self.enum_values,
                    },
                    default: if self.default.is_empty() {
                        None
                    } else {
                        Some(self.default)
                    },
                },
            )),
            true => EnumSchema::Multi(MultiSelectEnumSchema::Titled(TitledMultiSelectEnumSchema {
                type_: ArrayTypeConst,
                title: self.title,
                description: self.description,
                min_items: self.min_items,
                max_items: self.max_items,
                items: TitledItems {
                    any_of: self
                        .enum_titles
                        .into_iter()
                        .zip(self.enum_values)
                        .map(|(title, const_)| ConstTitle { const_, title })
                        .collect(),
                },
                default: if self.default.is_empty() {
                    None
                } else {
                    Some(self.default)
                },
            })),
        }
    }
}

impl EnumSchema {
    /// Creates a new `EnumSchemaBuilder` with the given enum values.
    ///
    /// This convenience method allows you to construct an enum schema by specifying
    /// the possible string values for the enum. Use the returned builder to further
    /// configure the schema before building it.
    ///
    /// # Arguments
    ///
    /// * `values` - A vector of strings representing the allowed enum values.
    ///
    /// # Example
    ///
    /// ```
    /// use rmcp::model::*;
    ///
    /// let enum_schema = EnumSchema::builder(vec!["A".to_string(), "B".to_string()]).
    ///     with_default("A").
    ///     expect("Default value should be valid").
    ///     enum_titles(vec!["Option A".to_string(), "Option B".to_string()]).
    ///     expect("Number of titles should match number of values").
    ///     build();
    /// ```
    pub fn builder(values: Vec<String>) -> EnumSchemaBuilder<SingleSelect> {
        EnumSchemaBuilder::new(values)
    }
}

// =============================================================================
// ELICITATION SCHEMA
// =============================================================================

/// Type-safe elicitation schema for requesting structured user input.
///
/// This enforces the MCP 2025-06-18 specification that elicitation schemas
/// must be objects with primitive-typed properties.
///
/// # Example
///
/// ```rust
/// use rmcp::model::*;
///
/// let schema = ElicitationSchema::builder()
///     .required_email("email")
///     .required_integer("age", 0, 150)
///     .optional_bool("newsletter", false)
///     .build();
/// ```
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct ElicitationSchema {
    /// Always "object" for elicitation schemas
    #[serde(rename = "type")]
    pub type_: ObjectTypeConst,

    /// Optional title for the schema
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<Cow<'static, str>>,

    /// Property definitions (must be primitive types)
    pub properties: BTreeMap<String, PrimitiveSchemaDefinition>,

    /// List of required property names
    #[serde(skip_serializing_if = "Option::is_none")]
    pub required: Option<Vec<String>>,

    /// Optional description of what this schema represents
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<Cow<'static, str>>,
}

impl ElicitationSchema {
    /// Create a new elicitation schema with the given properties
    pub fn new(properties: BTreeMap<String, PrimitiveSchemaDefinition>) -> Self {
        Self {
            type_: ObjectTypeConst,
            title: None,
            properties,
            required: None,
            description: None,
        }
    }

    /// Convert from a JSON Schema object (typically generated by schemars)
    ///
    /// This allows converting from JsonObject to ElicitationSchema, which is useful
    /// when working with automatically generated schemas from types.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// use rmcp::model::*;
    ///
    /// let json_schema = schema_for_type::<MyType>();
    /// let elicitation_schema = ElicitationSchema::from_json_schema(json_schema)?;
    /// ```
    ///
    /// # Errors
    ///
    /// Returns a [`serde_json::Error`] if the JSON object cannot be deserialized
    /// into a valid ElicitationSchema.
    pub fn from_json_schema(schema: crate::model::JsonObject) -> Result<Self, serde_json::Error> {
        serde_json::from_value(serde_json::Value::Object(schema))
    }

    /// Generate an ElicitationSchema from a Rust type that implements JsonSchema
    ///
    /// This is a convenience method that combines schema generation and conversion.
    /// It uses the same schema generation settings as the rest of the MCP SDK.
    ///
    /// # Example
    ///
    /// ```rust,ignore
    /// use rmcp::model::*;
    /// use schemars::JsonSchema;
    /// use serde::{Deserialize, Serialize};
    ///
    /// #[derive(JsonSchema, Serialize, Deserialize)]
    /// struct UserInput {
    ///     name: String,
    ///     age: u32,
    /// }
    ///
    /// let schema = ElicitationSchema::from_type::<UserInput>()?;
    /// ```
    ///
    /// # Errors
    ///
    /// Returns a [`serde_json::Error`] if the generated schema cannot be converted
    /// to a valid ElicitationSchema.
    #[cfg(feature = "schemars")]
    pub fn from_type<T>() -> Result<Self, serde_json::Error>
    where
        T: schemars::JsonSchema,
    {
        use crate::schemars::generate::SchemaSettings;

        let mut settings = SchemaSettings::draft07();
        settings.transforms = vec![Box::new(schemars::transform::AddNullable::default())];
        let generator = settings.into_generator();
        let schema = generator.into_root_schema_for::<T>();
        let object = serde_json::to_value(schema).expect("failed to serialize schema");
        match object {
            serde_json::Value::Object(object) => Self::from_json_schema(object),
            _ => panic!(
                "Schema serialization produced non-object value: expected JSON object but got {:?}",
                object
            ),
        }
    }

    /// Set the required fields
    pub fn with_required(mut self, required: Vec<String>) -> Self {
        self.required = Some(required);
        self
    }

    /// Set the title
    pub fn with_title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
        self.title = Some(title.into());
        self
    }

    /// Set the description
    pub fn with_description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
        self.description = Some(description.into());
        self
    }

    /// Create a builder for constructing elicitation schemas fluently
    pub fn builder() -> ElicitationSchemaBuilder {
        ElicitationSchemaBuilder::new()
    }
}

// =============================================================================
// BUILDER
// =============================================================================

/// Fluent builder for constructing elicitation schemas.
///
/// # Example
///
/// ```rust
/// use rmcp::model::*;
///
/// let schema = ElicitationSchema::builder()
///     .required_email("email")
///     .required_integer("age", 0, 150)
///     .optional_bool("newsletter", false)
///     .description("User registration")
///     .build();
/// ```
#[derive(Debug, Default)]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ElicitationSchemaBuilder {
    pub properties: BTreeMap<String, PrimitiveSchemaDefinition>,
    pub required: Vec<String>,
    pub title: Option<Cow<'static, str>>,
    pub description: Option<Cow<'static, str>>,
}

impl ElicitationSchemaBuilder {
    /// Create a new builder
    pub fn new() -> Self {
        Self::default()
    }

    /// Add a property to the schema
    pub fn property(mut self, name: impl Into<String>, schema: PrimitiveSchemaDefinition) -> Self {
        self.properties.insert(name.into(), schema);
        self
    }

    /// Add a required property to the schema
    pub fn required_property(
        mut self,
        name: impl Into<String>,
        schema: PrimitiveSchemaDefinition,
    ) -> Self {
        let name_str = name.into();
        self.required.push(name_str.clone());
        self.properties.insert(name_str, schema);
        self
    }

    // ===========================================================================
    // TYPED PROPERTY METHODS - Cleaner API without PrimitiveSchemaDefinition wrapper
    // ===========================================================================

    /// Add a string property with custom builder (required)
    pub fn string_property(
        mut self,
        name: impl Into<String>,
        f: impl FnOnce(StringSchema) -> StringSchema,
    ) -> Self {
        self.properties.insert(
            name.into(),
            PrimitiveSchemaDefinition::String(f(StringSchema::new())),
        );
        self
    }

    /// Add a required string property with custom builder
    pub fn required_string_property(
        mut self,
        name: impl Into<String>,
        f: impl FnOnce(StringSchema) -> StringSchema,
    ) -> Self {
        let name_str = name.into();
        self.required.push(name_str.clone());
        self.properties.insert(
            name_str,
            PrimitiveSchemaDefinition::String(f(StringSchema::new())),
        );
        self
    }

    /// Add a number property with custom builder
    pub fn number_property(
        mut self,
        name: impl Into<String>,
        f: impl FnOnce(NumberSchema) -> NumberSchema,
    ) -> Self {
        self.properties.insert(
            name.into(),
            PrimitiveSchemaDefinition::Number(f(NumberSchema::new())),
        );
        self
    }

    /// Add a required number property with custom builder
    pub fn required_number_property(
        mut self,
        name: impl Into<String>,
        f: impl FnOnce(NumberSchema) -> NumberSchema,
    ) -> Self {
        let name_str = name.into();
        self.required.push(name_str.clone());
        self.properties.insert(
            name_str,
            PrimitiveSchemaDefinition::Number(f(NumberSchema::new())),
        );
        self
    }

    /// Add an integer property with custom builder
    pub fn integer_property(
        mut self,
        name: impl Into<String>,
        f: impl FnOnce(IntegerSchema) -> IntegerSchema,
    ) -> Self {
        self.properties.insert(
            name.into(),
            PrimitiveSchemaDefinition::Integer(f(IntegerSchema::new())),
        );
        self
    }

    /// Add a required integer property with custom builder
    pub fn required_integer_property(
        mut self,
        name: impl Into<String>,
        f: impl FnOnce(IntegerSchema) -> IntegerSchema,
    ) -> Self {
        let name_str = name.into();
        self.required.push(name_str.clone());
        self.properties.insert(
            name_str,
            PrimitiveSchemaDefinition::Integer(f(IntegerSchema::new())),
        );
        self
    }

    /// Add a boolean property with custom builder
    pub fn bool_property(
        mut self,
        name: impl Into<String>,
        f: impl FnOnce(BooleanSchema) -> BooleanSchema,
    ) -> Self {
        self.properties.insert(
            name.into(),
            PrimitiveSchemaDefinition::Boolean(f(BooleanSchema::new())),
        );
        self
    }

    /// Add a required boolean property with custom builder
    pub fn required_bool_property(
        mut self,
        name: impl Into<String>,
        f: impl FnOnce(BooleanSchema) -> BooleanSchema,
    ) -> Self {
        let name_str = name.into();
        self.required.push(name_str.clone());
        self.properties.insert(
            name_str,
            PrimitiveSchemaDefinition::Boolean(f(BooleanSchema::new())),
        );
        self
    }

    // ===========================================================================
    // CONVENIENCE METHODS - Simple common cases
    // ===========================================================================

    /// Add a required string property
    pub fn required_string(self, name: impl Into<String>) -> Self {
        self.required_property(name, PrimitiveSchemaDefinition::String(StringSchema::new()))
    }

    /// Add an optional string property
    pub fn optional_string(self, name: impl Into<String>) -> Self {
        self.property(name, PrimitiveSchemaDefinition::String(StringSchema::new()))
    }

    /// Add a required email property
    pub fn required_email(self, name: impl Into<String>) -> Self {
        self.required_property(
            name,
            PrimitiveSchemaDefinition::String(StringSchema::email()),
        )
    }

    /// Add an optional email property
    pub fn optional_email(self, name: impl Into<String>) -> Self {
        self.property(
            name,
            PrimitiveSchemaDefinition::String(StringSchema::email()),
        )
    }

    /// Add a required string property with custom builder
    pub fn required_string_with(
        self,
        name: impl Into<String>,
        f: impl FnOnce(StringSchema) -> StringSchema,
    ) -> Self {
        self.required_property(
            name,
            PrimitiveSchemaDefinition::String(f(StringSchema::new())),
        )
    }

    /// Add an optional string property with custom builder
    pub fn optional_string_with(
        self,
        name: impl Into<String>,
        f: impl FnOnce(StringSchema) -> StringSchema,
    ) -> Self {
        self.property(
            name,
            PrimitiveSchemaDefinition::String(f(StringSchema::new())),
        )
    }

    // Convenience methods for numbers

    /// Add a required number property with range
    pub fn required_number(self, name: impl Into<String>, min: f64, max: f64) -> Self {
        self.required_property(
            name,
            PrimitiveSchemaDefinition::Number(NumberSchema::new().range(min, max)),
        )
    }

    /// Add an optional number property with range
    pub fn optional_number(self, name: impl Into<String>, min: f64, max: f64) -> Self {
        self.property(
            name,
            PrimitiveSchemaDefinition::Number(NumberSchema::new().range(min, max)),
        )
    }

    /// Add a required number property with custom builder
    pub fn required_number_with(
        self,
        name: impl Into<String>,
        f: impl FnOnce(NumberSchema) -> NumberSchema,
    ) -> Self {
        self.required_property(
            name,
            PrimitiveSchemaDefinition::Number(f(NumberSchema::new())),
        )
    }

    /// Add an optional number property with custom builder
    pub fn optional_number_with(
        self,
        name: impl Into<String>,
        f: impl FnOnce(NumberSchema) -> NumberSchema,
    ) -> Self {
        self.property(
            name,
            PrimitiveSchemaDefinition::Number(f(NumberSchema::new())),
        )
    }

    // Convenience methods for integers

    /// Add a required integer property with range
    pub fn required_integer(self, name: impl Into<String>, min: i64, max: i64) -> Self {
        self.required_property(
            name,
            PrimitiveSchemaDefinition::Integer(IntegerSchema::new().range(min, max)),
        )
    }

    /// Add an optional integer property with range
    pub fn optional_integer(self, name: impl Into<String>, min: i64, max: i64) -> Self {
        self.property(
            name,
            PrimitiveSchemaDefinition::Integer(IntegerSchema::new().range(min, max)),
        )
    }

    /// Add a required integer property with custom builder
    pub fn required_integer_with(
        self,
        name: impl Into<String>,
        f: impl FnOnce(IntegerSchema) -> IntegerSchema,
    ) -> Self {
        self.required_property(
            name,
            PrimitiveSchemaDefinition::Integer(f(IntegerSchema::new())),
        )
    }

    /// Add an optional integer property with custom builder
    pub fn optional_integer_with(
        self,
        name: impl Into<String>,
        f: impl FnOnce(IntegerSchema) -> IntegerSchema,
    ) -> Self {
        self.property(
            name,
            PrimitiveSchemaDefinition::Integer(f(IntegerSchema::new())),
        )
    }

    // Convenience methods for booleans

    /// Add a required boolean property
    pub fn required_bool(self, name: impl Into<String>) -> Self {
        self.required_property(
            name,
            PrimitiveSchemaDefinition::Boolean(BooleanSchema::new()),
        )
    }

    /// Add an optional boolean property with default value
    pub fn optional_bool(self, name: impl Into<String>, default: bool) -> Self {
        self.property(
            name,
            PrimitiveSchemaDefinition::Boolean(BooleanSchema::new().with_default(default)),
        )
    }

    /// Add a required boolean property with custom builder
    pub fn required_bool_with(
        self,
        name: impl Into<String>,
        f: impl FnOnce(BooleanSchema) -> BooleanSchema,
    ) -> Self {
        self.required_property(
            name,
            PrimitiveSchemaDefinition::Boolean(f(BooleanSchema::new())),
        )
    }

    /// Add an optional boolean property with custom builder
    pub fn optional_bool_with(
        self,
        name: impl Into<String>,
        f: impl FnOnce(BooleanSchema) -> BooleanSchema,
    ) -> Self {
        self.property(
            name,
            PrimitiveSchemaDefinition::Boolean(f(BooleanSchema::new())),
        )
    }

    // Enum convenience methods

    /// Add a required enum property using EnumSchema
    pub fn required_enum_schema(self, name: impl Into<String>, enum_schema: EnumSchema) -> Self {
        self.required_property(name, PrimitiveSchemaDefinition::Enum(enum_schema))
    }

    /// Add an optional enum property using EnumSchema
    pub fn optional_enum_schema(self, name: impl Into<String>, enum_schema: EnumSchema) -> Self {
        self.property(name, PrimitiveSchemaDefinition::Enum(enum_schema))
    }

    /// Add a required enum property using values. Creates an untitled single-select enum.
    #[deprecated(
        since = "0.13.0",
        note = "Use ElicitationSchemaBuilder::required_enum_schema with EnumSchema::builder instead"
    )]
    pub fn required_enum(self, name: impl Into<String>, values: Vec<String>) -> Self {
        self.required_property(
            name,
            PrimitiveSchemaDefinition::Enum(EnumSchema::Legacy(LegacyEnumSchema {
                type_: StringTypeConst,
                title: None,
                description: None,
                enum_: values,
                enum_names: None,
                default: None,
            })),
        )
    }

    /// Add an optional enum property using values. Creates an untitled single-select enum.
    #[deprecated(
        since = "0.13.0",
        note = "Use ElicitationSchemaBuilder::optional_enum_schema with EnumSchema::builder instead"
    )]
    pub fn optional_enum(self, name: impl Into<String>, values: Vec<String>) -> Self {
        self.property(
            name,
            PrimitiveSchemaDefinition::Enum(EnumSchema::Legacy(LegacyEnumSchema {
                type_: StringTypeConst,
                title: None,
                description: None,
                enum_: values,
                enum_names: None,
                default: None,
            })),
        )
    }

    /// Mark an existing property as required
    pub fn mark_required(mut self, name: impl Into<String>) -> Self {
        self.required.push(name.into());
        self
    }

    /// Set the schema title
    pub fn title(mut self, title: impl Into<Cow<'static, str>>) -> Self {
        self.title = Some(title.into());
        self
    }

    /// Set the schema description
    pub fn description(mut self, description: impl Into<Cow<'static, str>>) -> Self {
        self.description = Some(description.into());
        self
    }

    /// Build the elicitation schema with validation
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - Required fields reference non-existent properties
    /// - No properties are defined (empty schema)
    pub fn build(self) -> Result<ElicitationSchema, &'static str> {
        // Validate that all required fields exist in properties
        if !self.required.is_empty() {
            for field_name in &self.required {
                if !self.properties.contains_key(field_name) {
                    return Err("Required field does not exist in properties");
                }
            }
        }

        Ok(ElicitationSchema {
            type_: ObjectTypeConst,
            title: self.title,
            properties: self.properties,
            required: if self.required.is_empty() {
                None
            } else {
                Some(self.required)
            },
            description: self.description,
        })
    }

    /// Build the elicitation schema without validation (panics on invalid schema)
    ///
    /// # Panics
    ///
    /// Panics if required fields reference non-existent properties
    pub fn build_unchecked(self) -> ElicitationSchema {
        self.build().expect("Invalid elicitation schema")
    }
}

#[cfg(test)]
mod tests {
    use anyhow::anyhow;
    use rstest::rstest;
    use serde_json::json;

    use super::*;

    fn string_schema_json() -> serde_json::Value {
        serde_json::to_value(StringSchema::email().description("Email address")).unwrap()
    }

    fn number_schema_json() -> serde_json::Value {
        serde_json::to_value(
            NumberSchema::new()
                .range(0.0, 100.0)
                .description("Percentage"),
        )
        .unwrap()
    }

    fn integer_schema_json() -> serde_json::Value {
        serde_json::to_value(IntegerSchema::new().range(0, 150)).unwrap()
    }

    fn boolean_schema_json() -> serde_json::Value {
        serde_json::to_value(BooleanSchema::new().with_default(true)).unwrap()
    }

    #[rstest]
    #[case::string_schema(
        string_schema_json,
        json!({
            "type": "string",
            "format": "email",
            "description": "Email address",
        })
    )]
    #[case::number_schema(
        number_schema_json,
        json!({
            "type": "number",
            "description": "Percentage",
            "minimum": 0.0,
            "maximum": 100.0,
        })
    )]
    #[case::integer_schema(
        integer_schema_json,
        json!({
            "type": "integer",
            "minimum": 0,
            "maximum": 150,
        })
    )]
    #[case::boolean_schema(
        boolean_schema_json,
        json!({
            "type": "boolean",
            "default": true,
        })
    )]
    fn primitive_schema_serializes_to_expected_json(
        #[case] schema_json: fn() -> serde_json::Value,
        #[case] expected: serde_json::Value,
    ) {
        assert_eq!(schema_json(), expected);
    }

    #[test]
    fn test_enum_schema_untitled_single_select_serialization() {
        let schema = EnumSchema::builder(vec!["US".to_string(), "UK".to_string()])
            .description("Country code")
            .build();
        let json = serde_json::to_value(&schema).unwrap();

        assert_eq!(json["type"], "string");
        assert_eq!(json["enum"], json!(["US", "UK"]));
        assert_eq!(json["description"], "Country code");
    }

    #[test]
    fn test_enum_schema_untitled_multi_select_serialization() -> anyhow::Result<()> {
        let schema = EnumSchema::builder(vec!["US".to_string(), "UK".to_string()])
            .multiselect()
            .min_items(1u64)
            .map_err(|e| anyhow!("{e}"))?
            .max_items(4u64)
            .map_err(|e| anyhow!("{e}"))?
            .description("Country code")
            .build();
        let json = serde_json::to_value(&schema)?;

        assert_eq!(json["type"], "array");
        assert_eq!(json["minItems"], 1u64);
        assert_eq!(json["maxItems"], 4u64);
        assert_eq!(json["items"], json!({"type":"string", "enum":["US", "UK"]}));
        assert_eq!(json["description"], "Country code");
        Ok(())
    }

    #[test]
    fn test_enum_schema_titled_single_select_serialization() -> anyhow::Result<()> {
        let schema = EnumSchema::builder(vec!["US".to_string(), "UK".to_string()])
            .enum_titles(vec![
                "United States".to_string(),
                "United Kingdom".to_string(),
            ])
            .map_err(|e| anyhow!("{e}"))?
            .description("Country code")
            .build();
        let json = serde_json::to_value(&schema)?;

        assert_eq!(json["type"], "string");
        assert_eq!(
            json["oneOf"],
            json!([
                {"const": "US", "title":"United States"},
                {"const": "UK", "title":"United Kingdom"}
            ])
        );
        assert_eq!(json["description"], "Country code");
        Ok(())
    }

    #[test]
    fn test_enum_schema_legacy_serialization() -> anyhow::Result<()> {
        let schema = EnumSchema::Legacy(LegacyEnumSchema {
            type_: StringTypeConst,
            title: Some("Legacy Enum".into()),
            description: Some("A legacy enum schema".into()),
            enum_: vec!["A".to_string(), "B".to_string()],
            enum_names: Some(vec!["Option A".to_string(), "Option B".to_string()]),
            default: None,
        });
        let json = serde_json::to_value(&schema)?;

        assert_eq!(json["type"], "string");
        assert_eq!(json["title"], "Legacy Enum");
        assert_eq!(json["description"], "A legacy enum schema");
        assert_eq!(json["enum"], json!(["A", "B"]));
        assert_eq!(json["enumNames"], json!(["Option A", "Option B"]));
        Ok(())
    }

    #[test]
    fn test_legacy_enum_schema_roundtrip_preserves_enum_names() -> anyhow::Result<()> {
        // Regression test for: legacy enum payload with `enumNames` was silently
        // deserialized as `UntitledSingleSelectEnumSchema` (which has no `enumNames`
        // field), causing the array to be dropped on re-serialization.
        let input = serde_json::json!({
            "type": "object",
            "properties": {
                "choice": {
                    "type": "string",
                    "enum": ["opt1", "opt2", "opt3"],
                    "enumNames": ["Option One", "Option Two", "Option Three"]
                }
            }
        });
        let schema: ElicitationSchema = serde_json::from_value(input.clone())?;
        let output = serde_json::to_value(&schema)?;
        assert_eq!(
            output["properties"]["choice"]["enumNames"],
            serde_json::json!(["Option One", "Option Two", "Option Three"]),
        );
        Ok(())
    }

    #[test]
    fn test_legacy_enum_schema_no_enum_names_omits_field() -> anyhow::Result<()> {
        // `LegacyEnumSchema` with `enum_names: None` must not serialize `"enumNames": null`.
        let schema = EnumSchema::Legacy(LegacyEnumSchema {
            type_: StringTypeConst,
            title: None,
            description: None,
            enum_: vec!["a".to_string(), "b".to_string()],
            enum_names: None,
            default: None,
        });
        let json = serde_json::to_value(&schema)?;
        assert!(!json.as_object().unwrap().contains_key("enumNames"));
        Ok(())
    }

    #[test]
    fn test_enum_schema_titled_multi_select_serialization() -> anyhow::Result<()> {
        let schema = EnumSchema::builder(vec!["US".to_string(), "UK".to_string()])
            .enum_titles(vec![
                "United States".to_string(),
                "United Kingdom".to_string(),
            ])
            .map_err(|e| anyhow!("{e}"))?
            .multiselect()
            .min_items(1u64)
            .map_err(|e| anyhow!("{e}"))?
            .max_items(4u64)
            .map_err(|e| anyhow!("{e}"))?
            .description("Country code")
            .build();
        let json = serde_json::to_value(&schema)?;

        assert_eq!(json["type"], "array");
        assert_eq!(json["minItems"], 1u64);
        assert_eq!(json["maxItems"], 4u64);
        assert_eq!(
            json["items"],
            json!({"anyOf":[
                {"const":"US","title":"United States"},
                {"const":"UK","title":"United Kingdom"}
            ]})
        );
        assert_eq!(json["description"], "Country code");
        Ok(())
    }

    #[test]
    fn test_enum_schema_single_select_with_default() -> anyhow::Result<()> {
        let schema = EnumSchema::builder(vec![
            "red".to_string(),
            "green".to_string(),
            "blue".to_string(),
        ])
        .with_default("green")
        .map_err(|e| anyhow!("{e}"))?
        .description("Favorite color")
        .build();

        let json = serde_json::to_value(&schema)?;

        assert_eq!(json["type"], "string");
        assert_eq!(json["enum"], json!(["red", "green", "blue"]));
        assert_eq!(json["default"], "green");
        assert_eq!(json["description"], "Favorite color");
        Ok(())
    }

    #[test]
    fn test_enum_schema_multi_select_with_default() -> anyhow::Result<()> {
        let schema = EnumSchema::builder(vec![
            "red".to_string(),
            "green".to_string(),
            "blue".to_string(),
        ])
        .multiselect()
        .with_default(vec!["red".to_string(), "blue".to_string()])
        .map_err(|e| anyhow!("{e}"))?
        .min_items(1)
        .map_err(|e| anyhow!("{e}"))?
        .max_items(3)
        .map_err(|e| anyhow!("{e}"))?
        .build();

        let json = serde_json::to_value(&schema)?;

        assert_eq!(json["type"], "array");
        assert_eq!(json["items"]["enum"], json!(["red", "green", "blue"]));
        assert_eq!(json["default"], json!(["red", "blue"]));
        assert_eq!(json["minItems"], 1);
        assert_eq!(json["maxItems"], 3);
        Ok(())
    }

    #[test]
    fn test_enum_schema_transition_clears_defaults() -> anyhow::Result<()> {
        // Start with single-select with default
        let builder = EnumSchema::builder(vec!["A".to_string(), "B".to_string()])
            .with_default("A")
            .map_err(|e| anyhow!("{e}"))?;

        // Transition to multi-select should clear the default
        let schema = builder.multiselect().build();
        let json = serde_json::to_value(&schema)?;

        assert_eq!(json["type"], "array");
        assert!(json["default"].is_null());
        Ok(())
    }

    #[test]
    fn test_enum_schema_multi_to_single_transition() -> anyhow::Result<()> {
        // Start with multi-select with defaults
        let builder = EnumSchema::builder(vec!["A".to_string(), "B".to_string(), "C".to_string()])
            .multiselect()
            .with_default(vec!["A".to_string(), "B".to_string()])
            .map_err(|e| anyhow!("{e}"))?
            .min_items(1)
            .map_err(|e| anyhow!("{e}"))?;

        // Transition back to single-select should clear defaults and min/max items
        let schema = builder.single_select().build();
        let json = serde_json::to_value(&schema)?;

        assert_eq!(json["type"], "string");
        assert!(json["default"].is_null());
        assert!(json["minItems"].is_null());
        assert!(json["maxItems"].is_null());
        Ok(())
    }

    #[test]
    fn test_enum_schema_invalid_single_default() {
        let result = EnumSchema::builder(vec!["A".to_string(), "B".to_string()]).with_default("C");

        assert!(result.is_err());
        assert_eq!(
            result.unwrap_err(),
            "Provided default value is not in enum values"
        );
    }

    #[test]
    fn test_enum_schema_invalid_multi_default() {
        let result = EnumSchema::builder(vec!["A".to_string(), "B".to_string()])
            .multiselect()
            .with_default(vec!["A".to_string(), "C".to_string()]);

        assert!(result.is_err());
        assert_eq!(
            result.unwrap_err(),
            "One of the provided default values is not in enum values"
        );
    }

    #[test]
    fn test_enum_schema_titled_with_default() -> anyhow::Result<()> {
        let schema = EnumSchema::builder(vec!["US".to_string(), "UK".to_string()])
            .enum_titles(vec![
                "United States".to_string(),
                "United Kingdom".to_string(),
            ])
            .map_err(|e| anyhow!("{e}"))?
            .with_default("UK")
            .map_err(|e| anyhow!("{e}"))?
            .build();

        let json = serde_json::to_value(&schema)?;

        assert_eq!(json["type"], "string");
        assert_eq!(json["default"], "UK");
        assert_eq!(
            json["oneOf"],
            json!([
                {"const": "US", "title": "United States"},
                {"const": "UK", "title": "United Kingdom"}
            ])
        );
        Ok(())
    }

    #[test]
    fn test_enum_schema_untitled_after_titled() -> anyhow::Result<()> {
        let schema = EnumSchema::builder(vec!["A".to_string(), "B".to_string()])
            .enum_titles(vec!["Option A".to_string(), "Option B".to_string()])
            .map_err(|e| anyhow!("{e}"))?
            .untitled()
            .build();

        let json = serde_json::to_value(&schema)?;

        assert_eq!(json["type"], "string");
        assert_eq!(json["enum"], json!(["A", "B"]));
        assert!(json["oneOf"].is_null());
        Ok(())
    }

    #[test]
    fn test_primitive_schema_enum_deserialization() {
        // Test that enum schemas deserialize as Enum variant, not String
        let json = json!({
            "type": "string",
            "enum": ["a", "b"]
        });
        let schema: PrimitiveSchemaDefinition = serde_json::from_value(json).unwrap();
        assert!(matches!(schema, PrimitiveSchemaDefinition::Enum(_)));
        // Test that string schemas deserialize as String variant
        let json = json!({
            "type": "string"
        });
        let schema: PrimitiveSchemaDefinition = serde_json::from_value(json).unwrap();
        assert!(matches!(schema, PrimitiveSchemaDefinition::String(_)));
    }

    #[test]
    fn test_elicitation_schema_builder_simple() {
        let schema = ElicitationSchema::builder()
            .required_email("email")
            .optional_bool("newsletter", false)
            .build()
            .unwrap();

        assert_eq!(schema.properties.len(), 2);
        assert!(schema.properties.contains_key("email"));
        assert!(schema.properties.contains_key("newsletter"));
        assert_eq!(schema.required, Some(vec!["email".to_string()]));
    }

    #[test]
    fn test_elicitation_schema_builder_complex() {
        let enum_schema =
            EnumSchema::builder(vec!["US".to_string(), "UK".to_string(), "CA".to_string()]).build();
        let schema = ElicitationSchema::builder()
            .required_string_with("name", |s| s.length(1, 100))
            .required_integer("age", 0, 150)
            .optional_bool("newsletter", false)
            .required_enum_schema("country", enum_schema)
            .description("User registration")
            .build()
            .unwrap();

        assert_eq!(schema.properties.len(), 4);
        assert_eq!(
            schema.required,
            Some(vec![
                "name".to_string(),
                "age".to_string(),
                "country".to_string()
            ])
        );
        assert_eq!(schema.description.as_deref(), Some("User registration"));
    }

    #[test]
    fn test_elicitation_schema_serialization() {
        let schema = ElicitationSchema::builder()
            .required_string_with("name", |s| s.length(1, 100))
            .build()
            .unwrap();

        let json = serde_json::to_value(&schema).unwrap();

        assert_eq!(json["type"], "object");
        assert!(json["properties"]["name"].is_object());
        assert_eq!(json["required"], json!(["name"]));
    }

    #[test]
    #[should_panic(expected = "minimum must be <= maximum")]
    fn test_integer_range_validation() {
        IntegerSchema::new().range(10, 5); // Should panic
    }

    #[test]
    #[should_panic(expected = "min_length must be <= max_length")]
    fn test_string_length_validation() {
        StringSchema::new().length(10, 5); // Should panic
    }

    #[test]
    fn test_integer_range_validation_with_result() {
        let result = IntegerSchema::new().with_range(10, 5);
        assert!(result.is_err());
        assert_eq!(result.unwrap_err(), "minimum must be <= maximum");
    }

    #[cfg(feature = "schemars")]
    mod schemars_tests {
        use anyhow::Result;
        use schemars::JsonSchema;
        use serde::{Deserialize, Serialize};
        use serde_json::json;

        use crate::model::ElicitationSchema;

        #[derive(Debug, Serialize, Deserialize, JsonSchema, Default)]
        #[schemars(inline)]
        #[schemars(extend("type" = "string"))]
        enum TitledEnum {
            #[schemars(title = "Title for the first value")]
            #[default]
            FirstValue,
            #[schemars(title = "Title for the second value")]
            SecondValue,
        }

        #[derive(Debug, Serialize, Deserialize, JsonSchema)]
        #[schemars(inline)]
        enum UntitledEnum {
            First,
            Second,
            Third,
        }

        fn default_untitled_multi_select() -> Vec<UntitledEnum> {
            vec![UntitledEnum::Second, UntitledEnum::Third]
        }

        #[derive(Debug, Serialize, Deserialize, JsonSchema)]
        #[schemars(description = "User information")]
        struct UserInfo {
            #[schemars(description = "User's name")]
            pub name: String,
            pub single_select_untitled: UntitledEnum,
            #[schemars(
                title = "Single Select Titled",
                description = "Description for single select enum",
                default
            )]
            pub single_select_titled: TitledEnum,
            #[serde(default = "default_untitled_multi_select")]
            pub multi_select_untitled: Vec<UntitledEnum>,
            #[schemars(
                title = "Multi Select Titled",
                description = "Multi Select Description"
            )]
            pub multi_select_titled: Vec<TitledEnum>,
        }

        #[test]
        fn test_schema_inference_for_enum_fields() -> Result<()> {
            let schema = ElicitationSchema::from_type::<UserInfo>()?;

            let json = serde_json::to_value(&schema)?;
            assert_eq!(json["type"], "object");
            assert_eq!(json["description"], "User information");
            assert_eq!(
                json["required"],
                json!(["name", "single_select_untitled", "multi_select_titled"])
            );
            let properties = match json.get("properties") {
                Some(serde_json::Value::Object(map)) => map,
                _ => panic!("Schema does not have 'properties' field or it is not object type"),
            };

            assert_eq!(properties.len(), 5);
            assert_eq!(
                properties["name"],
                json!({"description":"User's name", "type":"string"})
            );

            assert_eq!(
                properties["single_select_untitled"],
                serde_json::json!({
                    "type":"string",
                    "enum":["First", "Second", "Third"]
                })
            );

            assert_eq!(
                properties["single_select_titled"],
                json!({
                    "type":"string",
                    "title":"Single Select Titled",
                    "description":"Description for single select enum",
                    "oneOf":[
                        {"const":"FirstValue", "title":"Title for the first value"},
                        {"const":"SecondValue", "title":"Title for the second value"}
                    ],
                    "default":"FirstValue"
                })
            );
            assert_eq!(
                properties["multi_select_untitled"],
                serde_json::json!({
                    "type":"array",
                    "items" : {
                        "type":"string",
                        "enum":["First", "Second", "Third"]
                    },
                    "default":["Second", "Third"]
                })
            );
            assert_eq!(
                properties["multi_select_titled"],
                serde_json::json!({
                    "type":"array",
                    "title":"Multi Select Titled",
                    "description":"Multi Select Description",
                    "items":{
                        "anyOf":[
                            {"const":"FirstValue", "title":"Title for the first value"},
                            {"const":"SecondValue", "title":"Title for the second value"}
                        ]
                    }
                })
            );
            Ok(())
        }
    }
}
