//! Type-safe schema definitions for MCP elicitation requests.
//!
//! Copied from rust-sdk and adapted for mcphost-rs architecture.
//! 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,ignore
//! use mcp_host::protocol::elicitation::*;
//!
//! let schema = ElicitationSchema::builder()
//!     .required_email("email")
//!     .required_integer("age", 0, 150)
//!     .optional_bool("newsletter", false)
//!     .build();
//! ```

use serde::{Deserialize, Serialize};
use std::{borrow::Cow, collections::BTreeMap};

// Re-export macro from parent if needed
use crate::protocol::version::const_string;

// =============================================================================
// 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.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum PrimitiveSchema {
    /// 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),
}

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

/// String format types allowed by the MCP specification.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
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.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StringSchema {
    #[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(skip_serializing_if = "Option::is_none")]
    pub min_length: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_length: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub format: Option<StringFormat>,
}

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

impl StringSchema {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn email() -> Self {
        Self {
            format: Some(StringFormat::Email),
            ..Default::default()
        }
    }

    pub fn uri() -> Self {
        Self {
            format: Some(StringFormat::Uri),
            ..Default::default()
        }
    }

    pub fn date() -> Self {
        Self {
            format: Some(StringFormat::Date),
            ..Default::default()
        }
    }

    pub fn date_time() -> Self {
        Self {
            format: Some(StringFormat::DateTime),
            ..Default::default()
        }
    }

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

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

    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)
    }

    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
    }

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

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

    pub fn format(mut self, format: StringFormat) -> Self {
        self.format = Some(format);
        self
    }
}

// NOTE: The rest of the schema types (Number, Integer, Boolean, Enum, ElicitationSchema)
// are too long to include in a single response. This is part 1 of the schema file.
// The implementation continues with NumberSchema, IntegerSchema, BooleanSchema, EnumSchema,
// and ElicitationSchema with their builders following the same pattern from rust-sdk.

// For brevity in this initial integration, I'll include stubs that you can expand:

/// Schema definition for number properties (floating-point).
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct NumberSchema {
    #[serde(rename = "type")]
    pub type_: NumberTypeConst,
    #[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 minimum: Option<f64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub maximum: Option<f64>,
}

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

impl NumberSchema {
    pub fn new() -> Self {
        Self::default()
    }

    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)
    }

    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
    }

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

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

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

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

/// Schema definition for integer properties.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct IntegerSchema {
    #[serde(rename = "type")]
    pub type_: IntegerTypeConst,
    #[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 minimum: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub maximum: Option<i64>,
}

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

impl IntegerSchema {
    pub fn new() -> Self {
        Self::default()
    }

    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)
    }

    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
    }

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

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

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

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

/// Schema definition for boolean properties.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BooleanSchema {
    #[serde(rename = "type")]
    pub type_: BooleanTypeConst,
    #[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 default: Option<bool>,
}

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

impl BooleanSchema {
    pub fn new() -> Self {
        Self::default()
    }

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

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

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

// =============================================================================
// ENUM SCHEMA - Single and Multi-Select with Optional Titles
// =============================================================================

/// Option for titled enum variants (const value + display title)
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EnumOption {
    /// The actual enum value
    #[serde(rename = "const")]
    pub const_value: String,
    /// Display title for this option
    pub title: String,
}

impl EnumOption {
    pub fn new(value: impl Into<String>, title: impl Into<String>) -> Self {
        Self {
            const_value: value.into(),
            title: title.into(),
        }
    }
}

/// Items schema for untitled multi-select (array of strings with enum constraint)
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct UntitledEnumItems {
    #[serde(rename = "type")]
    pub type_: StringTypeConst,
    #[serde(rename = "enum")]
    pub enum_values: Vec<String>,
}

/// Items schema for titled multi-select (anyOf with const/title pairs)
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TitledEnumItems {
    pub any_of: Vec<EnumOption>,
}

// -----------------------------------------------------------------------------
// Single-Select Enum Schemas
// -----------------------------------------------------------------------------

/// Schema for single-selection enum without display titles
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UntitledSingleSelectEnumSchema {
    #[serde(rename = "type")]
    pub type_: StringTypeConst,
    #[serde(rename = "enum")]
    pub enum_values: Vec<String>,
    #[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 default: Option<String>,
}

impl UntitledSingleSelectEnumSchema {
    pub fn new(values: impl Into<Vec<String>>) -> Self {
        Self {
            type_: StringTypeConst,
            enum_values: values.into(),
            title: None,
            description: None,
            default: None,
        }
    }

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

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

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

/// Schema for single-selection enum with display titles for each option
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TitledSingleSelectEnumSchema {
    #[serde(rename = "type")]
    pub type_: StringTypeConst,
    pub one_of: Vec<EnumOption>,
    #[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 default: Option<String>,
}

impl TitledSingleSelectEnumSchema {
    pub fn new(options: impl Into<Vec<EnumOption>>) -> Self {
        Self {
            type_: StringTypeConst,
            one_of: options.into(),
            title: None,
            description: None,
            default: None,
        }
    }

