//! Rust type definitions output backend.
//!
//! Generates Rust structs with serde derives.

use crate::ir::{EnumKind, Field, Schema, Type, TypeDef, TypeDefKind};
use crate::traits::{Backend, BackendCategory};

/// Options for Rust code generation.
#[derive(Debug, Clone, Default)]
pub struct RustOptions {
    /// Whether to add serde derives.
    pub serde: bool,
    /// Whether to add Debug derive.
    pub debug: bool,
    /// Whether to add Clone derive.
    pub clone: bool,
    /// Whether to add PartialEq derive.
    pub partial_eq: bool,
    /// Whether to use serde rename_all for field names.
    pub rename_all: Option<String>,
    /// Whether to make structs pub.
    pub public: bool,
}

impl RustOptions {
    pub fn with_serde() -> Self {
        Self {
            serde: true,
            debug: true,
            clone: true,
            partial_eq: false,
            rename_all: Some("camelCase".to_string()),
            public: true,
        }
    }
}

/// Generate Rust type definitions from an IR schema.
pub fn generate_rust_types(schema: &Schema, options: &RustOptions) -> String {
    let mut out = String::new();
    out.push_str("//! Auto-generated by normalize-typegen\n\n");

    // Imports
    if options.serde {
        out.push_str("use serde::{Deserialize, Serialize};\n\n");
    }

    for (i, def) in schema.definitions.iter().enumerate() {
        if i > 0 {
            out.push('\n');
        }
        generate_typedef(&mut out, def, options);
    }

    out
}

fn generate_typedef(out: &mut String, def: &TypeDef, options: &RustOptions) {
    // Doc comment
    if let Some(docs) = &def.docs {
        out.push_str("/// ");
        out.push_str(docs);
        out.push('\n');
    }

    // Derives
    let mut derives = Vec::new();
    if options.debug {
        derives.push("Debug");
    }
    if options.clone {
        derives.push("Clone");
    }
    if options.partial_eq {
        derives.push("PartialEq");
    }
    if options.serde {
        derives.push("Serialize");
        derives.push("Deserialize");
    }

    let vis = if options.public { "pub " } else { "" };

    match &def.kind {
        TypeDefKind::Struct(s) => {
            if !derives.is_empty() {
                out.push_str("#[derive(");
                out.push_str(&derives.join(", "));
                out.push_str(")]\n");
            }
            if options.serde
                && let Some(rename) = &options.rename_all
            {
                out.push_str("#[serde(rename_all = \"");
                out.push_str(rename);
                out.push_str("\")]\n");
            }

            out.push_str(vis);
            out.push_str("struct ");
            out.push_str(&def.name);
            out.push_str(" {\n");

            for field in &s.fields {
                generate_field(out, field, options, false);
            }

            out.push_str("}\n");
        }
        TypeDefKind::Enum(e) => match &e.kind {
            EnumKind::StringLiteral(variants) => {
                if !derives.is_empty() {
                    out.push_str("#[derive(");
                    out.push_str(&derives.join(", "));
                    out.push_str(")]\n");
                }
                if options.serde {
                    out.push_str("#[serde(rename_all = \"snake_case\")]\n");
                }

                out.push_str(vis);
                out.push_str("enum ");
                out.push_str(&def.name);
                out.push_str(" {\n");

                for variant in variants {
                    let variant_name = to_pascal_case(&variant.value);
                    // Add serde rename if the pascal case doesn't match original
                    if options.serde && variant_name.to_lowercase() != variant.value {
                        out.push_str("    #[serde(rename = \"");
                        out.push_str(&variant.value);
                        out.push_str("\")]\n");
                    }
                    out.push_str("    ");
                    out.push_str(&variant_name);
                    out.push_str(",\n");
                }

                out.push_str("}\n");
            }
            EnumKind::IntLiteral(variants) => {
                if !derives.is_empty() {
                    out.push_str("#[derive(");
                    out.push_str(&derives.join(", "));
                    out.push_str(")]\n");
                }
                if options.serde {
                    out.push_str("#[serde(from = \"i64\", into = \"i64\")]\n");
                }
                out.push_str("#[repr(i64)]\n");

                out.push_str(vis);
                out.push_str("enum ");
                out.push_str(&def.name);
                out.push_str(" {\n");

                for variant in variants {
                    let variant_name = variant
                        .name
                        .clone()
                        .unwrap_or_else(|| format!("V{}", variant.value));
                    out.push_str("    ");
                    out.push_str(&variant_name);
                    out.push_str(" = ");
                    out.push_str(&variant.value.to_string());
                    out.push_str(",\n");
                }

                out.push_str("}\n");
            }
            EnumKind::Tagged(tagged) => {
                if !derives.is_empty() {
                    out.push_str("#[derive(");
                    out.push_str(&derives.join(", "));
                    out.push_str(")]\n");
                }
                if options.serde {
                    out.push_str("#[serde(tag = \"");
                    out.push_str(&tagged.discriminator);
                    out.push_str("\")]\n");
                    if let Some(rename) = &options.rename_all {
                        out.push_str("#[serde(rename_all = \"");
                        out.push_str(rename);
                        out.push_str("\")]\n");
                    }
                }

                out.push_str(vis);
                out.push_str("enum ");
                out.push_str(&def.name);
                out.push_str(" {\n");

                for variant in &tagged.variants {
                    if let Some(docs) = &variant.docs {
                        out.push_str("    /// ");
                        out.push_str(docs);
                        out.push('\n');
                    }

                    let variant_name = to_pascal_case(&variant.tag);
                    if options.serde && variant_name.to_lowercase() != variant.tag {
                        out.push_str("    #[serde(rename = \"");
                        out.push_str(&variant.tag);
                        out.push_str("\")]\n");
                    }

                    out.push_str("    ");
                    out.push_str(&variant_name);

                    if variant.fields.is_empty() {
                        out.push_str(",\n");
                    } else {
                        out.push_str(" {\n");
                        for field in &variant.fields {
                            generate_field(out, field, options, true);
                        }
                        out.push_str("    },\n");
                    }
                }

                out.push_str("}\n");
            }
        },
        TypeDefKind::Alias(ty) => {
            out.push_str(vis);
            out.push_str("type ");
            out.push_str(&def.name);
            out.push_str(" = ");
            out.push_str(&type_to_rust(ty));
            out.push_str(";\n");
        }
    }
}

