
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum PrimitiveType {
    Bool,
    I8,
    I16,
    I32,
    I64,
    I128,
    Isize,
    U8,
    U16,
    U32,
    U64,
    U128,
    Usize,
    F32,
    F64,
    Char,
    String,
    Bytes,
}

impl PrimitiveType {
    pub fn is_signed_integer(&self) -> bool {
        matches!(
            self,
            PrimitiveType::I8
                | PrimitiveType::I16
                | PrimitiveType::I32
                | PrimitiveType::I64
                | PrimitiveType::I128
                | PrimitiveType::Isize
        )
    }

    pub fn is_unsigned_integer(&self) -> bool {
        matches!(
            self,
            PrimitiveType::U8
                | PrimitiveType::U16
                | PrimitiveType::U32
                | PrimitiveType::U64
                | PrimitiveType::U128
                | PrimitiveType::Usize
        )
    }

    pub fn is_integer(&self) -> bool {
        self.is_signed_integer() || self.is_unsigned_integer()
    }

    pub fn is_float(&self) -> bool {
        matches!(self, PrimitiveType::F32 | PrimitiveType::F64)
    }

    pub fn is_numeric(&self) -> bool {
        self.is_integer() || self.is_float()
    }

    pub fn min_value(&self) -> Option<i128> {
        match self {
            PrimitiveType::I8 => Some(i8::MIN as i128),
            PrimitiveType::I16 => Some(i16::MIN as i128),
            PrimitiveType::I32 => Some(i32::MIN as i128),
            PrimitiveType::I64 => Some(i64::MIN as i128),
            PrimitiveType::I128 => Some(i128::MIN),
            PrimitiveType::Isize => Some(isize::MIN as i128),
            PrimitiveType::U8 | PrimitiveType::U16 | PrimitiveType::U32 | PrimitiveType::U64 => {
                Some(0)
            }
            PrimitiveType::U128 | PrimitiveType::Usize => Some(0),
            _ => None,
        }
    }