    /// Create from tuples of (value, title)
    pub fn from_pairs<I, V, T>(pairs: I) -> Self
    where
        I: IntoIterator<Item = (V, T)>,
        V: Into<String>,
        T: Into<String>,
    {
        Self::new(
            pairs
                .into_iter()
                .map(|(v, t)| EnumOption::new(v, t))
                .collect::<Vec<_>>(),
        )
    }

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

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

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

// -----------------------------------------------------------------------------
// Multi-Select Enum Schemas
// -----------------------------------------------------------------------------

/// Schema for multi-selection enum without display titles
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UntitledMultiSelectEnumSchema {
    #[serde(rename = "type")]
    pub type_: ArrayTypeConst,
    pub items: UntitledEnumItems,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub min_items: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_items: Option<u32>,
    #[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 default: Option<Vec<String>>,
}

impl UntitledMultiSelectEnumSchema {
    pub fn new(values: impl Into<Vec<String>>) -> Self {
        Self {
            type_: ArrayTypeConst,
            items: UntitledEnumItems {
                type_: StringTypeConst,
                enum_values: values.into(),
            },
            min_items: None,
            max_items: None,
            title: None,
            description: None,
            default: None,
        }
    }

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

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

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

    pub fn min_items(mut self, min: u32) -> Self {
        self.min_items = Some(min);
        self
    }

    pub fn max_items(mut self, max: u32) -> Self {
        self.max_items = Some(max);
        self
    }

    pub fn items_range(mut self, min: u32, max: u32) -> Self {
        self.min_items = Some(min);
        self.max_items = Some(max);
        self
    }
}

/// Schema for multi-selection enum with display titles for each option
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TitledMultiSelectEnumSchema {
    #[serde(rename = "type")]
    pub type_: ArrayTypeConst,
    pub items: TitledEnumItems,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub min_items: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub max_items: Option<u32>,
    #[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 default: Option<Vec<String>>,
}

impl TitledMultiSelectEnumSchema {
    pub fn new(options: impl Into<Vec<EnumOption>>) -> Self {
        Self {
            type_: ArrayTypeConst,
            items: TitledEnumItems {
                any_of: options.into(),
            },
            min_items: None,
            max_items: None,
            title: None,
            description: None,
            default: None,
        }
    }

    /// Create from tuples of (value, title)
    pub fn from_pairs<I, V, T>(pairs: I) -> Self
    where
        I: IntoIterator<Item = (V, T)>,
        V: Into<String>,
        T: Into<String>,
    {
        Self::new(
            pairs
                .into_iter()
                .map(|(v, t)| EnumOption::new(v, t))
                .collect::<Vec<_>>(),
        )
    }

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

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

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

    pub fn min_items(mut self, min: u32) -> Self {
        self.min_items = Some(min);
        self
    }

    pub fn max_items(mut self, max: u32) -> Self {
        self.max_items = Some(max);
        self
    }