fn generate_field(out: &mut String, field: &Field, options: &RustOptions, in_enum_variant: bool) {
    let indent = if in_enum_variant { "        " } else { "    " };

    // Doc comment
    if let Some(docs) = &field.docs {
        out.push_str(indent);
        out.push_str("/// ");
        out.push_str(docs);
        out.push('\n');
    }

    // Serde skip_serializing_if for optional fields
    if options.serde && !field.required {
        out.push_str(indent);
        out.push_str("#[serde(skip_serializing_if = \"Option::is_none\")]\n");
    }

    // Enum variant fields don't have visibility modifiers (inherently public)
    let vis = if in_enum_variant {
        ""
    } else if options.public {
        "pub "
    } else {
        ""
    };

    out.push_str(indent);
    out.push_str(vis);
    out.push_str(&to_snake_case(&field.name));
    out.push_str(": ");

    if field.required {
        out.push_str(&type_to_rust(&field.ty));
    } else {
        out.push_str("Option<");
        out.push_str(&type_to_rust(&field.ty));
        out.push('>');
    }

    out.push_str(",\n");
}

fn type_to_rust(ty: &Type) -> String {
    match ty {
        Type::String => "String".to_string(),
        Type::Integer { bits, signed } => match (bits, signed) {
            (8, true) => "i8",
            (8, false) => "u8",
            (16, true) => "i16",
            (16, false) => "u16",
            (32, true) => "i32",
            (32, false) => "u32",
            (64, true) => "i64",
            (64, false) => "u64",
            _ => "i64",
        }
        .to_string(),
        Type::Float { bits } => if *bits <= 32 { "f32" } else { "f64" }.to_string(),
        Type::Boolean => "bool".to_string(),
        Type::Null => "()".to_string(),
        Type::Array(inner) => format!("Vec<{}>", type_to_rust(inner)),
        Type::Map { value, .. } => {
            format!("std::collections::HashMap<String, {}>", type_to_rust(value))
        }
        Type::Optional(inner) => format!("Option<{}>", type_to_rust(inner)),
        Type::Ref(name) => name.clone(),
        Type::Union(types) => {
            // Rust doesn't have ad-hoc unions, use an enum or Box<dyn Any>
            if types.len() == 2 {
                // Check for Option-like pattern (T | null)
                let non_null: Vec<_> = types.iter().filter(|t| !matches!(t, Type::Null)).collect();
                if non_null.len() == 1 {
                    return format!("Option<{}>", type_to_rust(non_null[0]));
                }
            }
            "serde_json::Value".to_string() // Fallback for arbitrary unions
        }
        Type::StringLiteral(_) => "String".to_string(), // Rust doesn't have literal types
        Type::IntLiteral(_) => "i64".to_string(),
        Type::BoolLiteral(_) => "bool".to_string(),
        Type::Any => "serde_json::Value".to_string(),
    }
}

