//! Code generation: converts parsed IDL type definitions to Rust source code.
//!
//! The generated code uses the derive macros from `cyclonedds-derive`:
//! - `DdsType` for structs
//! - `DdsEnum` for enums
//! - `DdsUnion` for unions
//! - `DdsBitmask` for bitmasks

use crate::idl_parser::{
    IdlBitmask, IdlEnum, IdlFile, IdlStruct, IdlType, IdlTypeRef, IdlUnion, PrimitiveType,
};

/// Generate a complete Rust source file from a parsed IDL file.
pub fn generate_rust(idl_file: &IdlFile, module_name: &str) -> String {
    let mut output = String::new();

    output.push_str("// @generated by cyclonedds-build. DO NOT EDIT.\n");
    output.push_str("// Source IDL module: ");
    output.push_str(module_name);
    output.push_str("\n\n");
    output
        .push_str("#![allow(unused_imports, dead_code, non_camel_case_types, non_snake_case)]\n\n");
    output.push_str(
        "use cyclonedds::{DdsTypeDerive, DdsEnumDerive, DdsUnionDerive, DdsBitmaskDerive};\n",
    );
    output.push_str("use cyclonedds::{DdsSequence, DdsBoundedSequence, DdsString};\n\n");

    // Generate top-level types
    for ty in &idl_file.types {
        output.push_str(&generate_type(ty));
        output.push('\n');
    }

    // Generate module-scoped types
    for (mod_name, types) in &idl_file.modules {
        output.push_str(&generate_module(mod_name, types));
        output.push('\n');
    }

    output
}

/// Generate a Rust module wrapping a set of types.
fn generate_module(name: &str, types: &[IdlType]) -> String {
    let mut output = String::new();
    output.push_str("pub mod ");
    output.push_str(&to_snake_case(name));
    output.push_str(" {\n");
    output.push_str("    use super::*;\n\n");

    for ty in types {
        let code = generate_type(ty);
        for line in code.lines() {
            output.push_str("    ");
            output.push_str(line);
            output.push('\n');
        }
        output.push('\n');
    }

    output.push_str("}\n");
    output
}

/// Generate Rust code for a single IDL type definition.
fn generate_type(ty: &IdlType) -> String {
    match ty {
        IdlType::Struct(s) => generate_struct(s),
        IdlType::Enum(e) => generate_enum(e),
        IdlType::Union(u) => generate_union(u),
        IdlType::Bitmask(b) => generate_bitmask(b),
        IdlType::Typedef(td) => generate_typedef(td),
    }
}

/// Generate a Rust struct with `#[derive(DdsType)]`.
fn generate_struct(s: &IdlStruct) -> String {
    let mut output = String::new();

    output.push_str("#[derive(Debug, Clone, DdsTypeDerive)]\n");
    output.push_str("pub struct ");
    output.push_str(&s.name);
    output.push_str(" {\n");

    for field in &s.fields {
        // Generate field annotations
        for ann in &field.annotations {
            match ann.name.as_str() {
                "key" => output.push_str("    #[key]\n"),
                "position" | "id" | "hash_id" => {
                    // These are IDL-level annotations; the derive macro handles them
                    // via the op descriptor layout. No Rust attribute needed for now.
                }
                _ => {}
            }
        }

        output.push_str("    pub ");
        output.push_str(&field.name);
        output.push_str(": ");
        output.push_str(&type_ref_to_rust(&field.ty));
        output.push_str(",\n");
    }

    output.push_str("}\n");
    output
}

/// Generate a Rust enum with `#[derive(DdsEnum)]`.
fn generate_enum(e: &IdlEnum) -> String {
    let mut output = String::new();

    output.push_str("#[derive(Debug, Clone, Copy, PartialEq, Eq, DdsEnumDerive)]\n");
    output.push_str("pub enum ");
    output.push_str(&e.name);
    output.push_str(" {\n");

    for variant in &e.variants {
        output.push_str("    ");
        output.push_str(&variant.name);
        if let Some(val) = variant.value {
            output.push_str(" = ");
            output.push_str(&val.to_string());
        }
        output.push_str(",\n");
    }

    output.push_str("}\n");
    output
}

