// ===============================================================================
// QUANTALANG CODE GENERATOR - C BACKEND
// ===============================================================================
// Copyright (c) 2022-2026 Zain Dana Harper. MIT License.
// ===============================================================================

//! C code generation backend.
//!
//! Transpiles MIR to C99-compliant code for maximum portability.

// NOTE: `.unwrap()` calls in this backend are intentional assertions on
// type-checked AST invariants. Failures indicate compiler bugs in earlier
// phases, not user input errors. See codegen/mod.rs for the full unwrap policy.

use std::fmt::Write;
use std::sync::Arc;

use super::{Backend, CodegenResult, Target};
use crate::codegen::ir::*;
use crate::codegen::runtime;
use crate::codegen::{GeneratedCode, OutputFormat};

/// C backend for code generation.
pub struct CBackend {
    /// Output buffer.
    output: String,
    /// Indentation level.
    indent: usize,
    /// Temp variable counter.
    temp_counter: u32,
    /// Function parameter types — indexed by function name, stores param types.
    fn_params: std::collections::HashMap<String, Vec<MirType>>,
    /// Return type of the current function being generated.
    current_ret_ty: MirType,
    /// Name of the current function being generated (for Self resolution).
    current_fn_name: Option<String>,
}

impl CBackend {
    /// Create a new C backend.
    pub fn new() -> Self {
        Self {
            output: String::new(),
            indent: 0,
            temp_counter: 0,
            fn_params: std::collections::HashMap::new(),
            current_ret_ty: MirType::Void,
            current_fn_name: None,
        }
    }

    /// Write indentation.
    fn write_indent(&mut self) {
        for _ in 0..self.indent {
            self.output.push_str("    ");
        }
    }

    /// Write a line with indentation.
    fn writeln(&mut self, s: &str) {
        self.write_indent();
        self.output.push_str(s);
        self.output.push('\n');
    }

    /// Generate a fresh temp name.
    fn fresh_temp(&mut self) -> String {
        let name = format!("__tmp{}", self.temp_counter);
        self.temp_counter += 1;
        name
    }

    // =========================================================================
    // CODE GENERATION
    // =========================================================================

    fn generate_module(&mut self, module: &MirModule) -> CodegenResult<()> {
        // Header comment
        self.output
            .push_str("// Generated by QuantaLang Compiler\n");
        self.output.push_str("// Do not edit manually\n\n");

        // Prevent Windows API name collisions
        self.output.push_str("#ifdef _WIN32\n");
        self.output.push_str("#define WIN32_LEAN_AND_MEAN\n");
        self.output.push_str("#define NOGDI\n");
        self.output.push_str("#endif\n\n");

        // Standard includes
        self.output.push_str("#include <stdint.h>\n");
        self.output.push_str("#include <stdbool.h>\n");
        self.output.push_str("#include <stddef.h>\n");
        self.output.push_str("#include <stdio.h>\n");
        self.output.push_str("#include <stdlib.h>\n");
        self.output.push_str("#include <string.h>\n");
        self.output.push_str("#include <math.h>\n");
        self.output.push_str("#include <ctype.h>\n");
        self.output.push_str("#include <time.h>\n");
        self.output.push_str("#include <assert.h>\n");
        self.output.push('\n');

        // Undefine known Windows API macros that collide with common type names
        self.output.push_str("#ifdef _WIN32\n");
        self.output.push_str("#undef DeviceCapabilities\n");
        self.output.push_str("#undef Rectangle\n");
        self.output.push_str("#undef CreateWindow\n");
        self.output.push_str("#undef GetMessage\n");
        self.output.push_str("#undef SendMessage\n");
        self.output.push_str("#undef LoadImage\n");
        self.output.push_str("#undef DrawText\n");
        self.output.push_str("#undef GetObject\n");
        self.output.push_str("#endif\n\n");

        // Embedded runtime library
        self.output.push_str(runtime::runtime_header());
        self.output.push('\n');

        // Type definitions
        self.generate_type_definitions(&module.types)?;

        // Vtable TYPE declarations (before forward declarations so dyn_* types are available)
        self.generate_vtable_types(module)?;

        // String table
        self.generate_string_table(&module.strings)?;

        // Global variables
        self.generate_globals(&module.globals)?;

        // Forward declarations
        self.generate_forward_declarations(&module.functions)?;

        // Vtable INSTANCES (after forward declarations so function names are known)
        self.generate_vtable_instances(module)?;

        // Build function parameter type index for auto-ref at call sites
        for func in &module.functions {
            self.fn_params.insert(
                func.name.to_string(),
                func.sig.params.clone(),
            );
        }

        // Generate monomorphized HashMap wrappers for non-f64 value types.
        // Scan all function locals to discover Map types used in the module.
        {
            let mut map_val_types: std::collections::HashSet<String> = std::collections::HashSet::new();
            for func in &module.functions {
                for local in &func.locals {
                    if let MirType::Map(_, ref val_ty) = local.ty {
                        let val_c = self.type_to_c(val_ty);
                        if val_c != "double" {
                            map_val_types.insert(val_c);
                        }
                    }
                }
            }
            if !map_val_types.is_empty() {
                self.output.push_str("// Monomorphized HashMap wrappers for non-f64 value types\n");
                for val_c in &map_val_types {
                    let safe_name = val_c.replace("*", "ptr").replace(" ", "_");
                    write!(self.output, "static {} quanta_hmap_get_val_{}(QuantaStrF64MapHandle h, const char* key) {{\n", val_c, safe_name).unwrap();
                    write!(self.output, "    double d = quanta_hmap_get_str_f64(h, key);\n").unwrap();
                    write!(self.output, "    {} v; memset(&v, 0, sizeof(v));\n", val_c).unwrap();
                    write!(self.output, "    memcpy(&v, &d, sizeof(d) < sizeof(v) ? sizeof(d) : sizeof(v));\n").unwrap();
                    write!(self.output, "    return v;\n}}\n").unwrap();
                    write!(self.output, "static void quanta_hmap_insert_val_{}(QuantaStrF64MapHandle h, const char* key, {} value) {{\n", safe_name, val_c).unwrap();
                    write!(self.output, "    double d = 0; memcpy(&d, &value, sizeof(value) < sizeof(d) ? sizeof(value) : sizeof(d));\n").unwrap();
                    write!(self.output, "    quanta_hmap_insert_str_f64(h, key, d);\n}}\n\n").unwrap();
                }
            }
        }

        // Function definitions
        for func in &module.functions {
            if !func.is_declaration() {
                self.generate_function(func)?;
            }
        }

        Ok(())
    }

    fn generate_type_definitions(&mut self, types: &[MirTypeDef]) -> CodegenResult<()> {
        if types.is_empty() {
            return Ok(());
        }

        // Pre-emit typedefs for tuple types used in struct fields.
        // (f32, f32) → typedef struct Tuple_f32_f32 { float field0; float field1; } Tuple_f32_f32;
        let mut emitted_tuples = std::collections::HashSet::new();
        for ty in types {
            if let TypeDefKind::Struct { fields, .. } = &ty.kind {
                for (_, field_ty) in fields {
                    if let MirType::Struct(name) = field_ty {
                        if name.starts_with("Tuple_") && !emitted_tuples.contains(name.as_ref()) {
                            emitted_tuples.insert(name.to_string());
                            // Parse the tuple element types from the mangled name
                            let parts: Vec<&str> = name[6..].split('_').collect();
                            write!(self.output, "typedef struct {} {{\n", name).unwrap();
                            for (i, part) in parts.iter().enumerate() {
                                let c_type = match *part {
                                    "f32" => "float",
                                    "f64" => "double",
                                    "i32" => "int32_t",
                                    "i64" => "int64_t",
                                    "u32" => "uint32_t",
                                    "u64" => "uint64_t",
                                    "bool" => "bool",
                                    _ => "int32_t",
                                };
                                write!(self.output, "    {} field{};\n", c_type, i).unwrap();
                            }
                            write!(self.output, "}} {};\n\n", name).unwrap();
                        }
                    }
                }
            }
        }

        // Runtime-provided types that must not be re-emitted.
        const RUNTIME_TYPES: &[&str] = &[
            "quanta_vec2", "quanta_vec3", "quanta_vec4", "quanta_mat4",
            "VecDeque", "HashSet", "Option", "Result",
        ];

        // Emit forward declarations for struct/union/enum types.
        // Enums are emitted as tagged structs, so they get struct forward
        // declarations.  The body later uses `struct X { ... };` (not
        // `typedef struct X { ... } X;`) to avoid MSVC redefinition errors.
        self.output.push_str("// Forward declarations\n");
        for ty in types {
            if RUNTIME_TYPES.contains(&ty.name.as_ref()) {
                continue;
            }
            match &ty.kind {
                TypeDefKind::Struct { .. } | TypeDefKind::Enum { .. } => {
                    write!(self.output, "typedef struct {} {};\n", ty.name, ty.name).unwrap();
                }
                TypeDefKind::Union { .. } => {
                    write!(self.output, "typedef union {} {};\n", ty.name, ty.name).unwrap();
                }
            }
        }
        self.output.push('\n');

        // Topological sort: emit types in dependency order.
        // A type must be emitted after all types it references by value.
        let type_names: std::collections::HashSet<&str> =
            types.iter().map(|t| t.name.as_ref()).collect();

        let mut emitted: std::collections::HashSet<&str> = std::collections::HashSet::new();
        for rt in RUNTIME_TYPES {
            emitted.insert(rt);
        }

        // Recursively collect named-type dependencies from a MirType.
        // Only value types (not behind a pointer) require ordering.
        fn collect_type_deps<'a>(
            mir_ty: &'a MirType,
            type_names: &std::collections::HashSet<&str>,
            out: &mut Vec<&'a str>,
        ) {
            match mir_ty {
                MirType::Struct(name) => {
                    if type_names.contains(name.as_ref()) {
                        out.push(name.as_ref());
                    }
                }
                MirType::Array(inner, _) | MirType::Slice(inner) => {
                    collect_type_deps(inner, type_names, out);
                }
                MirType::Tuple(elems) => {
                    for e in elems {
                        collect_type_deps(e, type_names, out);
                    }
                }
                // Vec/Map/Ptr are behind pointers — no ordering needed
                _ => {}
            }
        }

        // Collect value dependencies for each type
        let deps: std::collections::HashMap<&str, Vec<&str>> = types
            .iter()
            .map(|ty| {
                let mut d = Vec::new();
                match &ty.kind {
                    TypeDefKind::Struct { fields, .. } => {
                        for (_, ft) in fields {
                            collect_type_deps(ft, &type_names, &mut d);
                        }
                    }
                    TypeDefKind::Enum { variants, .. } => {
                        for v in variants {
                            for (_, ft) in &v.fields {
                                collect_type_deps(ft, &type_names, &mut d);
                            }
                        }
                    }
                    TypeDefKind::Union { variants } => {
                        for (_, vt) in variants {
                            collect_type_deps(vt, &type_names, &mut d);
                        }
                    }
                }
                (ty.name.as_ref(), d)
            })
            .collect();

        // Emit types in dependency order (simple iterative approach)
        self.output.push_str("// Type definitions (dependency-ordered)\n");
        let mut remaining: Vec<&MirTypeDef> = types
            .iter()
            .filter(|t| !RUNTIME_TYPES.contains(&t.name.as_ref()))
            .collect();
        let max_passes = remaining.len() + 1;
        for _ in 0..max_passes {
            if remaining.is_empty() {
                break;
            }
            let mut next_remaining = Vec::new();
            for ty in &remaining {
                let type_deps = deps.get(ty.name.as_ref()).cloned().unwrap_or_default();
                if type_deps.iter().all(|d| emitted.contains(d)) {
                    self.emit_type_def(ty);
                    emitted.insert(ty.name.as_ref());
                } else {
                    next_remaining.push(*ty);
                }
            }
            if next_remaining.len() == remaining.len() {
                // Circular dependency — emit remaining in original order
                for ty in &next_remaining {
                    self.emit_type_def(ty);
                }
                break;
            }
            remaining = next_remaining;
        }