    pub fn items_range(mut self, min: u32, max: u32) -> Self {
        self.min_items = Some(min);
        self.max_items = Some(max);
        self
    }
}

// -----------------------------------------------------------------------------
// Legacy Enum Schema (deprecated, for backwards compatibility)
// -----------------------------------------------------------------------------

/// Legacy enum schema with parallel enum/enumNames arrays
///
/// Deprecated: Use `TitledSingleSelectEnumSchema` instead.
/// This is kept for backwards compatibility with older implementations.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LegacyTitledEnumSchema {
    #[serde(rename = "type")]
    pub type_: StringTypeConst,
    #[serde(rename = "enum")]
    pub enum_values: Vec<String>,
    /// Display names for enum values (parallel array, same length as enum_values)
    pub enum_names: Vec<String>,
    #[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 default: Option<String>,
}

impl LegacyTitledEnumSchema {
    pub fn new(values: Vec<String>, names: Vec<String>) -> Self {
        debug_assert_eq!(
            values.len(),
            names.len(),
            "enum values and names must have same length"
        );
        Self {
            type_: StringTypeConst,
            enum_values: values,
            enum_names: names,
            title: None,
            description: None,
            default: None,
        }
    }

    /// Create from tuples of (value, name)
    pub fn from_pairs<I, V, N>(pairs: I) -> Self
    where
        I: IntoIterator<Item = (V, N)>,
        V: Into<String>,
        N: Into<String>,
    {
        let (values, names): (Vec<_>, Vec<_>) =
            pairs.into_iter().map(|(v, n)| (v.into(), n.into())).unzip();
        Self::new(values, names)
    }

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

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

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

// -----------------------------------------------------------------------------
// EnumSchema Union
// -----------------------------------------------------------------------------

/// Union of all enum schema variants for elicitation
///
/// Deserialization order matters for untagged enums:
/// 1. Titled variants (have oneOf/anyOf) must come before untitled (have plain enum)
/// 2. Multi-select (type: "array") must be checked appropriately
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum EnumSchema {
    /// Single-select with titled options (uses oneOf)
    TitledSingleSelect(TitledSingleSelectEnumSchema),
    /// Multi-select with titled options (uses items.anyOf)
    TitledMultiSelect(TitledMultiSelectEnumSchema),
    /// Multi-select without titles (type: "array")
    UntitledMultiSelect(UntitledMultiSelectEnumSchema),
    /// Single-select without titles (simple enum array)
    UntitledSingleSelect(UntitledSingleSelectEnumSchema),
    /// Legacy format with parallel enumNames array (deprecated)
    Legacy(LegacyTitledEnumSchema),
}

impl EnumSchema {
    /// Create a simple single-select enum from string values
    pub fn single_select<I, S>(values: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        Self::UntitledSingleSelect(UntitledSingleSelectEnumSchema::new(
            values.into_iter().map(Into::into).collect::<Vec<_>>(),
        ))
    }

    /// Create a single-select enum with display titles
    pub fn single_select_titled<I, V, T>(pairs: I) -> Self
    where
        I: IntoIterator<Item = (V, T)>,
        V: Into<String>,
        T: Into<String>,
    {
        Self::TitledSingleSelect(TitledSingleSelectEnumSchema::from_pairs(pairs))
    }

    /// Create a simple multi-select enum from string values
    pub fn multi_select<I, S>(values: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        Self::UntitledMultiSelect(UntitledMultiSelectEnumSchema::new(
            values.into_iter().map(Into::into).collect::<Vec<_>>(),
        ))
    }

    /// Create a multi-select enum with display titles
    pub fn multi_select_titled<I, V, T>(pairs: I) -> Self
    where
        I: IntoIterator<Item = (V, T)>,
        V: Into<String>,
        T: Into<String>,
    {
        Self::TitledMultiSelect(TitledMultiSelectEnumSchema::from_pairs(pairs))
    }

    /// Add title to the enum schema
    pub fn title(self, title: impl Into<Cow<'static, str>>) -> Self {
        let title = title.into();
        match self {
            Self::TitledSingleSelect(s) => Self::TitledSingleSelect(s.title(title)),
            Self::TitledMultiSelect(s) => Self::TitledMultiSelect(s.title(title)),
            Self::UntitledMultiSelect(s) => Self::UntitledMultiSelect(s.title(title)),
            Self::UntitledSingleSelect(s) => Self::UntitledSingleSelect(s.title(title)),
            Self::Legacy(s) => Self::Legacy(s.title(title)),
        }
    }

