
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct TypeAttribute {
    pub description: Option<String>,
    pub deprecated: bool,
    pub rename: Option<String>,
    pub rename_all: Option<RenameRule>,
    pub examples: Vec<serde_json::Value>,
    pub format_metadata: IndexMap<String, IndexMap<String, serde_json::Value>>,
}

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

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

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

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

    pub fn with_rename_all(mut self, rule: RenameRule) -> Self {
        self.rename_all = Some(rule);
        self
    }

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

#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct FieldAttribute {
    pub description: Option<String>,
    pub deprecated: bool,
    pub rename: Option<String>,
    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 multiple_of: Option<f64>,
    pub pattern: Option<String>,
    pub format: Option<String>,
    pub default: Option<serde_json::Value>,
    pub skip: bool,
    pub skip_formats: Vec<String>,
    pub flatten: bool,
    pub nullable: Option<bool>,
    pub format_renames: IndexMap<String, String>,
    pub examples: Vec<serde_json::Value>,
    pub read_only: bool,
    pub write_only: bool,
    pub format_metadata: IndexMap<String, IndexMap<String, serde_json::Value>>,
}

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

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

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

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

    pub fn with_min_length(mut self, len: usize) -> Self {
        self.min_length = Some(len);
        self
    }

    pub fn with_max_length(mut self, len: usize) -> Self {
        self.max_length = Some(len);
        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_minimum(mut self, min: f64) -> Self {
        self.minimum = Some(min);
        self
    }

    pub fn with_maximum(mut self, max: f64) -> Self {
        self.maximum = Some(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 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 skip(mut self) -> Self {
        self.skip = true;
        self
    }

    pub fn skip_for(mut self, formats: impl IntoIterator<Item = impl Into<String>>) -> Self {
        self.skip_formats = formats.into_iter().map(|f| f.into()).collect();
        self
    }

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

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

    pub fn rename_for(mut self, format: impl Into<String>, name: impl Into<String>) -> Self {
        self.format_renames.insert(format.into(), name.into());
        self
    }

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

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

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

    pub fn effective_name<'a>(&'a self, format: &str, original: &'a str) -> &'a str {
        self.format_renames
            .get(format)
            .map(|s| s.as_str())
            .or(self.rename.as_deref())
            .unwrap_or(original)
    }

    pub fn should_skip_for(&self, format: &str) -> bool {
        self.skip || self.skip_formats.iter().any(|f| f == format)
    }
}

#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct EnumAttribute {
    #[serde(flatten)]
    pub type_attr: TypeAttribute,
    pub representation: Option<EnumRepresentationAttr>,
}

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

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

    pub fn with_representation(mut self, repr: EnumRepresentationAttr) -> Self {
        self.representation = Some(repr);
        self
    }
}

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

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