        Ok(())
    }

    fn emit_type_def(&mut self, ty: &MirTypeDef) {
        match &ty.kind {
            TypeDefKind::Struct { fields, packed } => {
                    if *packed {
                        self.output.push_str("#pragma pack(push, 1)\n");
                    }
                    // Use struct tag only (not typedef) since forward decl already typedef'd
                    write!(self.output, "struct {} {{\n", ty.name).unwrap();
                    self.indent += 1;
                    // C requires at least one member in a struct
                    if fields.is_empty() {
                        self.write_indent();
                        self.output.push_str("char _pad;\n");
                    }
                    for (i, (name, field_ty)) in fields.iter().enumerate() {
                        self.write_indent();
                        let field_name = name
                            .as_ref()
                            .map(|n| n.to_string())
                            .unwrap_or_else(|| format!("field{}", i));
                        // Escape C reserved words in field names
                        let field_name = Self::escape_c_keyword(&field_name);
                        if matches!(field_ty, MirType::Array(_, _)) {
                            write!(
                                self.output,
                                "{};\n",
                                self.fmt_array_decl(field_ty, &field_name)
                            )
                            .unwrap();
                        } else if let MirType::FnPtr(ref sig) = field_ty {
                            // Function pointer fields need special syntax:
                            // ret_type (*field_name)(param_types)
                            let ret = self.type_to_c(&sig.ret);
                            let params: Vec<_> = sig.params.iter().map(|p| self.type_to_c(p)).collect();
                            write!(
                                self.output,
                                "{} (*{})({}){}\n",
                                ret, field_name, params.join(", "),
                                ";"
                            )
                            .unwrap();
                        } else {
                            write!(
                                self.output,
                                "{} {};\n",
                                self.type_to_c(field_ty),
                                field_name
                            )
                            .unwrap();
                        }
                    }
                    self.indent -= 1;
                    self.output.push_str("};\n\n");
                    if *packed {
                        self.output.push_str("#pragma pack(pop)\n");
                    }
                }
                TypeDefKind::Union { variants } => {
                    write!(self.output, "union {} {{\n", ty.name).unwrap();
                    self.indent += 1;
                    for (name, var_ty) in variants {
                        self.write_indent();
                        write!(self.output, "{} {};\n", self.type_to_c(var_ty), name).unwrap();
                    }
                    self.indent -= 1;
                    self.output.push_str("};\n\n");
                }
                TypeDefKind::Enum {
                    discriminant_ty: _,
                    variants,
                } => {
                    // Generate enum discriminants
                    write!(self.output, "typedef enum {{\n").unwrap();
                    self.indent += 1;
                    for variant in variants {
                        self.write_indent();
                        write!(
                            self.output,
                            "{}_{} = {},\n",
                            ty.name, variant.name, variant.discriminant
                        )
                        .unwrap();
                    }
                    self.indent -= 1;
                    write!(self.output, "}} {}_Tag;\n\n", ty.name).unwrap();

                    // Generate tagged union (forward decl already typedef'd)
                    write!(self.output, "struct {} {{\n", ty.name).unwrap();
                    self.indent += 1;
                    self.write_indent();
                    write!(self.output, "{}_Tag tag;\n", ty.name).unwrap();
                    self.write_indent();
                    self.output.push_str("union {\n");
                    self.indent += 1;
                    for variant in variants {
                        if !variant.fields.is_empty() {
                            self.write_indent();
                            self.output.push_str("struct {\n");
                            self.indent += 1;
                            for (i, (fname, fty)) in variant.fields.iter().enumerate() {
                                self.write_indent();
                                let field_name = fname
                                    .as_ref()
                                    .map(|n| n.to_string())
                                    .unwrap_or_else(|| format!("f{}", i));
                                let field_name = Self::escape_c_keyword(&field_name);
                                if matches!(fty, MirType::Array(_, _)) {
                                    write!(
                                        self.output,
                                        "{};\n",
                                        self.fmt_array_decl(fty, &field_name)
                                    )
                                    .unwrap();
                                } else if let MirType::FnPtr(ref sig) = fty {
                                    let ret = self.type_to_c(&sig.ret);
                                    let params: Vec<_> = sig.params.iter().map(|p| self.type_to_c(p)).collect();
                                    write!(self.output, "{} (*{})({}){}\n", ret, field_name, params.join(", "), ";").unwrap();
                                } else {
                                    write!(
                                        self.output,
                                        "{} {};\n",
                                        self.type_to_c(fty),
                                        field_name
                                    )
                                    .unwrap();
                                }
                            }
                            self.indent -= 1;
                            self.write_indent();
                            write!(self.output, "}} {};\n", variant.name).unwrap();
                        } else {
                            // Unit variant: add empty struct so it can be
                            // referenced in designated initializers
                            self.write_indent();
                            write!(self.output, "char _{};\n", variant.name).unwrap();
                        }
                    }
                    self.indent -= 1;
                    self.write_indent();
                    self.output.push_str("} data;\n");
                    self.indent -= 1;
                    self.output.push_str("};\n\n");
                }
            }
    }

    fn generate_vtable_types(&mut self, module: &MirModule) -> CodegenResult<()> {
        if module.trait_methods.is_empty() {
            return Ok(());
        }

        self.output
            .push_str("// Vtable types for dynamic dispatch\n");

        for (trait_name, methods) in &module.trait_methods {
            write!(self.output, "typedef struct {}_vtable {{\n", trait_name).unwrap();
            for (method_name, sig) in methods {
                let ret = self.type_to_c(&sig.ret);
                let params: Vec<String> = sig.params.iter().map(|p| self.type_to_c(p)).collect();
                write!(
                    self.output,
                    "    {} (*{})({});\n",
                    ret,
                    method_name,
                    params.join(", ")
                )
                .unwrap();
            }
            write!(self.output, "}} {}_vtable;\n\n", trait_name).unwrap();

            write!(self.output, "typedef struct dyn_{} {{\n", trait_name).unwrap();
            write!(self.output, "    void* data;\n").unwrap();
            write!(self.output, "    {}_vtable* vtable;\n", trait_name).unwrap();
            write!(self.output, "}} dyn_{};\n\n", trait_name).unwrap();
        }

        Ok(())
    }

    fn generate_vtable_instances(&mut self, module: &MirModule) -> CodegenResult<()> {
        if module.vtables.is_empty() {
            return Ok(());
        }

        // Generate wrapper functions that dereference void* to concrete type
        for vtable in &module.vtables {
            for (method_name, mangled_fn, sig) in &vtable.methods {
                let ret = self.type_to_c(&sig.ret);
                let wrapper_name = format!(
                    "__vtable_wrap_{}_{}_{}",
                    vtable.type_name, vtable.trait_name, method_name
                );

                // Generate: ret wrapper(void* self, ...) { return concrete(*(Type*)self, ...); }
                let mut wrapper_params = vec!["void* __self".to_string()];
                let mut call_args = vec![format!("(*({}*)__self)", vtable.type_name)];
                for (i, param) in sig.params.iter().skip(1).enumerate() {
                    let param_ty = self.type_to_c(param);
                    wrapper_params.push(format!("{} __arg{}", param_ty, i));
                    call_args.push(format!("__arg{}", i));
                }

                write!(
                    self.output,
                    "static {} {}({}) {{ return {}({}); }}\n",
                    ret,
                    wrapper_name,
                    wrapper_params.join(", "),
                    mangled_fn,
                    call_args.join(", ")
                )
                .unwrap();
            }
        }
        self.output.push('\n');

        // Generate vtable instances using wrapper functions
        for vtable in &module.vtables {
            write!(
                self.output,
                "static {}_vtable {}_{}_vtable_instance = {{\n",
                vtable.trait_name, vtable.type_name, vtable.trait_name
            )
            .unwrap();

            for (method_name, _, sig) in &vtable.methods {
                let ret = self.type_to_c(&sig.ret);
                let params: Vec<String> = sig.params.iter().map(|p| self.type_to_c(p)).collect();
                let wrapper_name = format!(
                    "__vtable_wrap_{}_{}_{}",
                    vtable.type_name, vtable.trait_name, method_name
                );
                write!(
                    self.output,
                    "    .{} = ({} (*)({})){},\n",
                    method_name,
                    ret,
                    params.join(", "),
                    wrapper_name
                )
                .unwrap();
            }
            write!(self.output, "}};\n\n").unwrap();
        }

        Ok(())
    }

    fn generate_string_table(&mut self, strings: &[Arc<str>]) -> CodegenResult<()> {
        if strings.is_empty() {
            return Ok(());
        }

        self.output.push_str("// String table\n");
        for (i, s) in strings.iter().enumerate() {
            let escaped = self.escape_string(s);
            write!(
                self.output,
                "static const char* __str{} = \"{}\";\n",
                i, escaped
            )
            .unwrap();
        }
        self.output.push('\n');

        Ok(())
    }

    fn generate_globals(&mut self, globals: &[MirGlobal]) -> CodegenResult<()> {
        if globals.is_empty() {
            return Ok(());
        }

        self.output.push_str("// Global variables\n");
        for global in globals {
            let c_type = self.type_to_c(&global.ty);
            let mut decl = if global.is_mut {
                format!("{} {}", c_type, global.name)
            } else {
                format!("const {} {}", c_type, global.name)
            };

            if let Some(init) = &global.init {
                decl.push_str(" = ");
                decl.push_str(&self.const_to_c(init));
            }

            decl.push_str(";\n");
            self.output.push_str(&decl);
        }
        self.output.push('\n');

        Ok(())
    }

    fn generate_forward_declarations(&mut self, functions: &[MirFunction]) -> CodegenResult<()> {
        // Separate extern "C" declarations from regular forward declarations.
        let extern_fns: Vec<_> = functions
            .iter()
            .filter(|f| f.is_declaration() && f.sig.calling_conv == CallingConv::C)
            .collect();
        let regular_fns: Vec<_> = functions
            .iter()
            .filter(|f| !(f.is_declaration() && f.sig.calling_conv == CallingConv::C))
            .collect();

        // Extern "C" functions: skip declarations for standard C library
        // functions that are already available through the included headers.
        // For non-standard FFI functions, emit a proper extern declaration.
        if !extern_fns.is_empty() {
            let std_c_fns = [
                "printf",
                "fprintf",
                "sprintf",
                "snprintf",
                "scanf",
                "sscanf",
                "puts",
                "putchar",
                "getchar",
                "gets",
                "fgets",
                "fputs",
                "fopen",
                "fclose",
                "fread",
                "fwrite",
                "fseek",
                "ftell",
                "rewind",
                "malloc",
                "calloc",
                "realloc",
                "free",
                "memcpy",
                "memset",
                "memmove",
                "memcmp",
                "strlen",
                "strcpy",
                "strncpy",
                "strcat",
                "strncat",
                "strcmp",
                "strncmp",
                "atoi",
                "atof",
                "atol",
                "strtol",
                "strtod",
                "abs",
                "labs",
                "div",
                "rand",
                "srand",
                "exit",
                "abort",
                "atexit",
                "qsort",
                "bsearch",
                "sin",
                "cos",
                "tan",
                "asin",
                "acos",
                "atan",
                "atan2",
                "sinh",
                "cosh",
                "tanh",
                "sqrt",
                "cbrt",
                "pow",
                "exp",
                "exp2",
                "log",
                "log10",
                "log2",
                "ceil",
                "floor",
                "round",
                "trunc",
                "fabs",
                "fmod",
                "fmax",
                "fmin",
                "hypot",
                "copysign",
                "sinf",
                "cosf",
                "tanf",
                "sqrtf",
                "powf",
                "expf",
                "logf",
                "ceilf",
                "floorf",
                "roundf",
                "fabsf",
                "fmodf",
                "time",
                "clock",
                "isalpha",
                "isdigit",
                "isalnum",
                "isspace",
                "toupper",
                "tolower",
                // QuantaLang runtime-provided functions (graphics stub, file I/O)
                "quanta_gfx_init",
                "quanta_gfx_load_shader",
                "quanta_gfx_create_pipeline",
                "quanta_gfx_begin_frame",
                "quanta_gfx_clear",
                "quanta_gfx_draw",
                "quanta_gfx_end_frame",
                "quanta_gfx_should_close",
                "quanta_gfx_shutdown",
                // Directory traversal (provided by runtime)
                "quanta_list_dir",
                "quanta_is_dir",
                "quanta_file_size",
                // String vec handle (provided by runtime)
                "quanta_hvec_new_str",
                "quanta_hvec_push_str",
                "quanta_hvec_get_str",
                // TCP socket functions (provided by runtime)
                "quanta_tcp_connect",
                "quanta_tcp_send",
                "quanta_tcp_recv",
                "quanta_tcp_close",
            ];

            let non_std: Vec<_> = extern_fns
                .iter()
                .filter(|f| !std_c_fns.contains(&f.name.as_ref()))
                .collect();

            if !non_std.is_empty() {
                self.output.push_str("// Extern function declarations\n");
                for func in non_std {
                    self.output.push_str("extern ");
                    self.generate_function_signature(func)?;
                    self.output.push_str(";\n");
                }
                self.output.push('\n');
            }
        }

        // Regular forward declarations for QuantaLang-defined functions.
        // Skip declaration-only functions (no body) — they have no param info
        // and would generate incorrect `func(void)` forward declarations.
        self.output.push_str("// Forward declarations\n");
        for func in regular_fns {
            if !func.is_declaration() {
                self.generate_function_signature(func)?;
                self.output.push_str(";\n");
            }
        }
        self.output.push('\n');

        Ok(())
    }

    fn generate_function(&mut self, func: &MirFunction) -> CodegenResult<()> {
        self.current_ret_ty = func.sig.ret.clone();
        self.current_fn_name = Some(func.name.to_string());
        self.generate_function_signature(func)?;
        self.output.push_str(" {\n");
        self.indent += 1;

        // For main(), initialize I/O and command-line args before anything else
        if func.name.as_ref() == "main" {
            self.write_indent();
            self.output.push_str("__quanta_init_io();\n");
            self.write_indent();
            self.output.push_str("quanta_args_init(argc, argv);\n");
        }

        // Dead local elimination: collect all referenced local IDs from the
        // function body. Only declare locals that are actually used.
        let used_locals = Self::collect_used_locals(func);

        // Generate local declarations (skip dead locals)
        for local in &func.locals {
            if !local.is_param {
                // Skip void-typed locals -- C does not allow `void x;`
                if matches!(local.ty, MirType::Void) {
                    continue;
                }
                // Skip locals that are never referenced in the function body
                if !used_locals.contains(&local.id) {
                    continue;
                }
                self.write_indent();
                let name = self.local_name(local.id, &func.locals);
                // Arrays need special C declaration syntax: `type name[size]`
                // Function pointers need: `ret (*name)(params)`
                if matches!(local.ty, MirType::Array(_, _)) {
                    write!(self.output, "{};\n", self.fmt_array_decl(&local.ty, &name)).unwrap();
                } else if let MirType::FnPtr(ref sig) = local.ty {
                    let ret = self.type_to_c(&sig.ret);
                    let params: Vec<_> = sig.params.iter().map(|p| self.type_to_c(p)).collect();
                    write!(self.output, "{} (*{})({});\n", ret, name, params.join(", ")).unwrap();
                } else {
                    write!(self.output, "{} {};\n", self.type_to_c(&local.ty), name).unwrap();
                }
            }
        }

        // Emit parameter aliases when MIR renames differ from C signature names.
        // The C signature uses the original name (e.g., `x`) but the MIR body
        // uses a disambiguated name (e.g., `x_0`). Emit `type x_0 = x;` so the
        // body can reference the parameter by its MIR name.
        for local in &func.locals {
            if local.is_param && !matches!(local.ty, MirType::Void) {
                let mir_name = self.local_name(local.id, &func.locals);
                if let Some(ref orig) = local.name {
                    let orig_str = orig.to_string();
                    let c_name = if Self::is_c_reserved(&orig_str) {
                        format!("_{}", orig_str)
                    } else {
                        orig_str
                    };
                    if mir_name != c_name {
                        self.write_indent();
                        write!(
                            self.output,
                            "{} {} = {};\n",
                            self.type_to_c(&local.ty),
                            mir_name,
                            c_name
                        )
                        .unwrap();
                    }
                }
            }
        }

        if !func.locals.iter().filter(|l| !l.is_param).next().is_none() {
            self.output.push('\n');
        }

        // Generate basic blocks (all labels emitted; trivial goto→label pairs
        // are cleaned up by eliminate_trivial_gotos after generation).
        if let Some(blocks) = &func.blocks {
            for (i, block) in blocks.iter().enumerate() {
                // Generate label (except for entry block)
                if i > 0 || block.label.is_some() {
                    let label = block
                        .label
                        .as_ref()
                        .map(|l| l.to_string())
                        .unwrap_or_else(|| format!("bb{}", block.id.0));
                    write!(self.output, "{}:\n", label).unwrap();
                }

                // Generate statements
                for stmt in &block.stmts {
                    self.generate_statement(stmt, &func.locals)?;
                }

                // Generate terminator
                if let Some(term) = &block.terminator {
                    self.generate_terminator(term, &func.locals, blocks)?;
                } else if block.stmts.is_empty() && (i > 0 || block.label.is_some()) {
                    self.write_indent();
                    self.output.push_str("(void)0;\n");
                }
            }
        }

        self.indent -= 1;
        self.output.push_str("}\n\n");

        // Post-process optimizations on generated C output:
        // 1. Trivial goto elimination: remove goto→label pairs for sequential blocks
        // 2. Copy propagation: inline single-use temporaries
        self.eliminate_trivial_gotos();
        // Copy propagation needs MIR-level dataflow analysis to be correct.
        // Text-based approach fails on reassigned temps and cross-block values.
        // Deferred to MIR optimization pass.
        // self.propagate_copies();

        Ok(())
    }

    /// Remove `goto bbN;\nbbN:\n` pairs from the output where the goto targets
    /// the immediately following label. Preserves the label if other jumps
    /// reference it; removes both if the label has only one predecessor.
    fn eliminate_trivial_gotos(&mut self) {
        use std::collections::HashSet;
        // Find all goto targets in the output to know which labels are multi-referenced
        let mut multi_ref_labels: HashSet<String> = HashSet::new();
        let mut label_refs: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
        for line in self.output.lines() {
            let trimmed = line.trim();
            if let Some(target) = trimmed.strip_prefix("goto ") {
                if let Some(label) = target.strip_suffix(';') {
                    *label_refs.entry(label.to_string()).or_insert(0) += 1;
                }
            }
        }
        for (label, count) in &label_refs {
            if *count > 1 {
                multi_ref_labels.insert(label.clone());
            }
        }

        let old = std::mem::take(&mut self.output);
        let lines: Vec<&str> = old.lines().collect();
        let mut i = 0;
        while i < lines.len() {
            let trimmed = lines[i].trim();
            // Check for `goto bbN;` followed by `bbN:`
            if let Some(target) = trimmed.strip_prefix("goto ") {
                if let Some(label) = target.strip_suffix(';') {
                    let expected_label = format!("{}:", label);
                    if i + 1 < lines.len() && lines[i + 1].trim() == expected_label {
                        // Skip the goto; keep the label only if multi-referenced
                        if multi_ref_labels.contains(label) {
                            // Keep the label, skip the goto
                            i += 1;
                            continue;
                        } else {
                            // Skip both goto and label
                            i += 2;
                            continue;
                        }
                    }
                }
            }
            self.output.push_str(lines[i]);
            self.output.push('\n');
            i += 1;
        }
    }

    /// Copy propagation: inline single-use temporaries.
    /// When `_N = expr;` appears and `_N` is used exactly once on the next line
    /// as a simple value (not an lvalue), replace the use with expr and remove
    /// the assignment.
    ///
    /// `_1 = add(3, 4);  result = _1;` → `result = add(3, 4);`
    /// `_3 = __str0;  printf(_3, x);` → `printf(__str0, x);`
    fn propagate_copies(&mut self) {
        // Run multiple passes until no more changes (cascading copies)
        for _ in 0..3 {
            if !self.propagate_copies_pass() {
                break;
            }
        }
    }

    fn propagate_copies_pass(&mut self) -> bool {
        let old = std::mem::take(&mut self.output);
        let lines: Vec<&str> = old.lines().collect();
        let mut result = Vec::with_capacity(lines.len());
        let mut changed = false;
        let mut skip_next = false;

        for i in 0..lines.len() {
            if skip_next {
                skip_next = false;
                continue;
            }

            let trimmed = lines[i].trim();

            // Match pattern: `_N = expr;` where _N is a MIR temporary
            if let Some((temp_name, expr)) = Self::parse_temp_assign(trimmed) {
                // Check if the temp is used exactly once in the NEXT line
                if i + 1 < lines.len() {
                    let next = lines[i + 1];
                    let next_trimmed = next.trim();
                    let occurrences = Self::count_ident_occurrences(next_trimmed, &temp_name);

                    // Skip if the next line REASSIGNS the same temp (lvalue use)
                    let next_is_reassign = next_trimmed.starts_with(&format!("{} = ", temp_name))
                        || next_trimmed.starts_with(&format!("{} =", temp_name));
                    // Inline if: used exactly once on next line, not as lvalue,
                    // and expr is safe to inline (no side effects when reordered)
                    if occurrences == 1
                        && !next_is_reassign
                        && Self::is_safe_to_inline(&expr)
                    {
                        let inlined = Self::replace_ident(next, &temp_name, &expr);
                        result.push(inlined);
                        skip_next = true;
                        changed = true;
                        continue;
                    }
                }
            }

            result.push(lines[i].to_string());
        }

        self.output = result.join("\n");
        if !self.output.is_empty() {
            self.output.push('\n');
        }
        changed
    }

    /// Parse `_N = expr;` or `name = expr;` for MIR temporaries.
    /// Returns (temp_name, expr) if the line is a simple assignment to a temp.
    fn parse_temp_assign(line: &str) -> Option<(String, String)> {
        // Must start with _ and a digit (MIR temporary like _1, _23)
        let parts: Vec<&str> = line.splitn(2, " = ").collect();
        if parts.len() != 2 {
            return None;
        }
        let name = parts[0].trim();
        let expr = parts[1].trim().strip_suffix(';')?.trim();

        // Only inline MIR temporaries (_N), not user-named variables
        if !name.starts_with('_') || name.len() < 2 {
            return None;
        }
        if !name[1..].chars().next().map(|c| c.is_ascii_digit()).unwrap_or(false) {
            return None;
        }

        Some((name.to_string(), expr.to_string()))
    }

    /// Count occurrences of an identifier in a line (whole-word match).
    fn count_ident_occurrences(line: &str, ident: &str) -> usize {
        let mut count = 0;
        let ident_bytes = ident.as_bytes();
        let line_bytes = line.as_bytes();
        let ilen = ident_bytes.len();

        for pos in 0..line_bytes.len() {
            if pos + ilen > line_bytes.len() {
                break;
            }
            if &line_bytes[pos..pos + ilen] == ident_bytes {
                // Check word boundary before
                let before_ok = pos == 0
                    || !line_bytes[pos - 1].is_ascii_alphanumeric()
                        && line_bytes[pos - 1] != b'_';
                // Check word boundary after
                let after_ok = pos + ilen >= line_bytes.len()
                    || !line_bytes[pos + ilen].is_ascii_alphanumeric()
                        && line_bytes[pos + ilen] != b'_';
                if before_ok && after_ok {
                    count += 1;
                }
            }
        }
        count
    }

    /// Check if an expression is safe to inline. Only inline trivial values:
    /// variables, constants, string literals, and field access. NOT expressions
    /// with operators or function calls.
    fn is_safe_to_inline(expr: &str) -> bool {
        // Only inline: identifiers, __strN, numeric literals, field access (x.y)
        // Reject: function calls, operators, casts, complex expressions
        if expr.contains('(') || expr.contains('+') || expr.contains('-')
            || expr.contains('*') || expr.contains('/') || expr.contains('?')
            || expr.contains('{') || expr.contains('[')
        {
            return false;
        }
        true
    }

    /// Replace an identifier with an expression, respecting word boundaries.
    fn replace_ident(line: &str, ident: &str, replacement: &str) -> String {
        let mut result = String::with_capacity(line.len() + replacement.len());
        let bytes = line.as_bytes();
        let ident_bytes = ident.as_bytes();
        let ilen = ident_bytes.len();
        let mut pos = 0;

        while pos < bytes.len() {
            if pos + ilen <= bytes.len() && &bytes[pos..pos + ilen] == ident_bytes {
                let before_ok = pos == 0
                    || (!bytes[pos - 1].is_ascii_alphanumeric() && bytes[pos - 1] != b'_');
                let after_ok = pos + ilen >= bytes.len()
                    || (!bytes[pos + ilen].is_ascii_alphanumeric() && bytes[pos + ilen] != b'_');
                if before_ok && after_ok {
                    result.push_str(replacement);
                    pos += ilen;
                    continue;
                }
            }
            result.push(bytes[pos] as char);
            pos += 1;
        }
        result
    }

    fn generate_function_signature(&mut self, func: &MirFunction) -> CodegenResult<()> {
        let ret_type = self.type_to_c(&func.sig.ret);

        // Linkage
        match func.linkage {
            Linkage::Internal => self.output.push_str("static "),
            Linkage::External => {}
            Linkage::Weak => self.output.push_str("__attribute__((weak)) "),
            Linkage::LinkOnce => self.output.push_str("static inline "),
        }

        // Escape user-defined function names that conflict with C macros
        let func_name = if matches!(func.name.as_ref(), "min" | "max" | "abs") {
            format!("_{}", func.name)
        } else {
            func.name.to_string()
        };
        write!(self.output, "{} {}(", ret_type, func_name).unwrap();

        // For main(), always emit (int argc, char** argv) signature
        if func.name.as_ref() == "main" {
            self.output.push_str("int argc, char** argv)");
            return Ok(());
        }

        // Parameters
        let params: Vec<_> = func.locals.iter().filter(|l| l.is_param).collect();

        if params.is_empty() {
            self.output.push_str("void");
        } else {
            for (i, param) in params.iter().enumerate() {
                if i > 0 {
                    self.output.push_str(", ");
                }
                let name = param
                    .name
                    .as_ref()
                    .map(|n| {
                        let s = n.to_string();
                        if Self::is_c_reserved(&s) {
                            format!("_{}", s)
                        } else {
                            s
                        }
                    })
                    .unwrap_or_else(|| format!("arg{}", i));
                // Arrays need special C parameter syntax: `type name[size]`
                // Function pointers need: `ret (*name)(params)`
                if matches!(param.ty, MirType::Array(_, _)) {
                    write!(self.output, "{}", self.fmt_array_decl(&param.ty, &name)).unwrap();
                } else if let MirType::FnPtr(ref sig) = param.ty {
                    let ret = self.type_to_c(&sig.ret);
                    let fn_params: Vec<_> = sig.params.iter().map(|p| self.type_to_c(p)).collect();
                    write!(self.output, "{} (*{})({})", ret, name, fn_params.join(", ")).unwrap();
                } else {
                    write!(self.output, "{} {}", self.type_to_c(&param.ty), name).unwrap();
                }
            }
            if func.sig.is_variadic {
                self.output.push_str(", ...");
            }
        }

        self.output.push(')');

        Ok(())
    }

    fn generate_statement(&mut self, stmt: &MirStmt, locals: &[MirLocal]) -> CodegenResult<()> {
        match &stmt.kind {
            MirStmtKind::Assign { dest, value } => {
                // Skip assignments to void-typed locals (these come from
                // statement-level if/else that don't produce values).
                if let Some(local) = locals.get(dest.0 as usize) {
                    if matches!(local.ty, MirType::Void) {
                        return Ok(());
                    }
                }
                let dest_name = self.local_name(*dest, locals);
                // Check if dest is an array type and value is an aggregate
                let is_array_dest = locals
                    .get(dest.0 as usize)
                    .map(|l| matches!(l.ty, MirType::Array(_, _)))
                    .unwrap_or(false);

                if is_array_dest {
                    if let MirRValue::Aggregate { operands, .. } = value {
                        // Emit element-by-element assignment for array initialization.
                        // If elements are arrays themselves (nested arrays like [[f64; 4]; 4]),
                        // use memcpy since C doesn't allow direct array assignment.
                        for (i, op) in operands.iter().enumerate() {
                            self.write_indent();
                            let val = self.value_to_c(op, locals);
                            // Check if the operand is a local with array type
                            let is_array_elem = if let MirValue::Local(local_id) = op {
                                locals
                                    .get(local_id.0 as usize)
                                    .map(|l| matches!(l.ty, MirType::Array(_, _)))
                                    .unwrap_or(false)
                            } else {
                                false
                            };
                            if is_array_elem {
                                write!(
                                    self.output,
                                    "memcpy({}[{}], {}, sizeof({}));\n",
                                    dest_name, i, val, val
                                )
                                .unwrap();
                            } else {
                                write!(self.output, "{}[{}] = {};\n", dest_name, i, val).unwrap();
                            }
                        }
                    } else if let MirRValue::Use(src_val) = value {
                        // Array-to-array copy: use memcpy since C does not
                        // allow direct array assignment.
                        self.write_indent();
                        let src = self.value_to_c(src_val, locals);
                        write!(
                            self.output,
                            "memcpy({}, {}, sizeof({}));\n",
                            dest_name, src, dest_name
                        )
                        .unwrap();
                    } else {
                        self.write_indent();
                        let rvalue = self.rvalue_to_c(value, locals)?;
                        write!(
                            self.output,
                            "memcpy({}, &({}), sizeof({}));\n",
                            dest_name, rvalue, dest_name
                        )
                        .unwrap();
                    }
                } else if let MirRValue::FieldAccess {
                    base, field_name, ..
                } = value
                {
                    // Special case: loading from handler_data needs cast+deref
                    // handler_data is a void* that holds a pointer to the perform argument
                    if field_name.as_ref() == "handler_data" {
                        let base_str = self.value_to_c(base, locals);
                        let dest_type = locals
                            .get(dest.0 as usize)
                            .map(|l| self.type_to_c(&l.ty))
                            .unwrap_or_else(|| "int32_t".to_string());
                        self.write_indent();
                        write!(
                            self.output,
                            "{} = *({}*){}.handler_data;\n",
                            dest_name, dest_type, base_str
                        )
                        .unwrap();
                    } else {
                        self.write_indent();
                        let rvalue = self.rvalue_to_c(value, locals)?;
                        write!(self.output, "{} = {};\n", dest_name, rvalue).unwrap();
                    }
                } else if let MirRValue::Aggregate {
                    kind: AggregateKind::Struct(_),
                    operands,
                } = value
                {
                    // Check if any operand is an array — if so, use memcpy
                    // because C compound literals can't initialize array fields
                    // from array variables.
                    let has_array_operand = operands.iter().any(|op| {
                        if let MirValue::Local(id) = op {
                            locals
                                .get(id.0 as usize)
                                .map(|l| matches!(l.ty, MirType::Array(_, _)))
                                .unwrap_or(false)
                        } else {
                            false
                        }
                    });
                    if has_array_operand && operands.len() == 1 {
                        // Single array field → memcpy the array into the struct
                        let src = self.value_to_c(&operands[0], locals);
                        self.write_indent();
                        write!(
                            self.output,
                            "memcpy(&{}, &{}, sizeof({}));\n",
                            dest_name, src, dest_name
                        )
                        .unwrap();
                    } else {
                        self.write_indent();
                        let rvalue = self.rvalue_to_c(value, locals)?;
                        write!(self.output, "{} = {};\n", dest_name, rvalue).unwrap();
                    }
                } else if let MirRValue::Use(src_val) = value {
                    // Detect type mismatch between dest and src for Use assignments.
                    // When one is a struct and the other is a primitive (or different
                    // struct), use memcpy to avoid C type errors.
                    let dest_ty = locals.get(dest.0 as usize).map(|l| &l.ty);
                    let src_ty = match src_val {
                        MirValue::Local(id) => locals.get(id.0 as usize).map(|l| &l.ty),
                        _ => None,
                    };
                    let needs_cast = if let (Some(dt), Some(st)) = (dest_ty, src_ty) {
                        let dt_is_struct = matches!(dt,
                            MirType::Struct(_) | MirType::Vec(_) | MirType::Map(_, _) | MirType::Tuple(_));
                        let st_is_struct = matches!(st,
                            MirType::Struct(_) | MirType::Vec(_) | MirType::Map(_, _) | MirType::Tuple(_));
                        (dt_is_struct || st_is_struct) && dt != st
                    } else {
                        false
                    };
                    if needs_cast {
                        let src_str = self.value_to_c(src_val, locals);
                        self.write_indent();
                        write!(
                            self.output,
                            "memcpy(&{}, &{}, sizeof({}) < sizeof({}) ? sizeof({}) : sizeof({}));\n",
                            dest_name, src_str, dest_name, src_str, dest_name, src_str
                        ).unwrap();
                    } else {
                        self.emit_typed_assign(dest_name.clone(), value, *dest, locals)?;
                    }
                } else {
                    self.emit_typed_assign(dest_name.clone(), value, *dest, locals)?;
                }
            }
            MirStmtKind::DerefAssign { ptr, value } => {
                let ptr_name = self.local_name(*ptr, locals);
                self.write_indent();
                let rvalue = self.rvalue_to_c(value, locals)?;
                write!(self.output, "*{} = {};\n", ptr_name, rvalue).unwrap();
            }
            MirStmtKind::FieldDerefAssign {
                ptr,
                field_name,
                value,
            } => {
                let ptr_name = self.local_name(*ptr, locals);
                self.write_indent();
                let rvalue = self.rvalue_to_c(value, locals)?;
                write!(self.output, "{}->{} = {};\n", ptr_name, field_name, rvalue).unwrap();
            }
            MirStmtKind::FieldAssign {
                base,
                field_name,
                value,
            } => {
                let base_name = self.local_name(*base, locals);
                self.write_indent();
                let rvalue = self.rvalue_to_c(value, locals)?;
                write!(self.output, "{}.{} = {};\n", base_name, field_name, rvalue).unwrap();
            }
            MirStmtKind::StorageLive(_) | MirStmtKind::StorageDead(_) => {
                // No-op in C
            }
            MirStmtKind::Nop => {
                self.writeln(";");
            }
        }

        Ok(())
    }

    /// Resolve a block ID to its goto label. If the block has a named label,
    /// use that; otherwise fall back to `bb{id}`.
    fn block_label(&self, id: &BlockId, blocks: &[MirBlock]) -> String {
        blocks
            .iter()
            .find(|b| b.id == *id)
            .and_then(|b| b.label.as_ref())
            .map(|l| l.to_string())
            .unwrap_or_else(|| format!("bb{}", id.0))
    }

    fn generate_terminator(
        &mut self,
        term: &MirTerminator,
        locals: &[MirLocal],
        blocks: &[MirBlock],
    ) -> CodegenResult<()> {
        match term {
            MirTerminator::Goto(target) => {
                self.write_indent();
                write!(self.output, "goto {};\n", self.block_label(target, blocks)).unwrap();
            }
            MirTerminator::If {
                cond,
                then_block,
                else_block,
            } => {
                self.write_indent();
                let cond_str = self.value_to_c(cond, locals);
                write!(
                    self.output,
                    "if ({}) goto {}; else goto {};\n",
                    cond_str,
                    self.block_label(then_block, blocks),
                    self.block_label(else_block, blocks)
                )
                .unwrap();
            }
            MirTerminator::Switch {
                value,
                targets,
                default,
            } => {
                self.write_indent();
                let val_str = self.value_to_c(value, locals);
                write!(self.output, "switch ({}) {{\n", val_str).unwrap();
                self.indent += 1;
                for (const_val, target) in targets {
                    self.write_indent();
                    write!(
                        self.output,
                        "case {}: goto {};\n",
                        self.const_to_c(const_val),
                        self.block_label(target, blocks)
                    )
                    .unwrap();
                }
                self.write_indent();
                write!(
                    self.output,
                    "default: goto {};\n",
                    self.block_label(default, blocks)
                )
                .unwrap();
                self.indent -= 1;
                self.writeln("}");
            }
            MirTerminator::Call {
                func,
                args,
                dest,
                target,
                ..
            } => {
                self.write_indent();
                let func_str = self.value_to_c(func, locals);

                // Handle vtable dispatch: __vtable_dispatch_TraitName_methodName_idx
                // args[0] = data pointer (void*), args[1..] = method arguments
                // The vtable pointer was stored in a local by the lowerer
                if func_str.starts_with("__vtable_dispatch_") {
                    let suffix = func_str.strip_prefix("__vtable_dispatch_").unwrap();
                    let parts: Vec<&str> = suffix.rsplitn(2, '_').collect();
                    if parts.len() == 2 {
                        let _method_idx: usize = parts[0].parse().unwrap_or(0);
                        let trait_and_method = parts[1];
                        let method_name = trait_and_method
                            .rsplit('_')
                            .next()
                            .unwrap_or(trait_and_method);

                        let args_str: Vec<_> =
                            args.iter().map(|a| self.value_to_c(a, locals)).collect();

                        // Find the vtable local — it's the local right after the data pointer
                        // that was assigned from a FieldAccess with field_name "vtable"
                        // For now, look for a local with void* type near the data pointer
                        let vtable_local_name = if args.len() > 0 {
                            if let MirValue::Local(data_id) = &args[0] {
                                // Vtable local is typically data_id + 1 (allocated together)
                                let vtable_id = LocalId(data_id.0 + 1);
                                self.local_name(vtable_id, locals)
                            } else {
                                "/* vtable */".to_string()
                            }
                        } else {
                            "/* vtable */".to_string()
                        };

                        // Generate: dest = ((TraitName_vtable*)vtable_ptr)->method(data_ptr, args...)
                        if let Some(dest_local) = dest {
                            let dest_name = self.local_name(*dest_local, locals);
                            write!(
                                self.output,
                                "{} = ((void*){} != NULL) ? ",
                                dest_name, vtable_local_name
                            )
                            .unwrap();
                            // Cast vtable to correct type and call through function pointer
                            let trait_name =
                                trait_and_method.rsplitn(2, '_').last().unwrap_or("Unknown");
                            write!(
                                self.output,
                                "(({}_vtable*){})->{}({}) : 0;\n",
                                trait_name,
                                vtable_local_name,
                                method_name,
                                args_str.join(", ")
                            )
                            .unwrap();
                        }

                        if let Some(target) = target {
                            self.write_indent();
                            write!(self.output, "goto bb{};\n", target.0).unwrap();
                        }

                        return Ok(());
                    }
                }

                // Map intrinsic_* calls to C standard library functions.
                let func_str = match func_str.as_str() {
                    "intrinsic_trunc" => "trunc".to_string(),
                    "intrinsic_exp2" => "exp2".to_string(),
                    "intrinsic_asin" => "asin".to_string(),
                    "intrinsic_acos" => "acos".to_string(),
                    "intrinsic_atan" => "atan".to_string(),
                    "intrinsic_atan2" => "atan2".to_string(),
                    "intrinsic_sinh" => "sinh".to_string(),
                    "intrinsic_cosh" => "cosh".to_string(),
                    "intrinsic_tanh" => "tanh".to_string(),
                    "intrinsic_cbrt" => "cbrt".to_string(),
                    "intrinsic_log" => "log".to_string(),
                    "intrinsic_log2" => "log2".to_string(),
                    "intrinsic_log10" => "log10".to_string(),
                    "intrinsic_fabs" => "fabs".to_string(),
                    "intrinsic_sqrt" => "sqrt".to_string(),
                    "intrinsic_ceil" => "ceil".to_string(),
                    "intrinsic_floor" => "floor".to_string(),
                    "intrinsic_round" => "round".to_string(),
                    "intrinsic_pow" => "pow".to_string(),
                    "intrinsic_fmin" => "fmin".to_string(),
                    "intrinsic_fmax" => "fmax".to_string(),
                    "intrinsic_copysign" => "copysign".to_string(),
                    "intrinsic_hypot" => "hypot".to_string(),
                    // Constructor calls → runtime functions
                    "HashMap_new" => "quanta_hmap_new_str_f64".to_string(),
                    "HashSet_new" => "quanta_hset_new".to_string(),
                    "VecDeque_new" => "quanta_vdeque_new".to_string(),
                    // println/print without ! — keep name, handle below
                    _ => func_str,
                };

                // Future: type-dispatch for map builtins (i32 vs str→f64) based on
                // argument types. Currently map_get/map_insert default to str→f64.

                // println/print (without !) → printf with QuantaString handling
                if matches!(func_str.as_str(), "println" | "print" | "eprintln" | "eprint") {
                    let is_err = matches!(func_str.as_str(), "eprintln" | "eprint");
                    let newline = matches!(func_str.as_str(), "println" | "eprintln");
                    let arg_str = if !args.is_empty() {
                        let a = self.value_to_c(&args[0], locals);
                        // Check if the arg is a QuantaString — use .ptr
                        let is_qstr = if let MirValue::Local(id) = &args[0] {
                            locals
                                .get(id.0 as usize)
                                .map(|l| {
                                    matches!(l.ty, MirType::Struct(ref n) if n.as_ref() == "QuantaString")
                                })
                                .unwrap_or(false)
                        } else {
                            false
                        };
                        if is_qstr {
                            format!("{}.ptr", a)
                        } else {
                            a
                        }
                    } else {
                        "\"\"".to_string()
                    };
                    let nl = if newline { "\\n" } else { "" };
                    if is_err {
                        write!(self.output, "fprintf(stderr, \"%s{}\", {});\n", nl, arg_str)
                            .unwrap();
                    } else {
                        write!(self.output, "printf(\"%s{}\", {});\n", nl, arg_str).unwrap();
                    }
                    if let Some(target_block) = target {
                        self.write_indent();
                        write!(
                            self.output,
                            "goto {};\n",
                            self.block_label(target_block, blocks)
                        )
                        .unwrap();
                    }
                    return Ok(());
                }

                // String::new() → quanta_string_new("")
                if func_str == "String_new" && args.is_empty() {
                    if let Some(dest_local) = dest {
                        let dest_name = self.local_name(*dest_local, locals);
                        write!(self.output, "{} = quanta_string_new(\"\");\n", dest_name).unwrap();
                    }
                    if let Some(target) = target {
                        self.write_indent();
                        write!(self.output, "goto bb{};\n", target.0).unwrap();
                    }
                    return Ok(());
                }

                // clone(x) → direct copy (x is value-typed in C)
                if func_str == "clone" && args.len() == 1 {
                    let arg_str = self.value_to_c(&args[0], locals);
                    if let Some(dest_local) = dest {
                        let dest_name = self.local_name(*dest_local, locals);
                        write!(self.output, "{} = {};\n", dest_name, arg_str).unwrap();
                    }
                    if let Some(target) = target {
                        self.write_indent();
                        write!(self.output, "goto bb{};\n", target.0).unwrap();
                    }
                    return Ok(());
                }

                // Runtime None/Some: emit zero-initialized Option struct.
                if func_str == "None" && args.is_empty() {
                    if let Some(dest_local) = dest {
                        let dest_name = self.local_name(*dest_local, locals);
                        write!(
                            self.output,
                            "{}.has_value = false;\n",
                            dest_name,
                        )
                        .unwrap();
                    }
                    if let Some(target) = target {
                        self.write_indent();
                        write!(self.output, "goto bb{};\n", target.0).unwrap();
                    }
                    return Ok(());
                }

                // Unit struct constructors: Stdin, Stdout, Stderr — emit zero-init.
                const UNIT_STRUCTS: &[(&str, &str)] = &[
                    ("Stdin", "io_Stdin"),
                    ("Stdout", "io_Stdout"),
                    ("Stderr", "io_Stderr"),
                ];
                if args.is_empty() {
                    if let Some((_, c_name)) = UNIT_STRUCTS.iter().find(|(ql, _)| func_str == *ql) {
                        if let Some(dest_local) = dest {
                            let dest_name = self.local_name(*dest_local, locals);
                            write!(
                                self.output,
                                "{} = ({}){{ 0 }};\n",
                                dest_name, c_name,
                            )
                            .unwrap();
                        }
                        if let Some(target) = target {
                            self.write_indent();
                            write!(self.output, "goto bb{};\n", target.0).unwrap();
                        }
                        return Ok(());
                    }
                }

                // Special-case setjmp: when passed a QuantaHandler local,
                // emit `setjmp(handler.env)` instead of `setjmp(handler)`.
                //
                // Special-case printf: bool arguments used with %s must be
                // converted to "true"/"false" strings via a ternary.
                // Look up target function's parameter types for auto-ref.
                let target_params = self.fn_params.get(func_str.as_str()).cloned();
                let is_printf = func_str == "printf";
                let args_str: Vec<_> = args
                    .iter()
                    .enumerate()
                    .map(|(i, a)| {
                        let s = self.value_to_c(a, locals);
                        // Auto-coerce arguments based on parameter types.
                        if let Some(ref params) = target_params {
                            if let Some(param_ty) = params.get(i) {
                                let arg_is_string = if let MirValue::Local(id) = a {
                                    locals.get(id.0 as usize)
                                        .map(|l| matches!(l.ty, MirType::Struct(ref n) if n.as_ref() == "QuantaString"))
                                        .unwrap_or(false)
                                } else { false };

                                // QuantaString → &QuantaString (auto-ref for &String params)
                                if let MirType::Ptr(ref inner) = param_ty {
                                    if let MirType::Struct(ref pname) = inner.as_ref() {
                                        if pname.as_ref() == "QuantaString" && arg_is_string {
                                            return format!("&{}", s);
                                        }
                                    }
                                    // QuantaString → const char* (extract .ptr)
                                    if let MirType::Int(IntSize::I8, _) = inner.as_ref() {
                                        if arg_is_string {
                                            return format!("{}.ptr", s);
                                        }
                                    }
                                }
                            }
                        }
                        if func_str == "setjmp" {
                            if let MirValue::Local(id) = a {
                                if let Some(local) = locals.get(id.0 as usize) {
                                    if let MirType::Struct(ref name) = local.ty {
                                        if name.as_ref() == "QuantaHandler" {
                                            return format!("{}.env", s);
                                        }
                                    }
                                }
                            }
                        }
                        // For printf, convert bool args to "true"/"false" strings
                        if is_printf {
                            if let MirValue::Local(id) = a {
                                if let Some(local) = locals.get(id.0 as usize) {
                                    if matches!(local.ty, MirType::Bool) {
                                        return format!("{} ? \"true\" : \"false\"", s);
                                    }
                                }
                            }
                            if let MirValue::Const(MirConst::Bool(_)) = a {
                                return format!("{} ? \"true\" : \"false\"", s);
                            }
                        }
                        s
                    })
                    .collect();

                if let Some(dest_local) = dest {
                    // Skip assignment for void-returning functions.
                    // Check both the dest type and known void functions
                    // (assert returns void but MIR may type the dest as i32).
                    let dest_is_void = locals
                        .get(dest_local.0 as usize)
                        .map(|l| matches!(l.ty, MirType::Void))
                        .unwrap_or(false);
                    let fn_returns_void = matches!(
                        func_str.as_str(),
                        "assert" | "free" | "exit" | "abort" | "process_exit"
                    );
                    if dest_is_void || fn_returns_void {
                        write!(self.output, "{}({});\n", func_str, args_str.join(", ")).unwrap();
                    } else {
                        let dest_name = self.local_name(*dest_local, locals);
                        write!(
                            self.output,
                            "{} = {}({});\n",
                            dest_name,
                            func_str,
                            args_str.join(", ")
                        )
                        .unwrap();
                    }
                } else {
                    write!(self.output, "{}({});\n", func_str, args_str.join(", ")).unwrap();
                }

                if let Some(target_block) = target {
                    self.write_indent();
                    write!(
                        self.output,
                        "goto {};\n",
                        self.block_label(target_block, blocks)
                    )
                    .unwrap();
                }
            }
            MirTerminator::Return(value) => {
                // Flush stdout before returning to ensure all output is visible,
                // especially when running as a child process on Windows.
                self.write_indent();
                self.output.push_str("fflush(stdout);\n");
                self.write_indent();
                if let Some(val) = value {
                    let val_str = self.value_to_c(val, locals);
                    // Check for type mismatch between return value and function
                    // return type.  Use memcpy cast for struct/primitive mismatches.
                    let val_ty = match val {
                        MirValue::Local(id) => locals.get(id.0 as usize).map(|l| &l.ty),
                        _ => None,
                    };
                    let ret_is_struct = matches!(&self.current_ret_ty,
                        MirType::Struct(_) | MirType::Vec(_) | MirType::Map(_, _) | MirType::Tuple(_));
                    let val_is_struct = val_ty.map(|t| matches!(t,
                        MirType::Struct(_) | MirType::Vec(_) | MirType::Map(_, _) | MirType::Tuple(_)))
                        .unwrap_or(false);
                    let types_mismatch = val_ty.map(|t| t != &self.current_ret_ty).unwrap_or(false);
                    if types_mismatch && (ret_is_struct || val_is_struct)
                        && self.current_ret_ty != MirType::Void
                    {
                        let ret_c = self.type_to_c(&self.current_ret_ty);
                        write!(self.output, "{{ {} _cast; memset(&_cast, 0, sizeof(_cast)); memcpy(&_cast, &{}, sizeof({}) < sizeof(_cast) ? sizeof({}) : sizeof(_cast)); return _cast; }}\n",
                            ret_c, val_str, val_str, val_str).unwrap();
                    } else {
                        write!(self.output, "return {};\n", val_str).unwrap();
                    }
                } else {
                    self.output.push_str("return;\n");
                }
            }
            MirTerminator::Unreachable => {
                self.writeln("__builtin_unreachable();");
            }
            MirTerminator::Abort => {
                self.writeln("abort();");
            }
            MirTerminator::Assert {
                cond,
                expected,
                msg,
                target,
                ..
            } => {
                self.write_indent();
                let cond_str = self.value_to_c(cond, locals);
                if *expected {
                    write!(self.output,
                        "if (!{}) {{ fprintf(stderr, \"Assertion failed: %s\\n\", \"{}\"); abort(); }}\n",
                        cond_str, msg
                    ).unwrap();
                } else {
                    write!(self.output,
                        "if ({}) {{ fprintf(stderr, \"Assertion failed: %s\\n\", \"{}\"); abort(); }}\n",
                        cond_str, msg
                    ).unwrap();
                }
                self.write_indent();
                write!(self.output, "goto {};\n", self.block_label(target, blocks)).unwrap();
            }
            MirTerminator::Drop {
                place: _, target, ..
            } => {
                // No explicit drop in C
                self.write_indent();
                write!(self.output, "goto {};\n", self.block_label(target, blocks)).unwrap();
            }
            MirTerminator::Resume => {
                self.writeln("// resume unwinding");
            }
        }

        Ok(())
    }

    // =========================================================================
    // TYPE AND VALUE CONVERSION
    // =========================================================================

    fn type_to_c(&self, ty: &MirType) -> String {
        match ty {
            MirType::Void => "void".to_string(),
            MirType::Bool => "bool".to_string(),
            MirType::Int(size, signed) => {
                let prefix = if *signed { "" } else { "u" };
                match size {
                    IntSize::I8 => format!("{}int8_t", prefix),
                    IntSize::I16 => format!("{}int16_t", prefix),
                    IntSize::I32 => format!("{}int32_t", prefix),
                    IntSize::I64 => format!("{}int64_t", prefix),
                    IntSize::I128 => format!("__int128_t"), // GCC extension
                    IntSize::ISize => format!("{}intptr_t", prefix),
                }
            }
            MirType::Float(size) => match size {
                FloatSize::F32 => "float".to_string(),
                FloatSize::F64 => "double".to_string(),
            },
            MirType::Ptr(inner) => {
                format!("{}*", self.type_to_c(inner))
            }
            MirType::Array(elem, len) => {
                // C doesn't allow array types directly in most contexts
                // This is handled specially in declarations
                format!("{}[{}]", self.type_to_c(elem), len)
            }
            MirType::Slice(elem) => {
                // Slice as fat pointer struct
                format!("struct {{ {}* ptr; size_t len; }}", self.type_to_c(elem))
            }
            MirType::Struct(name) => {
                // Resolve unresolved Self to the enclosing function's impl type.
                // E.g., in function "Point_new", Self → Point.
                if name.as_ref() == "Self" {
                    if let Some(ref fn_name) = self.current_fn_name {
                        if let Some(idx) = fn_name.rfind('_') {
                            return fn_name[..idx].to_string();
                        }
                    }
                }
                name.to_string()
            }
            MirType::FnPtr(sig) => {
                let ret = self.type_to_c(&sig.ret);
                let params: Vec<_> = sig.params.iter().map(|p| self.type_to_c(p)).collect();
                format!("{} (*)({})", ret, params.join(", "))
            }
            MirType::Never => "void".to_string(), // Never returns
            MirType::Vector(elem, lanes) => {
                // Use GCC/Clang vector extension
                let elem_ty = self.type_to_c(elem);
                format!("{} __attribute__((vector_size({})))", elem_ty, lanes * 4)
            }
            MirType::Texture2D(_) => "void*".to_string(), // Opaque GPU handle
            MirType::Sampler => "void*".to_string(),      // Opaque GPU handle
            MirType::SampledImage(_) => "void*".to_string(), // Opaque GPU handle
            MirType::TraitObject(name) => format!("dyn_{}", name), // vtable struct
            MirType::Vec(_) => "QuantaVecHandle".to_string(),
            MirType::Map(_, _) => "QuantaStrF64MapHandle".to_string(),
            MirType::Tuple(ref elems) => {
                if elems.is_empty() {
                    "void".to_string()
                } else {
                    MirType::tuple_type_name(elems).to_string()
                }
            }
        }
    }

    /// Recursively extract the base (non-array) element type and all dimension
    /// sizes from a (possibly nested) `MirType::Array`.
    ///
    /// For `Array(Array(f64, 3), 3)` this returns `(f64, [3, 3])`.
    /// The dimensions are in *outer-to-inner* order so they can be appended
    /// directly after the variable name: `double name[3][3]`.
    fn array_base_and_dims<'a>(&self, ty: &'a MirType) -> (&'a MirType, Vec<u64>) {
        let mut current = ty;
        let mut dims = Vec::new();
        while let MirType::Array(ref elem, len) = *current {
            dims.push(len);
            current = elem;
        }
        (current, dims)
    }

    /// Format an array declaration: `base_type name[d1][d2]...`.
    /// Returns the string assuming the caller will append `;\n` or similar.
    fn fmt_array_decl(&self, ty: &MirType, name: &str) -> String {
        let (base, dims) = self.array_base_and_dims(ty);
        let base_c = self.type_to_c(base);
        let dim_str: String = dims.iter().map(|d| format!("[{}]", d)).collect();
        format!("{} {}{}", base_c, name, dim_str)
    }

    fn value_to_c(&self, value: &MirValue, locals: &[MirLocal]) -> String {
        match value {
            MirValue::Local(id) => self.local_name(*id, locals),
            MirValue::Const(c) => self.const_to_c(c),
            MirValue::Global(name) => {
                match name.as_ref() {
                    "None" => return "((Option){ .has_value = false })".to_string(),
                    "Stdin" => return "((io_Stdin){ 0 })".to_string(),
                    "Stdout" => return "((io_Stdout){ 0 })".to_string(),
                    "Stderr" => return "((io_Stderr){ 0 })".to_string(),
                    _ => name.to_string(),
                }
            }
            MirValue::Function(name) => {
                // Map well-known value-position names to C struct literals.
                // These appear when the MIR uses a function name as a value
                // (e.g., `return None;`, `current = None;`).
                match name.as_ref() {
                    "None" => return "((Option){ .has_value = false })".to_string(),
                    "Stdin" => return "((io_Stdin){ 0 })".to_string(),
                    "Stdout" => return "((io_Stdout){ 0 })".to_string(),
                    "Stderr" => return "((io_Stderr){ 0 })".to_string(),
                    _ => {}
                }
                // Escape user-defined function names that conflict with C
                // reserved words or macros — but never escape runtime helpers
                // (quanta_*), standard C math/stdlib functions, or other
                // known builtins.  These are already correct as-is.
                if Self::is_runtime_or_builtin_fn(name) {
                    name.to_string()
                } else if Self::is_c_reserved(name) {
                    format!("_{}", name)
                } else {
                    name.to_string()
                }
            }
        }
    }

    fn const_to_c(&self, c: &MirConst) -> String {
        match c {
            MirConst::Bool(b) => if *b { "true" } else { "false" }.to_string(),
            MirConst::Int(v, ty) => match ty {
                MirType::Int(IntSize::I64, _) => format!("{}LL", v),
                MirType::Int(IntSize::I128, _) => format!("((__int128){})", v),
                _ => v.to_string(),
            },
            MirConst::Uint(v, ty) => match ty {
                MirType::Int(IntSize::I64, _) => format!("{}ULL", v),
                _ => format!("{}U", v),
            },
            MirConst::Float(v, ty) => {
                // Ensure float constants always have a decimal point in C
                // so that `1.0 / 3.0` doesn't become `1 / 3` (integer division).
                let s = format!("{}", v);
                let needs_dot = !s.contains('.') && !s.contains('e') && !s.contains('E');
                match ty {
                    MirType::Float(FloatSize::F32) => {
                        if needs_dot {
                            format!("{}.0f", s)
                        } else {
                            format!("{}f", s)
                        }
                    }
                    _ => {
                        if needs_dot {
                            format!("{}.0", s)
                        } else {
                            s
                        }
                    }
                }
            }
            MirConst::Str(idx) => format!("__str{}", idx),
            MirConst::ByteStr(bytes) => {
                let escaped: String = bytes.iter().map(|b| format!("\\x{:02x}", b)).collect();
                format!("\"{}\"", escaped)
            }
            MirConst::Null(_) => "NULL".to_string(),
            MirConst::Unit => "((void)0)".to_string(),
            MirConst::Zeroed(ty) => {
                // Zero initializer
                format!("(({}){{}})", self.type_to_c(ty))
            }
            MirConst::Undef(_) => "/* undef */ 0".to_string(),
            MirConst::Struct(name, fields) => {
                let field_strs: Vec<String> = fields.iter().map(|f| self.const_to_c(f)).collect();
                format!("({}){{ {} }}", name, field_strs.join(", "))
            }
        }
    }

    fn rvalue_to_c(&self, rvalue: &MirRValue, locals: &[MirLocal]) -> CodegenResult<String> {
        Ok(match rvalue {
            MirRValue::Use(value) => self.value_to_c(value, locals),
            MirRValue::BinaryOp { op, left, right } => {
                let l = self.value_to_c(left, locals);
                let r = self.value_to_c(right, locals);
                if *op == BinOp::Pow {
                    return Ok(format!("pow({}, {})", l, r));
                }
                // String comparison: use strcmp when either operand is QuantaString
                if *op == BinOp::Eq || *op == BinOp::Ne {
                    let is_string = |v: &MirValue| -> bool {
                        match v {
                            MirValue::Local(id) => locals
                                .get(id.0 as usize)
                                .map(|loc| matches!(loc.ty, MirType::Struct(ref n) if n.as_ref() == "QuantaString"))
                                .unwrap_or(false),
                            _ => false,
                        }
                    };
                    if is_string(left) && is_string(right) {
                        let cmp = format!("strcmp({}.ptr, {}.ptr)", l, r);
                        return Ok(if *op == BinOp::Eq {
                            format!("({} == 0)", cmp)
                        } else {
                            format!("({} != 0)", cmp)
                        });
                    }
                }
                let op_str = self.binop_to_c(*op);
                format!("({} {} {})", l, op_str, r)
            }
            MirRValue::UnaryOp { op, operand } => {
                let v = self.value_to_c(operand, locals);
                let op_str = match op {
                    UnaryOp::Not => "!",
                    UnaryOp::Neg => "-",
                };
                format!("({}{})", op_str, v)
            }
            MirRValue::Ref { is_mut: _, place } => {
                let local_name = self.local_name(place.local, locals);
                format!("&{}", local_name)
            }
            MirRValue::AddressOf { is_mut: _, place } => {
                let local_name = self.local_name(place.local, locals);
                format!("&{}", local_name)
            }
            MirRValue::Cast { kind: _, value, ty } => {
                let v = self.value_to_c(value, locals);
                let t = self.type_to_c(ty);
                format!("(({}){})", t, v)
            }
            MirRValue::Aggregate { kind, operands } => {
                let vals: Vec<_> = operands
                    .iter()
                    .map(|o| self.value_to_c(o, locals))
                    .collect();
                match kind {
                    AggregateKind::Array(_) => format!("{{ {} }}", vals.join(", ")),
                    AggregateKind::Tuple => {
                        // Determine element types to build the typedef name.
                        let elem_tys: Vec<MirType> = operands
                            .iter()
                            .map(|op| match op {
                                MirValue::Local(id) => locals
                                    .get(id.0 as usize)
                                    .map(|l| l.ty.clone())
                                    .unwrap_or(MirType::i32()),
                                MirValue::Const(c) => match c {
                                    MirConst::Bool(_) => MirType::Bool,
                                    MirConst::Int(_, ty) => ty.clone(),
                                    MirConst::Uint(_, ty) => ty.clone(),
                                    MirConst::Float(_, ty) => ty.clone(),
                                    _ => MirType::i32(),
                                },
                                _ => MirType::i32(),
                            })
                            .collect();
                        if elem_tys.is_empty() {
                            format!("{{ {} }}", vals.join(", "))
                        } else {
                            let name = MirType::tuple_type_name(&elem_tys);
                            format!("({}){{ {} }}", name, vals.join(", "))
                        }
                    }
                    AggregateKind::Struct(name) => {
                        if name.starts_with("dyn_") && vals.len() == 2 {
                            // Fat pointer: { data = (void*)&obj, vtable = &vtable_instance }
                            format!(
                                "({}){{ .data = {}, .vtable = ({}_vtable*)&{} }}",
                                name,
                                vals[0],
                                name.strip_prefix("dyn_").unwrap_or("Unknown"),
                                vals[1]
                            )
                        } else {
                            format!("({}){{ {} }}", name, vals.join(", "))
                        }
                    }
                    AggregateKind::Variant(name, disc, variant_name) => {
                        if vals.is_empty() {
                            // Unit variant — no data fields
                            format!("({}){{ .tag = {} }}", name, disc)
                        } else {
                            format!(
                                "({}){{ .tag = {}, .data = {{ .{} = {{ {} }} }} }}",
                                name,
                                disc,
                                variant_name,
                                vals.join(", ")
                            )
                        }
                    }
                    AggregateKind::Closure(_) => format!("{{ {} }}", vals.join(", ")),
                }
            }
            MirRValue::Repeat { value, count } => {
                let v = self.value_to_c(value, locals);
                // C doesn't have array repeat, use designated initializers
                format!("{{ [0 ... {}] = {} }}", count - 1, v)
            }
            MirRValue::Discriminant(place) => {
                let local_name = self.local_name(place.local, locals);
                format!("{}.tag", local_name)
            }
            MirRValue::Len(place) => {
                let local_name = self.local_name(place.local, locals);
                format!("{}.len", local_name)
            }
            MirRValue::NullaryOp(op, ty) => match op {
                NullaryOp::SizeOf => format!("sizeof({})", self.type_to_c(ty)),
                NullaryOp::AlignOf => format!("_Alignof({})", self.type_to_c(ty)),
            },
            MirRValue::FieldAccess {
                base, field_name, ..
            } => {
                let base_str = self.value_to_c(base, locals);
                // If the base value is a pointer type, use -> instead of .
                let is_ptr = match base {
                    MirValue::Local(id) => locals
                        .get(id.0 as usize)
                        .map(|l| l.ty.is_pointer())
                        .unwrap_or(false),
                    _ => false,
                };
                if is_ptr {
                    format!("{}->{}", base_str, field_name)
                } else {
                    format!("{}.{}", base_str, field_name)
                }
            }
            MirRValue::VariantField {
                base,
                variant_name,
                field_index,
                ..
            } => {
                let base_str = self.value_to_c(base, locals);
                format!("{}.data.{}.f{}", base_str, variant_name, field_index)
            }
            MirRValue::IndexAccess { base, index, elem_ty } => {
                let base_str = self.value_to_c(base, locals);
                let index_str = self.value_to_c(index, locals);
                // For Vec types, use typed runtime getter instead of raw subscript.
                let base_is_vec = match base {
                    MirValue::Local(id) => locals
                        .get(id.0 as usize)
                        .map(|l| matches!(l.ty, MirType::Vec(_)))
                        .unwrap_or(false),
                    _ => false,
                };
                if base_is_vec {
                    let suffix = match elem_ty {
                        MirType::Float(_) => "f64",
                        MirType::Int(IntSize::I64, _) => "i64",
                        MirType::Struct(n) if n.as_ref() == "QuantaString" => "str",
                        _ => "i32",
                    };
                    format!("quanta_hvec_get_{}({}, {})", suffix, base_str, index_str)
                } else {
                    // QuantaString indexing: access .ptr[index] for byte access
                    let base_is_string = match base {
                        MirValue::Local(id) => locals
                            .get(id.0 as usize)
                            .map(|l| matches!(l.ty, MirType::Struct(ref n) if n.as_ref() == "QuantaString"))
                            .unwrap_or(false),
                        _ => false,
                    };
                    if base_is_string {
                        format!("((uint8_t*){}.ptr)[{}]", base_str, index_str)
                    } else {
                        format!("{}[{}]", base_str, index_str)
                    }
                }
            }
            MirRValue::Deref { ptr, .. } => {
                let ptr_str = self.value_to_c(ptr, locals);
                format!("(*{})", ptr_str)
            }
            MirRValue::TextureSample { .. } => {
                // GPU-only operation; emit a zero placeholder in C
                "/* texture_sample: GPU-only */ 0".to_string()
            }
        })
    }

    /// Emit an assignment, using memcpy for struct/primitive type mismatches.
    fn emit_typed_assign(
        &mut self,
        dest_name: String,
        value: &MirRValue,
        dest_id: LocalId,
        locals: &[MirLocal],
    ) -> CodegenResult<()> {
        // Check for type mismatch that needs memcpy
        let dest_ty = locals.get(dest_id.0 as usize).map(|l| &l.ty);
        let src_ty = if let MirRValue::Use(MirValue::Local(id)) = value {
            locals.get(id.0 as usize).map(|l| &l.ty)
        } else if let MirRValue::FieldAccess { field_ty, .. } = value {
            Some(field_ty)
        } else {
            None
        };
        let needs_cast = if let (Some(dt), Some(st)) = (dest_ty, src_ty) {
            let is_struct = |t: &MirType| matches!(t,
                MirType::Struct(_) | MirType::Vec(_) | MirType::Map(_, _) | MirType::Tuple(_));
            (is_struct(dt) || is_struct(st)) && dt != st
        } else {
            false
        };
        if needs_cast {
            if let MirRValue::Use(src_val) = value {
                let src_str = self.value_to_c(src_val, locals);
                self.write_indent();
                write!(
                    self.output,
                    "memcpy(&{}, &{}, sizeof({}) < sizeof({}) ? sizeof({}) : sizeof({}));\n",
                    dest_name, src_str, dest_name, src_str, dest_name, src_str
                ).unwrap();
            } else {
                // For non-Use rvalues, fall back to direct assignment
                // (the type mismatch is from FieldAccess or other non-local sources)
                let rvalue = self.rvalue_to_c(value, locals)?;
                self.write_indent();
                write!(self.output, "{} = {};\n", dest_name, rvalue).unwrap();
            }
        } else {
            self.write_indent();
            let rvalue = self.rvalue_to_c(value, locals)?;
            write!(self.output, "{} = {};\n", dest_name, rvalue).unwrap();
        }
        Ok(())
    }

    fn binop_to_c(&self, op: BinOp) -> &'static str {
        match op {
            BinOp::Add | BinOp::AddChecked | BinOp::AddWrapping | BinOp::AddSaturating => "+",
            BinOp::Sub | BinOp::SubChecked | BinOp::SubWrapping | BinOp::SubSaturating => "-",
            BinOp::Mul | BinOp::MulChecked | BinOp::MulWrapping => "*",
            BinOp::Div => "/",
            BinOp::Rem => "%",
            BinOp::BitAnd => "&",
            BinOp::BitOr => "|",
            BinOp::BitXor => "^",
            BinOp::Shl => "<<",
            BinOp::Shr => ">>",
            BinOp::Eq => "==",
            BinOp::Ne => "!=",
            BinOp::Lt => "<",
            BinOp::Le => "<=",
            BinOp::Gt => ">",
            BinOp::Ge => ">=",
            // Pow needs special handling with pow() function call, not an operator
            BinOp::Pow => "/* pow */+",
        }
    }

    fn local_name(&self, id: LocalId, locals: &[MirLocal]) -> String {
        locals
            .get(id.0 as usize)
            .and_then(|l| l.name.as_ref())
            .map(|n| {
                let name = n.to_string();
                // Escape C reserved words/types by prefixing with underscore
                let base = if Self::is_c_reserved(&name) {
                    format!("_{}", name)
                } else {
                    name.clone()
                };
                // Disambiguate when multiple locals share the same user name
                // (e.g. pattern bindings `v` in different match arms).  Check
                // if any *other* local has the same name; if so, append the
                // local ID to make the C declaration unique.
                let has_dup = locals.iter().any(|other| {
                    other.id != id && other.name.as_ref().map(|s| s.as_ref()) == Some(n.as_ref())
                });
                if has_dup {
                    format!("{}_{}", base, id.0)
                } else {
                    base
                }
            })
            .unwrap_or_else(|| format!("_{}", id.0))
    }

    /// Check if a name conflicts with C reserved words or standard type names.
    fn is_c_reserved(name: &str) -> bool {
        matches!(
            name,
            // C keywords
            "auto" | "break" | "case" | "char" | "const" | "continue" |
            "default" | "do" | "double" | "else" | "enum" | "extern" |
            "float" | "for" | "goto" | "if" | "inline" | "int" | "long" |
            "register" | "restrict" | "return" | "short" | "signed" |
            "sizeof" | "static" | "struct" | "switch" | "typedef" |
            "union" | "unsigned" | "void" | "volatile" | "while" |
            // C99/C11 keywords
            "_Alignas" | "_Alignof" | "_Atomic" | "_Bool" | "_Complex" |
            "_Generic" | "_Imaginary" | "_Noreturn" | "_Static_assert" |
            "_Thread_local" |
            // Common standard library identifiers
            "bool" | "true" | "false" | "NULL" |
            // Standard types from stdint.h
            "int8_t" | "int16_t" | "int32_t" | "int64_t" |
            "uint8_t" | "uint16_t" | "uint32_t" | "uint64_t" |
            "size_t" | "ptrdiff_t" | "intptr_t" | "uintptr_t" |
            // printf itself
            "printf" | "fprintf" | "sprintf" |
            // Common macros / functions from stdlib that may collide
            "min" | "max" | "abs" |
            // Win16 legacy keywords (defined as macros in windef.h)
            "near" | "far"
        )
    }

    /// Check if a function name is a QuantaLang runtime helper or a standard
    /// C math/stdlib function that must NOT be escaped.  These names are
    /// Escape C reserved keywords used as identifiers by appending an underscore.
    fn escape_c_keyword(name: &str) -> String {
        match name {
            "default" | "register" | "volatile" | "signed" | "unsigned"
            | "auto" | "extern" | "static" | "typedef" | "union" | "enum"
            | "struct" | "switch" | "case" | "break" | "continue" | "goto"
            | "return" | "if" | "else" | "while" | "do" | "for" | "inline"
            | "restrict" | "const" => format!("{}_", name),
            _ => name.to_string(),
        }
    }

    /// produced by the lowerer for builtin operations and should pass through
    /// to C unchanged.
    /// Collect block IDs that need labels (targeted by non-sequential jumps).
    /// A block needs a label if ANY block other than its immediate predecessor
    /// has a jump (goto/if/switch/call) targeting it.
    fn collect_needed_labels(blocks: &[MirBlock]) -> std::collections::HashSet<BlockId> {
        use crate::codegen::ir::*;
        let mut needed = std::collections::HashSet::new();

        for (i, block) in blocks.iter().enumerate() {
            let next_id = blocks.get(i + 1).map(|b| b.id);
            if let Some(ref term) = block.terminator {
                match term {
                    MirTerminator::Goto(target) => {
                        // Only need label if NOT the next sequential block
                        if Some(*target) != next_id {
                            needed.insert(*target);
                        }
                    }
                    MirTerminator::If {
                        then_block,
                        else_block,
                        ..
                    } => {
                        needed.insert(*then_block);
                        needed.insert(*else_block);
                    }
                    MirTerminator::Switch { targets, default, .. } => {
                        for (_, target) in targets {
                            needed.insert(*target);
                        }
                        needed.insert(*default);
                    }
                    MirTerminator::Call { target, .. } => {
                        if let Some(t) = target {
                            // Call continuation: need label if not next block
                            if Some(*t) != next_id {
                                needed.insert(*t);
                            }
                        }
                    }
                    MirTerminator::Assert {
                        target, unwind, ..
                    } => {
                        needed.insert(*target);
                        if let Some(u) = unwind {
                            needed.insert(*u);
                        }
                    }
                    _ => {}
                }
            }
        }
        needed
    }

    /// Collect all LocalId values that are referenced in the function body.
    /// Used for dead local elimination — locals not in this set can be skipped.
    fn collect_used_locals(func: &MirFunction) -> std::collections::HashSet<LocalId> {
        use crate::codegen::ir::*;
        let mut used = std::collections::HashSet::new();

        let blocks = match &func.blocks {
            Some(blocks) => blocks,
            None => return used,
        };

        fn collect_val(val: &MirValue, used: &mut std::collections::HashSet<LocalId>) {
            if let MirValue::Local(id) = val {
                used.insert(*id);
            }
        }
        fn collect_place(place: &MirPlace, used: &mut std::collections::HashSet<LocalId>) {
            used.insert(place.local);
        }

        for block in blocks {
            for stmt in &block.stmts {
                match &stmt.kind {
                    MirStmtKind::Assign { dest, value } => {
                        used.insert(*dest);
                        match value {
                            MirRValue::Use(v) => collect_val(v, &mut used),
                            MirRValue::BinaryOp { left, right, .. } => {
                                collect_val(left, &mut used);
                                collect_val(right, &mut used);
                            }
                            MirRValue::UnaryOp { operand, .. } => {
                                collect_val(operand, &mut used);
                            }
                            MirRValue::Ref { place, .. }
                            | MirRValue::AddressOf { place, .. } => {
                                collect_place(place, &mut used);
                            }
                            MirRValue::Cast { value, .. } => collect_val(value, &mut used),
                            MirRValue::Aggregate { operands, .. } => {
                                for op in operands {
                                    collect_val(op, &mut used);
                                }
                            }
                            MirRValue::FieldAccess { base, .. }
                            | MirRValue::VariantField { base, .. } => {
                                collect_val(base, &mut used);
                            }
                            MirRValue::IndexAccess { base, index, .. } => {
                                collect_val(base, &mut used);
                                collect_val(index, &mut used);
                            }
                            MirRValue::Repeat { value, .. } => collect_val(value, &mut used),
                            MirRValue::Discriminant(p) | MirRValue::Len(p) => {
                                collect_place(p, &mut used);
                            }
                            MirRValue::TextureSample {
                                texture,
                                sampler,
                                coords,
                            } => {
                                collect_val(texture, &mut used);
                                collect_val(sampler, &mut used);
                                collect_val(coords, &mut used);
                            }
                            _ => {}
                        }
                    }
                    MirStmtKind::DerefAssign { ptr, value } => {
                        used.insert(*ptr);
                        match value {
                            MirRValue::Use(v) => collect_val(v, &mut used),
                            _ => {}
                        }
                    }
                    MirStmtKind::StorageLive(id) | MirStmtKind::StorageDead(id) => {
                        used.insert(*id);
                    }
                    _ => {}
                }
            }
            if let Some(ref term) = block.terminator {
                match term {
                    MirTerminator::Return(v) => {
                        if let Some(v) = v {
                            collect_val(v, &mut used);
                        }
                    }
                    MirTerminator::If { cond, .. } => collect_val(cond, &mut used),
                    MirTerminator::Switch { value, .. } => collect_val(value, &mut used),
                    MirTerminator::Call {
                        func: f,
                        args,
                        dest,
                        ..
                    } => {
                        collect_val(f, &mut used);
                        for a in args {
                            collect_val(a, &mut used);
                        }
                        if let Some(d) = dest {
                            used.insert(*d);
                        }
                    }
                    MirTerminator::Assert { cond, .. } => collect_val(cond, &mut used),
                    _ => {}
                }
            }
        }

        used
    }

    fn is_runtime_or_builtin_fn(name: &str) -> bool {
        // Runtime helpers all start with "quanta_"
        if name.starts_with("quanta_") {
            return true;
        }
        // Standard C math / stdlib functions used by the builtin system
        matches!(
            name,
            "sqrt"
                | "cbrt"
                | "sin"
                | "cos"
                | "tan"
                | "pow"
                | "fabs"
                | "asin"
                | "acos"
                | "atan"
                | "atan2"
                | "sinh"
                | "cosh"
                | "tanh"
                | "exp"
                | "exp2"
                | "log"
                | "log2"
                | "log10"
                | "floor"
                | "ceil"
                | "round"
                | "trunc"
                | "fmax"
                | "fmin"
                | "fmod"
                | "hypot"
                | "copysign"
                | "abs"
                | "printf"
                | "fprintf"
                | "sprintf"
                | "setjmp"
                | "longjmp"
                | "malloc"
                | "realloc"
                | "free"
                | "memcpy"
                | "memcmp"
                | "strlen"
                | "fopen"
                | "fclose"
                | "fread"
                | "fwrite"
                | "fseek"
                | "ftell"
                | "exit"
                | "abort"
        )
    }

    fn escape_string(&self, s: &str) -> String {
        let mut result = String::new();
        for c in s.chars() {
            match c {
                '\n' => result.push_str("\\n"),
                '\r' => result.push_str("\\r"),
                '\t' => result.push_str("\\t"),
                '\\' => result.push_str("\\\\"),
                '"' => result.push_str("\\\""),
                c if c.is_ascii_control() => {
                    result.push_str(&format!("\\x{:02x}", c as u8));
                }
                c => result.push(c),
            }
        }
        result
    }
}

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