    /// Add description to the enum schema
    pub fn description(self, description: impl Into<Cow<'static, str>>) -> Self {
        let description = description.into();
        match self {
            Self::TitledSingleSelect(s) => Self::TitledSingleSelect(s.description(description)),
            Self::TitledMultiSelect(s) => Self::TitledMultiSelect(s.description(description)),
            Self::UntitledMultiSelect(s) => Self::UntitledMultiSelect(s.description(description)),
            Self::UntitledSingleSelect(s) => Self::UntitledSingleSelect(s.description(description)),
            Self::Legacy(s) => Self::Legacy(s.description(description)),
        }
    }
}

/// Type-safe elicitation schema for requesting structured user input.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ElicitationSchema {
    #[serde(rename = "type")]
    pub type_: ObjectTypeConst,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub title: Option<Cow<'static, str>>,
    pub properties: BTreeMap<String, PrimitiveSchema>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub required: Option<Vec<String>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<Cow<'static, str>>,
}

impl ElicitationSchema {
    pub fn new(properties: BTreeMap<String, PrimitiveSchema>) -> Self {
        Self {
            type_: ObjectTypeConst,
            title: None,
            properties,
            required: None,
            description: None,
        }
    }

    pub fn builder() -> ElicitationSchemaBuilder {
        ElicitationSchemaBuilder::new()
    }
}

/// Fluent builder for constructing elicitation schemas.
#[derive(Debug, Default)]
pub struct ElicitationSchemaBuilder {
    pub properties: BTreeMap<String, PrimitiveSchema>,
    pub required: Vec<String>,
    pub title: Option<Cow<'static, str>>,
    pub description: Option<Cow<'static, str>>,
}

impl ElicitationSchemaBuilder {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn property(mut self, name: impl Into<String>, schema: PrimitiveSchema) -> Self {
        self.properties.insert(name.into(), schema);
        self
    }

    pub fn required_property(mut self, name: impl Into<String>, schema: PrimitiveSchema) -> Self {
        let name_str = name.into();
        self.required.push(name_str.clone());
        self.properties.insert(name_str, schema);
        self
    }

    pub fn required_email(self, name: impl Into<String>) -> Self {
        self.required_property(name, PrimitiveSchema::String(StringSchema::email()))
    }

    pub fn optional_bool(self, name: impl Into<String>, default: bool) -> Self {
        self.property(
            name,
            PrimitiveSchema::Boolean(BooleanSchema::new().with_default(default)),
        )
    }

    pub fn required_integer(self, name: impl Into<String>, min: i64, max: i64) -> Self {
        self.required_property(
            name,
            PrimitiveSchema::Integer(IntegerSchema::new().range(min, max)),
        )
    }

    /// Add a required single-select enum property
    pub fn required_enum<I, S>(self, name: impl Into<String>, values: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.required_property(
            name,
            PrimitiveSchema::Enum(EnumSchema::single_select(values)),
        )
    }

    /// Add an optional single-select enum property with a default value
    pub fn optional_enum<I, S>(self, name: impl Into<String>, values: I, default: S) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        let values_vec: Vec<String> = values.into_iter().map(Into::into).collect();
        let schema = UntitledSingleSelectEnumSchema::new(values_vec).with_default(default);
        self.property(
            name,
            PrimitiveSchema::Enum(EnumSchema::UntitledSingleSelect(schema)),
        )
    }

    /// Add a required single-select enum with titled options
    pub fn required_enum_titled<I, V, T>(self, name: impl Into<String>, options: I) -> Self
    where
        I: IntoIterator<Item = (V, T)>,
        V: Into<String>,
        T: Into<String>,
    {
        self.required_property(
            name,
            PrimitiveSchema::Enum(EnumSchema::single_select_titled(options)),
        )
    }

    /// Add a required multi-select enum property
    pub fn required_multi_enum<I, S>(self, name: impl Into<String>, values: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.required_property(
            name,
            PrimitiveSchema::Enum(EnumSchema::multi_select(values)),
        )
    }