/// Generate a Rust enum with `#[derive(DdsUnion)]`.
fn generate_union(u: &IdlUnion) -> String {
    let mut output = String::new();

    output.push_str("#[derive(Debug, Clone, DdsUnionDerive)]\n");
    output.push_str("#[dds_discriminant(");
    output.push_str(&type_ref_to_rust(&u.discriminant_type));
    output.push_str(")]\n");
    output.push_str("pub enum ");
    output.push_str(&u.name);
    output.push_str(" {\n");

    for case in &u.cases {
        for &label in &case.label_values {
            output.push_str("    #[dds_case(");
            output.push_str(&label.to_string());
            output.push_str(")]\n");
        }
        output.push_str("    ");
        output.push_str(&to_pascal_case(&case.name));
        output.push('(');
        output.push_str(&type_ref_to_rust(&case.ty));
        output.push_str("),\n");
    }

    if let Some(default) = &u.default_case {
        output.push_str("    #[dds_default]\n");
        output.push_str("    ");
        output.push_str(&to_pascal_case(&default.name));
        output.push('(');
        output.push_str(&type_ref_to_rust(&default.ty));
        output.push_str("),\n");
    }

    output.push_str("}\n");
    output
}

/// Generate a Rust struct with `#[derive(DdsBitmask)]`.
fn generate_bitmask(b: &IdlBitmask) -> String {
    let mut output = String::new();

    output.push_str("#[derive(Debug, Clone, DdsBitmaskDerive)]\n");
    output.push_str("#[bit_bound(");
    output.push_str(&b.bit_bound.to_string());
    output.push_str(")]\n");
    output.push_str("pub struct ");
    output.push_str(&b.name);
    output.push_str(" {\n");

    for flag in &b.flags {
        output.push_str("    pub ");
        output.push_str(flag);
        output.push_str(": bool,\n");
    }

    output.push_str("}\n");
    output
}

/// Generate a Rust type alias for a typedef.
fn generate_typedef(td: &crate::idl_parser::IdlTypedef) -> String {
    let mut output = String::new();
    output.push_str("pub type ");
    output.push_str(&td.name);
    output.push_str(" = ");
    output.push_str(&type_ref_to_rust(&td.ty));
    output.push_str(";\n");
    output
}

/// Map an IDL type reference to a Rust type string.
fn type_ref_to_rust(ty: &IdlTypeRef) -> String {
    match ty {
        IdlTypeRef::Primitive(p) => primitive_to_rust(*p).to_string(),
        IdlTypeRef::String { bound: None } => "String".to_string(),
        IdlTypeRef::String { bound: Some(_n) } => {
            // Bounded strings use DdsString in the current derive macro
            "DdsString".to_string()
        }
        IdlTypeRef::Sequence {
            element_type,
            bound: None,
        } => {
            format!("DdsSequence<{}>", type_ref_to_rust(element_type))
        }
        IdlTypeRef::Sequence {
            element_type,
            bound: Some(max),
        } => {
            format!(
                "DdsBoundedSequence<{}, {}>",
                type_ref_to_rust(element_type),
                max
            )
        }
        IdlTypeRef::Array { element_type, size } => {
            format!("[{}; {}]", type_ref_to_rust(element_type), size)
        }
        IdlTypeRef::Named(name) => {
            // Strip leading :: if present, and use the simple name
            let clean = name.trim_start_matches("::");
            // For scoped names like Module::Type, just use the last segment
            // as the user is expected to import or use the correct path
            if let Some(last) = clean.rsplit("::").next() {
                last.to_string()
            } else {
                clean.to_string()
            }
        }
    }
}