impl Backend for CBackend {
    fn generate(&mut self, mir: &MirModule) -> CodegenResult<GeneratedCode> {
        self.output.clear();
        self.temp_counter = 0;

        self.generate_module(mir)?;

        Ok(GeneratedCode::new(
            OutputFormat::CSource,
            self.output.as_bytes().to_vec(),
        ))
    }

    fn target(&self) -> Target {
        Target::C
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::codegen::builder::{values, MirBuilder, MirModuleBuilder};
    use std::sync::Arc;

    // =========================================================================
    // C BACKEND TESTS
    // =========================================================================

    #[test]
    fn test_c_backend_simple() {
        let mut module_builder = MirModuleBuilder::new("test");

        // Build: fn add(a: i32, b: i32) -> i32 { a + b }
        let sig = MirFnSig::new(vec![MirType::i32(), MirType::i32()], MirType::i32());
        let mut builder = MirBuilder::new("add", sig);

        let a = builder.param_local(0);
        let b = builder.param_local(1);
        let result = builder.create_local(MirType::i32());

        builder.binary_op(result, BinOp::Add, values::local(a), values::local(b));
        builder.ret(Some(values::local(result)));

        module_builder.add_function(builder.build());
        let module = module_builder.build();

        let mut backend = CBackend::new();
        let output = backend.generate(&module).unwrap();

        let code = output.as_string().unwrap();
        assert!(code.contains("int32_t add("));
        assert!(code.contains("return"));
    }

    #[test]
    fn test_c_backend_void_function() {
        let mut module_builder = MirModuleBuilder::new("test");

        let sig = MirFnSig::new(vec![], MirType::Void);
        let mut builder = MirBuilder::new("noop", sig);
        builder.ret_void();

        module_builder.add_function(builder.build());
        let module = module_builder.build();

        let mut backend = CBackend::new();
        let output = backend.generate(&module).unwrap();

        let code = output.as_string().unwrap();
        assert!(code.contains("void noop(void)"));
        assert!(code.contains("return;"));
    }

    #[test]
    fn test_c_backend_global_variable() {
        let mut module_builder = MirModuleBuilder::new("test");

        let mut global = MirGlobal::new("MY_CONST", MirType::i32());
        global.init = Some(MirConst::Int(42, MirType::i32()));
        module_builder.add_global(global);

        // Add a dummy function so we have something to generate
        let sig = MirFnSig::new(vec![], MirType::Void);
        let mut builder = MirBuilder::new("main", sig);
        builder.ret_void();
        module_builder.add_function(builder.build());

        let module = module_builder.build();

        let mut backend = CBackend::new();
        let output = backend.generate(&module).unwrap();

        let code = output.as_string().unwrap();
        assert!(code.contains("MY_CONST"));
    }

    #[test]
    fn test_c_backend_string_table() {
        let mut module_builder = MirModuleBuilder::new("test");

        module_builder.intern_string("hello");
        module_builder.intern_string("world");

        let sig = MirFnSig::new(vec![], MirType::Void);
        let mut builder = MirBuilder::new("main", sig);
        builder.ret_void();
        module_builder.add_function(builder.build());

        let module = module_builder.build();

        let mut backend = CBackend::new();
        let output = backend.generate(&module).unwrap();

        let code = output.as_string().unwrap();
        assert!(code.contains("__str0"));
        assert!(code.contains("hello"));
    }

    #[test]
    fn test_c_backend_branching() {
        let mut module_builder = MirModuleBuilder::new("test");

        let sig = MirFnSig::new(vec![MirType::Bool], MirType::i32());
        let mut builder = MirBuilder::new("branch_test", sig);

        let cond = builder.param_local(0);
        let then_block = builder.create_block();
        let else_block = builder.create_block();

        builder.branch(values::local(cond), then_block, else_block);

        builder.switch_to_block(then_block);
        builder.ret(Some(values::i32(1)));

        builder.switch_to_block(else_block);
        builder.ret(Some(values::i32(0)));

        module_builder.add_function(builder.build());
        let module = module_builder.build();

        let mut backend = CBackend::new();
        let output = backend.generate(&module).unwrap();

        let code = output.as_string().unwrap();
        assert!(code.contains("if ("));
        assert!(code.contains("goto"));
    }

    #[test]
    fn test_c_backend_all_binary_ops() {
        let mut module_builder = MirModuleBuilder::new("test");

        let ops = [
            BinOp::Add,
            BinOp::Sub,
            BinOp::Mul,
            BinOp::Div,
            BinOp::Rem,
            BinOp::BitAnd,
            BinOp::BitOr,
            BinOp::BitXor,
            BinOp::Eq,
            BinOp::Ne,
            BinOp::Lt,
            BinOp::Le,
            BinOp::Gt,
            BinOp::Ge,
        ];

        for (i, op) in ops.iter().enumerate() {
            let sig = MirFnSig::new(vec![MirType::i32(), MirType::i32()], MirType::i32());
            let mut builder = MirBuilder::new(format!("op_{}", i), sig);

            let a = builder.param_local(0);
            let b = builder.param_local(1);
            let result = builder.create_local(MirType::i32());

            builder.binary_op(result, *op, values::local(a), values::local(b));
            builder.ret(Some(values::local(result)));

            module_builder.add_function(builder.build());
        }

        let module = module_builder.build();

        let mut backend = CBackend::new();
        let output = backend.generate(&module).unwrap();
        let code = output.as_string().unwrap();

        // Verify it generated without errors
        assert!(code.contains("op_0"));
        assert!(code.contains("op_13"));
    }

    #[test]
    fn test_c_backend_type_to_c() {
        let backend = CBackend::new();

        assert_eq!(backend.type_to_c(&MirType::Void), "void");
        assert_eq!(backend.type_to_c(&MirType::Bool), "bool");
        assert_eq!(backend.type_to_c(&MirType::i8()), "int8_t");
        assert_eq!(backend.type_to_c(&MirType::u8()), "uint8_t");
        assert_eq!(backend.type_to_c(&MirType::i32()), "int32_t");
        assert_eq!(backend.type_to_c(&MirType::u32()), "uint32_t");
        assert_eq!(backend.type_to_c(&MirType::i64()), "int64_t");
        assert_eq!(backend.type_to_c(&MirType::f32()), "float");
        assert_eq!(backend.type_to_c(&MirType::f64()), "double");
        assert_eq!(backend.type_to_c(&MirType::isize()), "intptr_t");
        assert_eq!(backend.type_to_c(&MirType::usize()), "uintptr_t");
    }

    #[test]
    fn test_c_backend_struct_type() {
        let mut module_builder = MirModuleBuilder::new("test");

        module_builder.create_struct(
            "Point",
            vec![
                (Some(Arc::from("x")), MirType::i32()),
                (Some(Arc::from("y")), MirType::i32()),
            ],
        );

        let sig = MirFnSig::new(vec![], MirType::Void);
        let mut builder = MirBuilder::new("main", sig);
        builder.ret_void();
        module_builder.add_function(builder.build());

        let module = module_builder.build();

        let mut backend = CBackend::new();
        let output = backend.generate(&module).unwrap();

        let code = output.as_string().unwrap();
        assert!(code.contains("typedef struct Point"));
        assert!(code.contains("int32_t x;"));
        assert!(code.contains("int32_t y;"));
    }

    #[test]
    fn test_c_backend_target() {
        let backend = CBackend::new();
        assert_eq!(backend.target(), Target::C);
    }

    #[test]
    fn test_c_backend_includes() {
        let module_builder = MirModuleBuilder::new("test");
        let module = module_builder.build();

        let mut backend = CBackend::new();
        let output = backend.generate(&module).unwrap();

        let code = output.as_string().unwrap();
        assert!(code.contains("#include <stdint.h>"));
        assert!(code.contains("#include <stdbool.h>"));
        assert!(code.contains("#include <stddef.h>"));
        assert!(code.contains("#include <math.h>"));
        // Runtime library should be embedded
        assert!(code.contains("QuantaString"));
        assert!(code.contains("QuantaVec"));
    }

    #[test]
    fn test_c_backend_struct_field_access() {
        let mut module_builder = MirModuleBuilder::new("test");

        module_builder.create_struct(
            "Point",
            vec![
                (Some(Arc::from("x")), MirType::i32()),
                (Some(Arc::from("y")), MirType::i32()),
            ],
        );

        let sig = MirFnSig::new(vec![MirType::Struct(Arc::from("Point"))], MirType::i32());
        let mut builder = MirBuilder::new("get_x", sig);
        builder.set_param_name(0, "p");

        let result = builder.create_local(MirType::i32());
        builder.assign(
            result,
            MirRValue::FieldAccess {
                base: values::local(LocalId(0)),
                field_name: Arc::from("x"),
                field_ty: MirType::i32(),
            },
        );
        builder.ret(Some(values::local(result)));

        module_builder.add_function(builder.build());
        let module = module_builder.build();

        let mut backend = CBackend::new();
        let output = backend.generate(&module).unwrap();

        let code = output.as_string().unwrap();
        assert!(code.contains("p.x"), "Expected 'p.x' in:\n{}", code);
        assert!(code.contains("Point p"), "Expected 'Point p' in:\n{}", code);
    }

    #[test]
    fn test_c_backend_struct_aggregate() {
        let mut module_builder = MirModuleBuilder::new("test");

        module_builder.create_struct(
            "Point",
            vec![
                (Some(Arc::from("x")), MirType::i32()),
                (Some(Arc::from("y")), MirType::i32()),
            ],
        );

        let sig = MirFnSig::new(vec![], MirType::Void);
        let mut builder = MirBuilder::new("main", sig);

        let p = builder.create_named_local(Arc::from("p"), MirType::Struct(Arc::from("Point")));
        builder.aggregate(
            p,
            AggregateKind::Struct(Arc::from("Point")),
            vec![values::i32(3), values::i32(4)],
        );
        builder.ret_void();

        module_builder.add_function(builder.build());
        let module = module_builder.build();

        let mut backend = CBackend::new();
        let output = backend.generate(&module).unwrap();

        let code = output.as_string().unwrap();
        assert!(
            code.contains("(Point){ 3, 4 }"),
            "Expected struct aggregate in:\n{}",
            code
        );
    }

    #[test]
    fn test_c_backend_enum_type() {
        let mut module_builder = MirModuleBuilder::new("test");

        module_builder.create_enum(
            "Shape",
            MirType::i32(),
            vec![
                MirEnumVariant {
                    name: Arc::from("Circle"),
                    discriminant: 0,
                    fields: vec![(None, MirType::f64())],
                },
                MirEnumVariant {
                    name: Arc::from("Rectangle"),
                    discriminant: 1,
                    fields: vec![(None, MirType::f64()), (None, MirType::f64())],
                },
            ],
        );

        let sig = MirFnSig::new(vec![], MirType::Void);
        let mut builder = MirBuilder::new("main", sig);
        builder.ret_void();

        module_builder.add_function(builder.build());
        let module = module_builder.build();

        let mut backend = CBackend::new();
        let output = backend.generate(&module).unwrap();

        let code = output.as_string().unwrap();
        assert!(
            code.contains("Shape_Circle = 0"),
            "Expected enum tag in:\n{}",
            code
        );
        assert!(
            code.contains("Shape_Rectangle = 1"),
            "Expected enum tag in:\n{}",
            code
        );
        assert!(
            code.contains("Shape_Tag"),
            "Expected tag type in:\n{}",
            code
        );
        assert!(
            code.contains("typedef struct Shape Shape;"),
            "Expected forward declaration in:\n{}",
            code
        );
        assert!(
            code.contains("struct Shape {"),
            "Expected struct definition in:\n{}",
            code
        );
    }

    #[test]
    fn test_c_backend_variant_field_access() {
        let mut module_builder = MirModuleBuilder::new("test");

        module_builder.create_enum(
            "Shape",
            MirType::i32(),
            vec![MirEnumVariant {
                name: Arc::from("Circle"),
                discriminant: 0,
                fields: vec![(None, MirType::f64())],
            }],
        );

        let sig = MirFnSig::new(vec![MirType::Struct(Arc::from("Shape"))], MirType::f64());
        let mut builder = MirBuilder::new("get_radius", sig);
        builder.set_param_name(0, "s");

        let result = builder.create_local(MirType::f64());
        builder.assign(
            result,
            MirRValue::VariantField {
                base: values::local(LocalId(0)),
                variant_name: Arc::from("Circle"),
                field_index: 0,
                field_ty: MirType::f64(),
            },
        );
        builder.ret(Some(values::local(result)));

        module_builder.add_function(builder.build());
        let module = module_builder.build();

        let mut backend = CBackend::new();
        let output = backend.generate(&module).unwrap();

        let code = output.as_string().unwrap();
        assert!(
            code.contains("s.data.Circle.f0"),
            "Expected variant field access in:\n{}",
            code
        );
    }

    #[test]
    fn test_c_backend_escape_string() {
        let backend = CBackend::new();

        assert_eq!(backend.escape_string("hello"), "hello");
        assert_eq!(backend.escape_string("hello\nworld"), "hello\\nworld");
        assert_eq!(backend.escape_string("tab\there"), "tab\\there");
        assert_eq!(backend.escape_string("quote\"here"), "quote\\\"here");
        assert_eq!(backend.escape_string("back\\slash"), "back\\\\slash");
    }

    #[test]
    fn test_e2e_struct_codegen() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
struct Point {
    x: i32,
    y: i32,
}

fn get_x(p: Point) -> i32 {
    p.x
}

fn main() {
    let p = Point { x: 3, y: 4 };
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen.generate(&module).expect("Failed to generate");
        let code = output.as_string().unwrap();

        // Verify struct type definition (forward decl + definition)
        assert!(
            code.contains("struct Point {"),
            "Missing struct definition in:\n{}",
            code
        );
        assert!(code.contains("int32_t x;"), "Missing field x in:\n{}", code);
        assert!(code.contains("int32_t y;"), "Missing field y in:\n{}", code);
        assert!(
            code.contains("};") && code.contains("struct Point {"),
            "Missing struct definition in:\n{}",
            code
        );

        // Verify struct literal
        assert!(
            code.contains("(Point){ 3, 4 }"),
            "Missing struct literal in:\n{}",
            code
        );

        // Verify field access
        assert!(code.contains("p.x"), "Missing field access in:\n{}", code);

        // Verify function signature uses struct type
        assert!(
            code.contains("Point p"),
            "Missing struct param in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_enum_codegen() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
enum Shape {
    Circle(f64),
    Rect(f64, f64),
}

fn area(s: Shape) -> f64 {
    match s {
        Shape::Circle(r) => 3.14 * r * r,
        Shape::Rect(w, h) => w * h,
    }
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen.generate(&module).expect("Failed to generate");
        let code = output.as_string().unwrap();

        // Verify enum type definition
        assert!(
            code.contains("Shape_Circle = 0"),
            "Missing Circle tag in:\n{}",
            code
        );
        assert!(
            code.contains("Shape_Rect = 1"),
            "Missing Rect tag in:\n{}",
            code
        );
        assert!(code.contains("Shape_Tag"), "Missing tag type in:\n{}", code);
        assert!(
            code.contains("typedef struct Shape Shape;"),
            "Missing enum forward decl in:\n{}",
            code
        );
        assert!(
            code.contains("struct Shape {"),
            "Missing enum struct body in:\n{}",
            code
        );
        assert!(code.contains("union"), "Missing union in:\n{}", code);

        // Verify variant field access in match
        assert!(
            code.contains(".data.Circle.f0"),
            "Missing variant field access in:\n{}",
            code
        );
        assert!(
            code.contains(".data.Rect.f0"),
            "Missing Rect field0 access in:\n{}",
            code
        );
        assert!(
            code.contains(".data.Rect.f1"),
            "Missing Rect field1 access in:\n{}",
            code
        );

        // Verify tag comparison
        assert!(code.contains(".tag"), "Missing tag access in:\n{}", code);
    }

    #[test]
    fn test_e2e_ref_self_method() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
struct Point {
    x: f64,
    y: f64,
}

impl Point {
    fn new(x: f64, y: f64) -> Self {
        Point { x: x, y: y }
    }

    fn magnitude(&self) -> f64 {
        sqrt(self.x * self.x + self.y * self.y)
    }

    fn scale(&mut self, factor: f64) {
        self.x = self.x * factor;
        self.y = self.y * factor;
    }
}

fn main() {
    let mut p = Point::new(3.0, 4.0);
    let mag = p.magnitude();
    p.scale(2.0);
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen.generate(&module).expect("Failed to generate");
        let code = output.as_string().unwrap();

        // &self method should take a pointer parameter
        assert!(code.contains("Point*"), "Expected 'Point*' in:\n{}", code);
        // field access through pointer should use ->
        assert!(
            code.contains("->x") || code.contains("-> x"),
            "Expected '->x' (pointer field access) in:\n{}",
            code
        );
        // Method call should pass &p
        assert!(
            code.contains("&p") || code.contains("& p"),
            "Expected '&p' (address-of) in method call in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_ref_self_distance() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
struct Point {
    x: f64,
    y: f64,
}

impl Point {
    fn distance(&self, other: &Point) -> f64 {
        let dx = self.x - other.x;
        let dy = self.y - other.y;
        sqrt(dx * dx + dy * dy)
    }
}

fn main() {
    let p = Point { x: 3.0, y: 4.0 };
    let q = Point { x: 0.0, y: 0.0 };
    let dist = p.distance(&q);
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen.generate(&module).expect("Failed to generate");
        let code = output.as_string().unwrap();

        // &self method takes pointer
        assert!(
            code.contains("Point*"),
            "Expected 'Point*' param in:\n{}",
            code
        );
        // Field access through pointer should use ->
        assert!(code.contains("->x"), "Expected '->x' in:\n{}", code);
    }

    #[test]
    fn test_e2e_mut_self_method() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
struct Point {
    x: f64,
    y: f64,
}

impl Point {
    fn scale(&mut self, factor: f64) {
        self.x = self.x * factor;
        self.y = self.y * factor;
    }
}

fn main() {
    let mut p = Point { x: 3.0, y: 4.0 };
    p.scale(2.0);
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen.generate(&module).expect("Failed to generate");
        let code = output.as_string().unwrap();

        // &mut self should generate pointer parameter
        assert!(
            code.contains("Point*"),
            "Expected 'Point*' param in:\n{}",
            code
        );
        // Field assignment through pointer: self->x = ...
        assert!(
            code.contains("->x =") || code.contains("->x="),
            "Expected '->x =' (pointer field assign) in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_primitive_float_methods() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let x: f64 = -3.7;
    let y: f64 = 2.0;

    let a = x.abs();
    let f = x.floor();
    let c = x.ceil();
    let s = (4.0).sqrt();
    let p = y.powi(3);
    let mx = x.max(y);
    let mn = x.min(y);
    let pi = 3.14159265358979323846;
    let deg = pi.to_degrees();
    let rad = (180.0).to_radians();
    let sv = (0.0).sin();
    let cv = (0.0).cos();
    let ev = (1.0).exp();
    let lv = (1.0).ln();
    let fv = (3.7).fract();
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // Verify primitive method calls are lowered to correct C functions
        assert!(
            code.contains("fabs("),
            "Expected fabs() for .abs() in:\n{}",
            code
        );
        assert!(
            code.contains("floor("),
            "Expected floor() for .floor() in:\n{}",
            code
        );
        assert!(
            code.contains("ceil("),
            "Expected ceil() for .ceil() in:\n{}",
            code
        );
        assert!(
            code.contains("sqrt("),
            "Expected sqrt() for .sqrt() in:\n{}",
            code
        );
        assert!(
            code.contains("pow("),
            "Expected pow() for .powi() in:\n{}",
            code
        );
        assert!(
            code.contains("fmax("),
            "Expected fmax() for .max() in:\n{}",
            code
        );
        assert!(
            code.contains("fmin("),
            "Expected fmin() for .min() in:\n{}",
            code
        );
        assert!(
            code.contains("sin("),
            "Expected sin() for .sin() in:\n{}",
            code
        );
        assert!(
            code.contains("cos("),
            "Expected cos() for .cos() in:\n{}",
            code
        );
        assert!(
            code.contains("exp("),
            "Expected exp() for .exp() in:\n{}",
            code
        );
        assert!(
            code.contains("log("),
            "Expected log() for .ln() in:\n{}",
            code
        );

        // to_degrees and to_radians lower to multiplication by constants
        // They should NOT produce a function call to "to_degrees" or "to_radians"
        assert!(
            !code.contains("to_degrees("),
            "to_degrees should be inlined, not a call in:\n{}",
            code
        );
        assert!(
            !code.contains("to_radians("),
            "to_radians should be inlined, not a call in:\n{}",
            code
        );

        // fract should use floor() and subtraction
        // Verify floor appears (used by both .floor() and .fract())
        let floor_count = code.matches("floor(").count();
        assert!(
            floor_count >= 2,
            "Expected at least 2 floor() calls (for .floor() and .fract()) in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_inline_module_basic() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
pub mod math {
    pub fn add(a: i32, b: i32) -> i32 {
        a + b
    }
}

fn main() {
    let result = math::add(3, 4);
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // The function inside `mod math` should be emitted as `math_add`
        assert!(
            code.contains("math_add"),
            "Expected math_add function in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_inline_module_with_const() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
pub mod config {
    pub const MAX_SIZE: i32 = 100;
}

fn main() {
    let x = config::MAX_SIZE;
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // The const inside `mod config` should be emitted as `config_MAX_SIZE`
        assert!(
            code.contains("config_MAX_SIZE"),
            "Expected config_MAX_SIZE global in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_inline_module_with_use_super() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn helper() -> i32 {
    42
}

pub mod inner {
    use super::*;

    pub fn call_helper() -> i32 {
        helper()
    }
}

fn main() {
    let x = inner::call_helper();
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // inner::call_helper should generate inner_call_helper
        assert!(
            code.contains("inner_call_helper"),
            "Expected inner_call_helper function in:\n{}",
            code
        );
        // The body should call helper() (the parent-scope function)
        assert!(
            code.contains("helper("),
            "Expected call to helper() in inner_call_helper body:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_nested_inline_modules() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
pub mod outer {
    pub mod inner {
        pub fn deep_fn() -> i32 {
            99
        }
    }
}

fn main() {
    let x = outer::inner::deep_fn();
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // Nested modules should produce outer_inner_deep_fn
        assert!(
            code.contains("outer_inner_deep_fn"),
            "Expected outer_inner_deep_fn function in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_struct_const() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
struct Color {
    r: f64,
    g: f64,
    b: f64,
}

const WHITE: Color = Color { r: 1.0, g: 1.0, b: 1.0 };
const BLACK: Color = Color { r: 0.0, g: 0.0, b: 0.0 };

fn main() {
    let w = WHITE;
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // Verify struct constant globals are emitted with initializers
        assert!(
            code.contains("const Color WHITE = (Color)"),
            "Expected const Color WHITE global in:\n{}",
            code
        );
        assert!(
            code.contains("const Color BLACK = (Color)"),
            "Expected const Color BLACK global in:\n{}",
            code
        );

        // Verify field values are present in the initializer
        assert!(
            code.contains("1") && code.contains("0"),
            "Expected field values in struct const initializer:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_vec_macro_literal() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let v = vec![1, 2, 3];
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // vec![1, 2, 3] should expand to vec_new + 3x vec_push
        assert!(
            code.contains("quanta_hvec_new_i32"),
            "Expected quanta_hvec_new_i32 call in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_hvec_push_i32"),
            "Expected quanta_hvec_push_i32 calls in:\n{}",
            code
        );
        let push_count = code.matches("quanta_hvec_push_i32").count();
        assert!(
            push_count >= 3,
            "Expected at least 3 push calls, got {} in:\n{}",
            push_count,
            code
        );
    }

    #[test]
    fn test_e2e_vec_macro_repeat() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let zeros = vec![0.0; 5];
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // vec![0.0; 5] should use f64 variant and have a loop
        assert!(
            code.contains("quanta_hvec_new_f64"),
            "Expected quanta_hvec_new_f64 call in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_hvec_push_f64"),
            "Expected quanta_hvec_push_f64 call in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_vec_type_annotation() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let v: Vec<f64> = vec![1.0, 2.0, 3.0];
    let len = vec_len(v);
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // Vec<f64> type annotation should produce QuantaVecHandle
        assert!(
            code.contains("QuantaVecHandle"),
            "Expected QuantaVecHandle type in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_hvec_new_f64"),
            "Expected quanta_hvec_new_f64 in:\n{}",
            code
        );
    }

    // =========================================================================
    // Iterator chain lowering tests
    // =========================================================================

    #[test]
    fn test_e2e_iter_map_collect() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let v = vec![1.0, 2.0, 3.0];
    let doubled = v.iter().map(|x: f64| x * 2.0).collect();
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // Iterator chain should lower to a loop with vec_new + get + push
        assert!(
            code.contains("quanta_hvec_new_f64"),
            "Expected quanta_hvec_new_f64 for collect result in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_hvec_get_f64"),
            "Expected quanta_hvec_get_f64 for element access in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_hvec_push_f64"),
            "Expected quanta_hvec_push_f64 for collect push in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_hvec_len"),
            "Expected quanta_hvec_len for loop bound in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_iter_fold() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let v = vec![1.0, 2.0, 3.0];
    let sum = v.iter().fold(0.0, |acc: f64, x: f64| acc + x);
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // fold should lower to a loop with accumulator but NO vec_new/push
        assert!(
            code.contains("quanta_hvec_get_f64"),
            "Expected quanta_hvec_get_f64 for element access in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_hvec_len"),
            "Expected quanta_hvec_len for loop bound in:\n{}",
            code
        );
        // fold shouldn't create a new output vec (only the source vec![...] + runtime definition)
        let new_count = code.matches("quanta_hvec_new_f64").count();
        // 1 for the runtime header definition + 1 for source vec = 2 total
        assert!(
            new_count == 2,
            "Expected exactly 2 quanta_hvec_new_f64 (runtime def + source), got {} in:\n{}",
            new_count,
            code
        );
        // Also verify no push calls (fold accumulates, doesn't push)
        // Count only calls in main(), not the runtime definition
        let push_count = code.matches("quanta_hvec_push_f64(").count();
        // 3 pushes for vec![1.0, 2.0, 3.0] + 1 runtime def = 4
        assert!(
            push_count <= 4,
            "Fold should not add extra push calls, got {} in:\n{}",
            push_count,
            code
        );
    }

    #[test]
    fn test_e2e_iter_map_fold_chain() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let v = vec![1.0, 2.0, 3.0];
    let sum_sq = v.iter().map(|x: f64| x * x).fold(0.0, |acc: f64, x: f64| acc + x);
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // map + fold chain: should have get + len but only 1 new (for source)
        assert!(
            code.contains("quanta_hvec_get_f64"),
            "Expected quanta_hvec_get_f64 in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_hvec_len"),
            "Expected quanta_hvec_len in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_iter_enumerate_map_collect() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let v = vec![10.0, 20.0, 30.0];
    let indices = v.iter().enumerate().map(|i: i64, x: f64| i).collect();
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // enumerate + map + collect: should have the loop infrastructure
        assert!(
            code.contains("quanta_hvec_len"),
            "Expected quanta_hvec_len for loop bound in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_hvec_get_f64"),
            "Expected quanta_hvec_get_f64 in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_inclusive_range_for_loop() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let mut sum = 0;
    for i in 0..=5 {
        sum = sum + i;
    }
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // Inclusive range: exit condition uses > (not >=)
        assert!(
            code.contains("> 5)"),
            "Expected `> 5)` for inclusive range exit in:\n{}",
            code
        );
        // Should have an increment by 1
        assert!(
            code.contains("+ 1)"),
            "Expected increment by 1 in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_range_step_by() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let mut count = 0;
    for i in (0..10).step_by(2) {
        count = count + 1;
    }
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // Exclusive range: exit condition uses >=
        assert!(
            code.contains(">= 10)"),
            "Expected `>= 10)` for exclusive range exit in:\n{}",
            code
        );
        // step_by(2): increment by 2 instead of 1
        assert!(
            code.contains("+ 2)"),
            "Expected increment by 2 for step_by(2) in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_inclusive_range_step_by() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let mut vals = vec![];
    for i in (0..=10).step_by(3) {
        vec_push(vals, i);
    }
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // Inclusive range: exit condition uses >
        assert!(
            code.contains("> 10)"),
            "Expected `> 10)` for inclusive range exit in:\n{}",
            code
        );
        // step_by(3): increment by 3
        assert!(
            code.contains("+ 3)"),
            "Expected increment by 3 for step_by(3) in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_main_argc_argv() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let n = args_count();
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        // main gets argc/argv signature
        assert!(
            code.contains("main(int argc, char** argv)"),
            "Missing argc/argv signature in:\n{}",
            code
        );
        // args init is called
        assert!(
            code.contains("quanta_args_init(argc, argv)"),
            "Missing args init in:\n{}",
            code
        );
        // args_count builtin is called
        assert!(
            code.contains("quanta_args_count"),
            "Missing args_count call in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_string_parse_methods() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let s = "Hello, World!";
    let idx = s.index_of("World");
    let sub = s.substring(0, 5);
    let r = s.replace("World", "QuantaLang");
    let n = "42".parse_int();
    let f = "3.14".parse_float();
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        assert!(
            code.contains("quanta_string_index_of"),
            "Missing index_of call in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_string_substring"),
            "Missing substring call in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_string_replace"),
            "Missing replace call in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_string_parse_int"),
            "Missing parse_int call in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_string_parse_float"),
            "Missing parse_float call in:\n{}",
            code
        );
    }

    #[test]
    fn test_e2e_stdin_builtins() {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let source = r#"
fn main() {
    let piped = stdin_is_pipe();
    if piped {
        let input = read_all();
    }
}
"#;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        let code = output.as_string().unwrap();

        assert!(
            code.contains("quanta_stdin_is_pipe"),
            "Missing stdin_is_pipe call in:\n{}",
            code
        );
        assert!(
            code.contains("quanta_read_all"),
            "Missing read_all call in:\n{}",
            code
        );
    }

    // =========================================================================
    // SNAPSHOT TESTS (insta)
    // =========================================================================

    /// Helper: parse source → type-check → generate C code string.
    fn snapshot_codegen(source: &str) -> String {
        use crate::codegen::{CodeGenerator, Target};
        use crate::parser::parse_source;
        use crate::types::TypeContext;

        let module = parse_source("test.quanta", source).expect("Failed to parse");
        let ctx = TypeContext::new();
        let mut codegen = CodeGenerator::with_source(&ctx, Target::C, Arc::from(source));
        let output = codegen
            .generate(&module)
            .expect("Failed to generate C code");
        output.as_string().unwrap().to_string()
    }

    #[test]
    fn test_snapshot_hello() {
        let source = r#"
fn main() {
    println!("Hello");
}
"#;
        let code = snapshot_codegen(source);
        insta::assert_snapshot!(code);
    }

    #[test]
    fn test_snapshot_arithmetic() {
        let source = r#"
fn main() {
    let x = 3 + 4 * 5;
    println!("{}", x);
}
"#;
        let code = snapshot_codegen(source);
        insta::assert_snapshot!(code);
    }

    #[test]
    fn test_snapshot_struct() {
        let source = r#"
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 1, y: 2 };
}
"#;
        let code = snapshot_codegen(source);
        insta::assert_snapshot!(code);
    }

    #[test]
    fn test_snapshot_if_else() {
        let source = r#"
fn main() {
    let x = 10;
    if x > 5 {
        println!("big");
    } else {
        println!("small");
    }
}
"#;
        let code = snapshot_codegen(source);
        insta::assert_snapshot!(code);
    }

    #[test]
    fn test_snapshot_recursion() {
        let source = r#"
fn factorial(n: i32) -> i32 {
    if n <= 1 {
        1
    } else {
        n * factorial(n - 1)
    }
}

fn main() {
    println!("{}", factorial(5));
}
"#;
        let code = snapshot_codegen(source);
        insta::assert_snapshot!(code);
    }
}