fn to_snake_case(s: &str) -> String {
    let mut result = String::new();
    for (i, c) in s.chars().enumerate() {
        if c.is_uppercase() {
            if i > 0 {
                result.push('_');
            }
            // normalize-syntax-allow: rust/unwrap-in-impl - to_lowercase() always yields at least one char
            result.push(c.to_lowercase().next().unwrap());
        } else {
            result.push(c);
        }
    }
    result
}

fn to_pascal_case(s: &str) -> String {
    let mut result = String::new();
    let mut capitalize_next = true;
    for c in s.chars() {
        if c == '_' || c == '-' || c == '.' {
            capitalize_next = true;
        } else if capitalize_next {
            // normalize-syntax-allow: rust/unwrap-in-impl - to_uppercase() always yields at least one char
            result.push(c.to_uppercase().next().unwrap());
            capitalize_next = false;
        } else {
            result.push(c);
        }
    }
    result
}

/// Static backend instance with default options.
pub static RUST_BACKEND: RustBackend = RustBackend {
    options: RustOptions {
        serde: true,
        debug: true,
        clone: true,
        partial_eq: false,
        rename_all: None, // Will be set in generate
        public: true,
    },
};

/// Rust backend with configurable options.
pub struct RustBackend {
    /// Generation options.
    pub options: RustOptions,
}

impl RustBackend {
    /// Create a new Rust backend with the given options.
    pub fn new(options: RustOptions) -> Self {
        Self { options }
    }
}

impl Backend for RustBackend {
    fn name(&self) -> &'static str {
        "rust"
    }

    fn language(&self) -> &'static str {
        "rust"
    }

    fn extension(&self) -> &'static str {
        "rs"
    }

    fn category(&self) -> BackendCategory {
        BackendCategory::Types
    }

    fn generate(&self, schema: &Schema) -> String {
        // Use with_serde defaults if using default RUST_BACKEND
        let options = if self.options.rename_all.is_none() && self.options.serde {
            RustOptions {
                rename_all: Some("camelCase".to_string()),
                ..self.options.clone()
            }
        } else {
            self.options.clone()
        };
        generate_rust_types(schema, &options)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ir::{Field, TypeDef};

    #[test]
    fn generate_simple_struct() {
        let mut schema = Schema::default();
        schema.add(TypeDef::structure(
            "User",
            vec![
                Field::required("id", Type::String),
                Field::required("name", Type::String),
                Field::optional("email", Type::String),
            ],
        ));

        let output = generate_rust_types(&schema, &RustOptions::with_serde());

        assert!(output.contains("use serde::{Deserialize, Serialize}"));
        assert!(output.contains("#[derive(Debug, Clone, Serialize, Deserialize)]"));
        assert!(output.contains("pub struct User {"));
        assert!(output.contains("pub id: String,"));
        assert!(output.contains("pub name: String,"));
        assert!(output.contains("pub email: Option<String>,"));
    }

    #[test]
    fn generate_string_enum() {
        let mut schema = Schema::default();
        schema.add(TypeDef::string_enum(
            "Status",
            vec!["pending", "active", "done"],
        ));

        let output = generate_rust_types(&schema, &RustOptions::with_serde());

        assert!(output.contains("pub enum Status {"));
        assert!(output.contains("Pending,"));
        assert!(output.contains("Active,"));
        assert!(output.contains("Done,"));
    }

    #[test]
    fn generate_without_serde() {
        let mut schema = Schema::default();
        schema.add(TypeDef::structure(
            "User",
            vec![Field::required("id", Type::String)],
        ));

        let output = generate_rust_types(
            &schema,
            &RustOptions {
                debug: true,
                clone: true,
                ..Default::default()
            },
        );

        assert!(output.contains("#[derive(Debug, Clone)]"));
        assert!(!output.contains("Serialize"));
        assert!(!output.contains("use serde"));
    }
}