/// Map an IDL primitive type to a Rust type.
fn primitive_to_rust(p: PrimitiveType) -> &'static str {
    match p {
        PrimitiveType::Boolean => "bool",
        PrimitiveType::Octet => "u8",
        PrimitiveType::Char => "u8",
        PrimitiveType::Wchar => "u16",
        PrimitiveType::Short => "i16",
        PrimitiveType::UnsignedShort => "u16",
        PrimitiveType::Long => "i32",
        PrimitiveType::UnsignedLong => "u32",
        PrimitiveType::LongLong => "i64",
        PrimitiveType::UnsignedLongLong => "u64",
        PrimitiveType::Float => "f32",
        PrimitiveType::Double => "f64",
        PrimitiveType::LongDouble => "f64", // Rust has no f128; approximate with f64
    }
}

/// Convert a snake_case or lowercase string to PascalCase.
fn to_pascal_case(s: &str) -> String {
    s.split('_')
        .map(|part| {
            let mut chars = part.chars();
            match chars.next() {
                None => String::new(),
                Some(c) => {
                    let upper = c.to_uppercase().to_string();
                    format!("{}{}", upper, chars.as_str())
                }
            }
        })
        .collect()
}

/// Convert a CamelCase string to snake_case.
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('_');
            }
            result.push(c.to_ascii_lowercase());
        } else {
            result.push(c);
        }
    }
    result
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::idl_parser::{IdlEnumVariant, IdlField};

    #[test]
    fn test_generate_simple_struct() {
        let s = IdlStruct {
            name: "Point".into(),
            fields: vec![
                IdlField {
                    name: "x".into(),
                    ty: IdlTypeRef::Primitive(PrimitiveType::Long),
                    annotations: vec![],
                },
                IdlField {
                    name: "y".into(),
                    ty: IdlTypeRef::Primitive(PrimitiveType::Double),
                    annotations: vec![],
                },
            ],
            annotations: vec![],
        };
        let code = generate_struct(&s);
        assert!(code.contains("#[derive(Debug, Clone, DdsTypeDerive)]"));
        assert!(code.contains("pub struct Point"));
        assert!(code.contains("pub x: i32"));
        assert!(code.contains("pub y: f64"));
    }

    #[test]
    fn test_generate_enum() {
        let e = IdlEnum {
            name: "Color".into(),
            variants: vec![
                IdlEnumVariant {
                    name: "Red".into(),
                    value: Some(0),
                },
                IdlEnumVariant {
                    name: "Green".into(),
                    value: Some(1),
                },
                IdlEnumVariant {
                    name: "Blue".into(),
                    value: Some(2),
                },
            ],
        };
        let code = generate_enum(&e);
        assert!(code.contains("#[derive(Debug, Clone, Copy, PartialEq, Eq, DdsEnumDerive)]"));
        assert!(code.contains("Red = 0"));
        assert!(code.contains("Blue = 2"));
    }

    #[test]
    fn test_type_ref_mapping() {
        assert_eq!(
            type_ref_to_rust(&IdlTypeRef::Primitive(PrimitiveType::Boolean)),
            "bool"
        );
        assert_eq!(
            type_ref_to_rust(&IdlTypeRef::Primitive(PrimitiveType::Long)),
            "i32"
        );
        assert_eq!(
            type_ref_to_rust(&IdlTypeRef::Primitive(PrimitiveType::UnsignedLongLong)),
            "u64"
        );
        assert_eq!(
            type_ref_to_rust(&IdlTypeRef::String { bound: None }),
            "String"
        );
        assert_eq!(
            type_ref_to_rust(&IdlTypeRef::Named("MyType".into())),
            "MyType"
        );
    }

    #[test]
    fn test_generate_bitmask() {
        let b = IdlBitmask {
            name: "Flags".into(),
            bit_bound: 16,
            flags: vec!["Ready".into(), "Active".into()],
        };
        let code = generate_bitmask(&b);
        assert!(code.contains("#[derive(Debug, Clone, DdsBitmaskDerive)]"));
        assert!(code.contains("#[bit_bound(16)]"));
        assert!(code.contains("pub Ready: bool"));
    }
}