    pub fn max_value(&self) -> Option<u128> {
        match self {
            PrimitiveType::I8 => Some(i8::MAX as u128),
            PrimitiveType::I16 => Some(i16::MAX as u128),
            PrimitiveType::I32 => Some(i32::MAX as u128),
            PrimitiveType::I64 => Some(i64::MAX as u128),
            PrimitiveType::I128 => Some(i128::MAX as u128),
            PrimitiveType::Isize => Some(isize::MAX as u128),
            PrimitiveType::U8 => Some(u8::MAX as u128),
            PrimitiveType::U16 => Some(u16::MAX as u128),
            PrimitiveType::U32 => Some(u32::MAX as u128),
            PrimitiveType::U64 => Some(u64::MAX as u128),
            PrimitiveType::U128 => Some(u128::MAX),
            PrimitiveType::Usize => Some(usize::MAX as u128),
            _ => None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum TypeReference {
    Named(String),
    Option(Box<TypeReference>),
    Array(Box<TypeReference>),
    Set(Box<TypeReference>),
    Map(Box<TypeReference>),
    Tuple(Vec<TypeReference>),
    Primitive(PrimitiveType),
    Unit,
    Any,
}

impl TypeReference {
    pub fn named(name: impl Into<String>) -> Self {
        TypeReference::Named(name.into())
    }

    pub fn optional(self) -> Self {
        TypeReference::Option(Box::new(self))
    }

    pub fn array(self) -> Self {
        TypeReference::Array(Box::new(self))
    }

    pub fn is_optional(&self) -> bool {
        matches!(self, TypeReference::Option(_))
    }

    pub fn unwrap_option(&self) -> &TypeReference {
        match self {
            TypeReference::Option(inner) => inner,
            other => other,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum SchemaType {
    Primitive(PrimitiveType),
    Option(Box<SchemaType>),
    Array(Box<SchemaType>),
    Set(Box<SchemaType>),
    Map(Box<SchemaType>),
    Tuple(Vec<Box<SchemaType>>),
    Struct(StructDefinition),
    Enum(EnumDefinition),
    Newtype(NewtypeDefinition),
    Reference(String),
    Unit,
    Any,
}

impl SchemaType {
    pub fn is_nullable(&self) -> bool {
        matches!(self, SchemaType::Option(_))
    }

    pub fn is_primitive(&self) -> bool {
        matches!(self, SchemaType::Primitive(_))
    }

    pub fn is_container(&self) -> bool {
        matches!(
            self,
            SchemaType::Option(_)
                | SchemaType::Array(_)
                | SchemaType::Set(_)
                | SchemaType::Map(_)
        )
    }

    pub fn is_complex(&self) -> bool {
        matches!(self, SchemaType::Struct(_) | SchemaType::Enum(_))
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StructDefinition {
    pub fields: IndexMap<String, StructField>,
    pub is_tuple_struct: bool,
}

impl StructDefinition {
    pub fn new() -> Self {
        Self {
            fields: IndexMap::new(),
            is_tuple_struct: false,
        }
    }

    pub fn tuple() -> Self {
        Self {
            fields: IndexMap::new(),
            is_tuple_struct: true,
        }
    }

    pub fn with_field(mut self, name: impl Into<String>, field: StructField) -> Self {
        self.fields.insert(name.into(), field);
        self
    }

    pub fn required_fields(&self) -> Vec<&str> {
        self.fields
            .iter()
            .filter(|(_, f)| f.required)
            .map(|(name, _)| name.as_str())
            .collect()
    }
}

impl Default for StructDefinition {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StructField {
    pub schema_type: SchemaType,
    pub original_name: String,
    pub required: bool,
    pub description: Option<String>,
    pub format: Option<String>,
    pub default: Option<serde_json::Value>,
    pub deprecated: bool,
    pub min_length: Option<usize>,
    pub max_length: Option<usize>,
    pub minimum: Option<f64>,
    pub maximum: Option<f64>,
    pub exclusive_minimum: Option<f64>,
    pub exclusive_maximum: Option<f64>,
    pub pattern: Option<String>,
    pub format_renames: IndexMap<String, String>,
    pub skip_formats: Vec<String>,
    pub flatten: bool,
    pub nullable: bool,
    pub metadata: IndexMap<String, serde_json::Value>,
}

impl StructField {
    pub fn new(schema_type: SchemaType, original_name: impl Into<String>) -> Self {
        let original_name = original_name.into();
        let required = !matches!(schema_type, SchemaType::Option(_));
        Self {
            schema_type,
            original_name,
            required,
            description: None,
            format: None,
            default: None,
            deprecated: false,
            min_length: None,
            max_length: None,
            minimum: None,
            maximum: None,
            exclusive_minimum: None,
            exclusive_maximum: None,
            pattern: None,
            format_renames: IndexMap::new(),
            skip_formats: Vec::new(),
            flatten: false,
            nullable: false,
            metadata: IndexMap::new(),
        }
    }

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

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

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

    pub fn deprecated(mut self) -> Self {
        self.deprecated = true;
        self
    }

    pub fn with_length(mut self, min: Option<usize>, max: Option<usize>) -> Self {
        self.min_length = min;
        self.max_length = max;
        self
    }

    pub fn with_range(mut self, min: Option<f64>, max: Option<f64>) -> Self {
        self.minimum = min;
        self.maximum = max;
        self
    }

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

    pub fn flattened(mut self) -> Self {
        self.flatten = true;
        self
    }

    pub fn nullable(mut self) -> Self {
        self.nullable = true;
        self
    }

    pub fn with_required(mut self, required: bool) -> Self {
        self.required = required;
        self
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EnumDefinition {
    pub variants: Vec<EnumVariant>,
    pub representation: EnumRepresentation,
}

impl EnumDefinition {
    pub fn new(representation: EnumRepresentation) -> Self {
        Self {
            variants: Vec::new(),
            representation,
        }
    }

    pub fn with_variant(mut self, variant: EnumVariant) -> Self {
        self.variants.push(variant);
        self
    }

    pub fn is_simple_enum(&self) -> bool {
        self.variants
            .iter()
            .all(|v| matches!(v.data, VariantData::Unit))
    }

    pub fn variant_names(&self) -> Vec<&str> {
        self.variants.iter().map(|v| v.name.as_str()).collect()
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum EnumRepresentation {
    External,
    Internal { tag: String },
    Adjacent { tag: String, content: String },
    Untagged,
}

impl Default for EnumRepresentation {
    fn default() -> Self {
        EnumRepresentation::External
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct EnumVariant {
    pub name: String,
    pub original_name: String,
    pub data: VariantData,
    pub description: Option<String>,
    pub deprecated: bool,
    pub metadata: IndexMap<String, serde_json::Value>,
}

impl EnumVariant {
    pub fn unit(name: impl Into<String>) -> Self {
        let name = name.into();
        Self {
            original_name: name.clone(),
            name,
            data: VariantData::Unit,
            description: None,
            deprecated: false,
            metadata: IndexMap::new(),
        }
    }

    pub fn tuple(name: impl Into<String>, types: Vec<SchemaType>) -> Self {
        let name = name.into();
        Self {
            original_name: name.clone(),
            name,
            data: VariantData::Tuple(types),
            description: None,
            deprecated: false,
            metadata: IndexMap::new(),
        }
    }

    pub fn newtype(name: impl Into<String>, schema_type: SchemaType) -> Self {
        let name = name.into();
        Self {
            original_name: name.clone(),
            name,
            data: VariantData::Newtype(Box::new(schema_type)),
            description: None,
            deprecated: false,
            metadata: IndexMap::new(),
        }
    }

    pub fn struct_variant(name: impl Into<String>, fields: IndexMap<String, StructField>) -> Self {
        let name = name.into();
        Self {
            original_name: name.clone(),
            name,
            data: VariantData::Struct(fields),
            description: None,
            deprecated: false,
            metadata: IndexMap::new(),
        }
    }

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

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

    pub fn deprecated(mut self) -> Self {
        self.deprecated = true;
        self
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum VariantData {
    Unit,
    Newtype(Box<SchemaType>),
    Tuple(Vec<SchemaType>),
    Struct(IndexMap<String, StructField>),
}

impl VariantData {
    pub fn has_data(&self) -> bool {
        !matches!(self, VariantData::Unit)
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NewtypeDefinition {
    pub inner_type: Box<SchemaType>,
    pub transparent: bool,
}

impl NewtypeDefinition {
    pub fn new(inner_type: SchemaType) -> Self {
        Self {
            inner_type: Box::new(inner_type),
            transparent: false,
        }
    }

    pub fn transparent(inner_type: SchemaType) -> Self {
        Self {
            inner_type: Box::new(inner_type),
            transparent: true,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SchemaDefinition {
    pub name: Cow<'static, str>,
    pub schema_type: SchemaType,
    pub description: Option<String>,
    pub format: Option<String>,
    pub deprecated: bool,
    pub examples: Vec<serde_json::Value>,
    pub default: Option<serde_json::Value>,
    pub module_path: Option<String>,
    pub metadata: IndexMap<String, serde_json::Value>,
    pub dependencies: Vec<String>,
}

impl SchemaDefinition {
    pub fn new(name: impl Into<Cow<'static, str>>, schema_type: SchemaType) -> Self {
        Self {
            name: name.into(),
            schema_type,
            description: None,
            format: None,
            deprecated: false,
            examples: Vec::new(),
            default: None,
            module_path: None,
            metadata: IndexMap::new(),
            dependencies: Vec::new(),
        }
    }

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

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

    pub fn deprecated(mut self) -> Self {
        self.deprecated = true;
        self
    }

    pub fn with_example(mut self, example: serde_json::Value) -> Self {
        self.examples.push(example);
        self
    }

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

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

    pub fn with_dependency(mut self, dep: impl Into<String>) -> Self {
        self.dependencies.push(dep.into());
        self
    }

    pub fn with_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
        self.metadata.insert(key.into(), value);
        self
    }

    pub fn type_reference(&self) -> TypeReference {
        TypeReference::Named(self.name.to_string())
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FieldDefinition {
    pub name: String,
    pub original_name: String,
    pub schema_type: SchemaType,
    pub required: bool,
    pub attributes: crate::attributes::FieldAttribute,
}

impl FieldDefinition {
    pub fn new(
        name: impl Into<String>,
        original_name: impl Into<String>,
        schema_type: SchemaType,
    ) -> Self {
        let required = !matches!(schema_type, SchemaType::Option(_));
        Self {
            name: name.into(),
            original_name: original_name.into(),
            schema_type,
            required,
            attributes: crate::attributes::FieldAttribute::default(),
        }
    }

    pub fn with_required(mut self, required: bool) -> Self {
        self.required = required;
        self
    }

    pub fn with_attributes(mut self, attributes: crate::attributes::FieldAttribute) -> Self {
        self.attributes = attributes;
        self
    }
}

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

    #[test]
    fn test_primitive_type_properties() {
        assert!(PrimitiveType::I32.is_signed_integer());
        assert!(PrimitiveType::U64.is_unsigned_integer());
        assert!(PrimitiveType::F64.is_float());
        assert!(PrimitiveType::I32.is_numeric());
        assert!(!PrimitiveType::String.is_numeric());
    }

    #[test]
    fn test_primitive_type_bounds() {
        assert_eq!(PrimitiveType::I8.min_value(), Some(-128));
        assert_eq!(PrimitiveType::I8.max_value(), Some(127));
        assert_eq!(PrimitiveType::U8.min_value(), Some(0));
        assert_eq!(PrimitiveType::U8.max_value(), Some(255));
    }

    #[test]
    fn test_type_reference() {
        let tr = TypeReference::named("User");
        assert!(!tr.is_optional());

        let optional = tr.optional();
        assert!(optional.is_optional());
    }

    #[test]
    fn test_struct_definition() {
        let def = StructDefinition::new()
            .with_field(
                "name",
                StructField::new(SchemaType::Primitive(PrimitiveType::String), "name"),
            )
            .with_field(
                "age",
                StructField::new(
                    SchemaType::Option(Box::new(SchemaType::Primitive(PrimitiveType::U8))),
                    "age",
                ),
            );

        assert_eq!(def.fields.len(), 2);
        assert_eq!(def.required_fields(), vec!["name"]);
    }

    #[test]
    fn test_enum_definition() {
        let def = EnumDefinition::new(EnumRepresentation::External)
            .with_variant(EnumVariant::unit("Active"))
            .with_variant(EnumVariant::unit("Inactive"));

        assert!(def.is_simple_enum());
        assert_eq!(def.variant_names(), vec!["Active", "Inactive"]);
    }

    #[test]
    fn test_enum_with_data() {
        let mut fields = IndexMap::new();
        fields.insert(
            "reason".to_string(),
            StructField::new(SchemaType::Primitive(PrimitiveType::String), "reason"),
        );

        let def = EnumDefinition::new(EnumRepresentation::External)
            .with_variant(EnumVariant::unit("Active"))
            .with_variant(EnumVariant::struct_variant("Suspended", fields));

        assert!(!def.is_simple_enum());
    }

    #[test]
    fn test_schema_definition() {
        let def = SchemaDefinition::new("User", SchemaType::Struct(StructDefinition::new()))
            .with_description("A user in the system")
            .with_dependency("Status");

        assert_eq!(def.name, "User");
        assert!(def.description.is_some());
        assert_eq!(def.dependencies, vec!["Status"]);
    }
}