#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct VariantAttribute {
    pub description: Option<String>,
    pub deprecated: bool,
    pub rename: Option<String>,
    pub skip: bool,
    pub format_renames: IndexMap<String, String>,
    pub aliases: Vec<String>,
    pub format_metadata: IndexMap<String, IndexMap<String, serde_json::Value>>,
}

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

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

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

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

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

    pub fn rename_for(mut self, format: impl Into<String>, name: impl Into<String>) -> Self {
        self.format_renames.insert(format.into(), name.into());
        self
    }

    pub fn with_alias(mut self, alias: impl Into<String>) -> Self {
        self.aliases.push(alias.into());
        self
    }

    pub fn effective_name<'a>(&'a self, format: &str, original: &'a str) -> &'a str {
        self.format_renames
            .get(format)
            .map(|s| s.as_str())
            .or(self.rename.as_deref())
            .unwrap_or(original)
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RenameRule {
    None,
    Lowercase,
    Uppercase,
    PascalCase,
    CamelCase,
    SnakeCase,
    ScreamingSnakeCase,
    KebabCase,
    ScreamingKebabCase,
}

impl RenameRule {
    pub fn apply(&self, s: &str) -> String {
        match self {
            RenameRule::None => s.to_string(),
            RenameRule::Lowercase => s.to_lowercase(),
            RenameRule::Uppercase => s.to_uppercase(),
            RenameRule::PascalCase => to_pascal_case(s),
            RenameRule::CamelCase => to_camel_case(s),
            RenameRule::SnakeCase => to_snake_case(s),
            RenameRule::ScreamingSnakeCase => to_snake_case(s).to_uppercase(),
            RenameRule::KebabCase => to_snake_case(s).replace('_', "-"),
            RenameRule::ScreamingKebabCase => to_snake_case(s).replace('_', "-").to_uppercase(),
        }
    }
}

impl Default for RenameRule {
    fn default() -> Self {
        RenameRule::None
    }
}

fn to_pascal_case(s: &str) -> String {
    let mut result = String::with_capacity(s.len());
    let mut capitalize_next = true;

    for c in s.chars() {
        if c == '_' || c == '-' || c == ' ' {
            capitalize_next = true;
        } else if capitalize_next {
            result.extend(c.to_uppercase());
            capitalize_next = false;
        } else {
            result.push(c);
        }
    }

    result
}

fn to_camel_case(s: &str) -> String {
    let pascal = to_pascal_case(s);
    let mut chars = pascal.chars();
    match chars.next() {
        None => String::new(),
        Some(first) => first.to_lowercase().chain(chars).collect(),
    }
}

fn to_snake_case(s: &str) -> String {
    let mut result = String::with_capacity(s.len() + 4);
    let mut prev_was_uppercase = false;
    let mut prev_was_separator = true;

    for c in s.chars() {
        if c == '-' || c == ' ' {
            result.push('_');
            prev_was_separator = true;
            prev_was_uppercase = false;
        } else if c.is_uppercase() {
            if !prev_was_separator && !prev_was_uppercase {
                result.push('_');
            }
            result.extend(c.to_lowercase());
            prev_was_uppercase = true;
            prev_was_separator = false;
        } else {
            result.push(c);
            prev_was_uppercase = false;
            prev_was_separator = c == '_';
        }
    }

    result
}

#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct SchemaAttributes {
    pub type_attr: TypeAttribute,
    pub field_attrs: IndexMap<String, FieldAttribute>,
    pub variant_attrs: IndexMap<String, VariantAttribute>,
    pub enum_attr: Option<EnumAttribute>,
}

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

    pub fn with_type_attr(mut self, attr: TypeAttribute) -> Self {
        self.type_attr = attr;
        self
    }

    pub fn with_field_attr(mut self, field: impl Into<String>, attr: FieldAttribute) -> Self {
        self.field_attrs.insert(field.into(), attr);
        self
    }

    pub fn with_variant_attr(mut self, variant: impl Into<String>, attr: VariantAttribute) -> Self {
        self.variant_attrs.insert(variant.into(), attr);
        self
    }

    pub fn with_enum_attr(mut self, attr: EnumAttribute) -> Self {
        self.enum_attr = Some(attr);
        self
    }

    pub fn get_field_attr(&self, field: &str) -> &FieldAttribute {
        static DEFAULT: once_cell::sync::Lazy<FieldAttribute> =
            once_cell::sync::Lazy::new(FieldAttribute::default);
        self.field_attrs.get(field).unwrap_or(&DEFAULT)
    }

    pub fn get_variant_attr(&self, variant: &str) -> &VariantAttribute {
        static DEFAULT: once_cell::sync::Lazy<VariantAttribute> =
            once_cell::sync::Lazy::new(VariantAttribute::default);
        self.variant_attrs.get(variant).unwrap_or(&DEFAULT)
    }
}

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

    #[test]
    fn test_rename_rules() {
        assert_eq!(RenameRule::CamelCase.apply("user_name"), "userName");
        assert_eq!(RenameRule::PascalCase.apply("user_name"), "UserName");
        assert_eq!(RenameRule::SnakeCase.apply("userName"), "user_name");
        assert_eq!(
            RenameRule::ScreamingSnakeCase.apply("userName"),
            "USER_NAME"
        );
        assert_eq!(RenameRule::KebabCase.apply("user_name"), "user-name");
        assert_eq!(RenameRule::Lowercase.apply("UserName"), "username");
        assert_eq!(RenameRule::Uppercase.apply("userName"), "USERNAME");
    }

    #[test]
    fn test_field_attribute_effective_name() {
        let attr = FieldAttribute::new()
            .with_rename("defaultName")
            .rename_for("graphql", "graphqlName")
            .rename_for("typescript", "tsName");

        assert_eq!(attr.effective_name("graphql", "original"), "graphqlName");
        assert_eq!(attr.effective_name("typescript", "original"), "tsName");
        assert_eq!(attr.effective_name("json-schema", "original"), "defaultName");
    }

    #[test]
    fn test_field_attribute_skip() {
        let attr = FieldAttribute::new().skip_for(["graphql", "protobuf"]);

        assert!(attr.should_skip_for("graphql"));
        assert!(attr.should_skip_for("protobuf"));
        assert!(!attr.should_skip_for("json-schema"));
    }

    #[test]
    fn test_type_attribute_builder() {
        let attr = TypeAttribute::new()
            .with_description("A user type")
            .with_rename("UserDTO")
            .with_rename_all(RenameRule::CamelCase)
            .deprecated();

        assert_eq!(attr.description, Some("A user type".to_string()));
        assert_eq!(attr.rename, Some("UserDTO".to_string()));
        assert_eq!(attr.rename_all, Some(RenameRule::CamelCase));
        assert!(attr.deprecated);
    }

    #[test]
    fn test_schema_attributes() {
        let attrs = SchemaAttributes::new()
            .with_type_attr(TypeAttribute::new().with_description("Test type"))
            .with_field_attr(
                "name",
                FieldAttribute::new()
                    .with_min_length(1)
                    .with_max_length(100),
            )
            .with_field_attr("email", FieldAttribute::new().with_format("email"));

        assert!(attrs.type_attr.description.is_some());
        assert_eq!(attrs.get_field_attr("name").min_length, Some(1));
        assert_eq!(
            attrs.get_field_attr("email").format,
            Some("email".to_string())
        );
        assert!(attrs.get_field_attr("unknown").min_length.is_none());
    }
}