    /// Add an optional multi-select enum property
    pub fn optional_multi_enum<I, S>(self, name: impl Into<String>, values: I) -> Self
    where
        I: IntoIterator<Item = S>,
        S: Into<String>,
    {
        self.property(
            name,
            PrimitiveSchema::Enum(EnumSchema::multi_select(values)),
        )
    }

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

    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,
        })
    }

    pub fn build_unchecked(self) -> ElicitationSchema {
        self.build().expect("Invalid elicitation schema")
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use serde_json::json;

    #[test]
    fn test_untitled_single_select_serialization() {
        let schema = UntitledSingleSelectEnumSchema::new(vec![
            "red".to_string(),
            "green".to_string(),
            "blue".to_string(),
        ])
        .title("Color")
        .with_default("red");

        let json = serde_json::to_value(&schema).unwrap();
        assert_eq!(json["type"], "string");
        assert_eq!(json["enum"], json!(["red", "green", "blue"]));
        assert_eq!(json["title"], "Color");
        assert_eq!(json["default"], "red");
    }

    #[test]
    fn test_untitled_single_select_deserialization() {
        let json = json!({
            "type": "string",
            "enum": ["a", "b", "c"],
            "title": "Choice"
        });

        let schema: UntitledSingleSelectEnumSchema = serde_json::from_value(json).unwrap();
        assert_eq!(schema.enum_values, vec!["a", "b", "c"]);
        assert_eq!(schema.title.as_deref(), Some("Choice"));
    }

    #[test]
    fn test_titled_single_select_serialization() {
        let schema = TitledSingleSelectEnumSchema::from_pairs([
            ("#FF0000", "Red"),
            ("#00FF00", "Green"),
            ("#0000FF", "Blue"),
        ])
        .title("Color Selection");

        let json = serde_json::to_value(&schema).unwrap();
        assert_eq!(json["type"], "string");
        assert_eq!(json["oneOf"][0]["const"], "#FF0000");
        assert_eq!(json["oneOf"][0]["title"], "Red");
        assert_eq!(json["title"], "Color Selection");
    }

    #[test]
    fn test_titled_single_select_deserialization() {
        let json = json!({
            "type": "string",
            "oneOf": [
                { "const": "val1", "title": "Value 1" },
                { "const": "val2", "title": "Value 2" }
            ]
        });

        let schema: TitledSingleSelectEnumSchema = serde_json::from_value(json).unwrap();
        assert_eq!(schema.one_of.len(), 2);
        assert_eq!(schema.one_of[0].const_value, "val1");
        assert_eq!(schema.one_of[0].title, "Value 1");
    }

    #[test]
    fn test_untitled_multi_select_serialization() {
        let schema = UntitledMultiSelectEnumSchema::new(vec![
            "apple".to_string(),
            "banana".to_string(),
            "cherry".to_string(),
        ])
        .min_items(1)
        .max_items(2)
        .title("Fruits");

        let json = serde_json::to_value(&schema).unwrap();
        assert_eq!(json["type"], "array");
        assert_eq!(json["items"]["type"], "string");
        assert_eq!(json["items"]["enum"], json!(["apple", "banana", "cherry"]));
        assert_eq!(json["minItems"], 1);
        assert_eq!(json["maxItems"], 2);
    }

    #[test]
    fn test_titled_multi_select_serialization() {
        let schema =
            TitledMultiSelectEnumSchema::from_pairs([("opt1", "Option 1"), ("opt2", "Option 2")])
                .items_range(1, 2)
                .with_default(vec!["opt1".to_string()]);

        let json = serde_json::to_value(&schema).unwrap();
        assert_eq!(json["type"], "array");
        assert_eq!(json["items"]["anyOf"][0]["const"], "opt1");
        assert_eq!(json["items"]["anyOf"][0]["title"], "Option 1");
        assert_eq!(json["default"], json!(["opt1"]));
    }

    #[test]
    fn test_legacy_enum_serialization() {
        let schema = LegacyTitledEnumSchema::from_pairs([("v1", "Value 1"), ("v2", "Value 2")]);

        let json = serde_json::to_value(&schema).unwrap();
        assert_eq!(json["type"], "string");
        assert_eq!(json["enum"], json!(["v1", "v2"]));
        assert_eq!(json["enumNames"], json!(["Value 1", "Value 2"]));
    }

    #[test]
    fn test_enum_schema_factory_methods() {
        // Single select
        let single = EnumSchema::single_select(["a", "b", "c"]);
        assert!(matches!(single, EnumSchema::UntitledSingleSelect(_)));

        // Single select titled
        let titled = EnumSchema::single_select_titled([("val", "Title")]);
        assert!(matches!(titled, EnumSchema::TitledSingleSelect(_)));

        // Multi select
        let multi = EnumSchema::multi_select(["x", "y"]);
        assert!(matches!(multi, EnumSchema::UntitledMultiSelect(_)));

        // Multi select titled
        let multi_titled = EnumSchema::multi_select_titled([("a", "A"), ("b", "B")]);
        assert!(matches!(multi_titled, EnumSchema::TitledMultiSelect(_)));
    }

    #[test]
    fn test_enum_schema_deserialization_dispatch() {
        // Should deserialize as TitledSingleSelect (has oneOf)
        let json = json!({
            "type": "string",
            "oneOf": [{ "const": "x", "title": "X" }]
        });
        let schema: EnumSchema = serde_json::from_value(json).unwrap();
        assert!(matches!(schema, EnumSchema::TitledSingleSelect(_)));

        // Should deserialize as UntitledSingleSelect (has enum, no oneOf)
        let json = json!({
            "type": "string",
            "enum": ["a", "b"]
        });
        let schema: EnumSchema = serde_json::from_value(json).unwrap();
        assert!(matches!(schema, EnumSchema::UntitledSingleSelect(_)));

        // Should deserialize as TitledMultiSelect (type array, has anyOf)
        let json = json!({
            "type": "array",
            "items": {
                "anyOf": [{ "const": "x", "title": "X" }]
            }
        });
        let schema: EnumSchema = serde_json::from_value(json).unwrap();
        assert!(matches!(schema, EnumSchema::TitledMultiSelect(_)));

        // Should deserialize as UntitledMultiSelect (type array, items.enum)
        let json = json!({
            "type": "array",
            "items": {
                "type": "string",
                "enum": ["a", "b"]
            }
        });
        let schema: EnumSchema = serde_json::from_value(json).unwrap();
        assert!(matches!(schema, EnumSchema::UntitledMultiSelect(_)));
    }

    #[test]
    fn test_elicitation_builder_with_enum() {
        let schema = ElicitationSchema::builder()
            .required_enum("color", ["red", "green", "blue"])
            .required_enum_titled("size", [("s", "Small"), ("m", "Medium"), ("l", "Large")])
            .required_multi_enum("tags", ["tag1", "tag2", "tag3"])
            .build()
            .unwrap();

        assert_eq!(
            schema.required,
            Some(vec![
                "color".to_string(),
                "size".to_string(),
                "tags".to_string()
            ])
        );
        assert!(schema.properties.contains_key("color"));
        assert!(schema.properties.contains_key("size"));
        assert!(schema.properties.contains_key("tags"));
    }

    #[test]
    fn test_enum_schema_roundtrip() {
        // Test that serialization -> deserialization produces equivalent schema
        let original = EnumSchema::single_select_titled([("#FF0000", "Red"), ("#00FF00", "Green")])
            .title("Pick a color")
            .description("Choose your favorite");

        let json = serde_json::to_string(&original).unwrap();
        let parsed: EnumSchema = serde_json::from_str(&json).unwrap();

        // Re-serialize and compare
        let json2 = serde_json::to_string(&parsed).unwrap();
        assert_eq!(json, json2);
    }

    #[test]
    fn test_primitive_schema_with_enum() {
        let enum_schema = EnumSchema::single_select(["a", "b", "c"]);
        let primitive = PrimitiveSchema::Enum(enum_schema);

        let json = serde_json::to_value(&primitive).unwrap();
        assert_eq!(json["type"], "string");
        assert_eq!(json["enum"], json!(["a", "b", "c"]));
    }
}
