use std::fs::File;
use std::io::{BufWriter, Write};
use std::collections::{HashMap, BTreeMap};
use crate::error::Result;
use crate::il2cpp::base::Il2Cpp;
use crate::il2cpp::metadata::Metadata;
use crate::executor::Il2CppExecutor;
use crate::executor::custom_attribute_reader;
use crate::config::Config;

fn safe_truncate(s: &str, max_bytes: usize) -> &str {
    if s.len() <= max_bytes {
        return s;
    }
    let mut end = max_bytes;
    while end > 0 && !s.is_char_boundary(end) {
        end -= 1;
    }
    &s[..end]
}

pub fn dump_generics(
    output_path: &str,
    metadata: &mut Metadata,
    il2cpp: &mut Il2Cpp,
    executor: &mut Il2CppExecutor,
    config: &Config,
) -> Result<()> {
    let mut file = BufWriter::with_capacity(1 << 20, File::create(output_path)?);

    writeln!(file, "// ============================================================================")?;
    writeln!(file, "// IL2CPP Advanced Dump - Runtime Generic Contexts, Attributes & Cross-References")?;
    writeln!(file, "// Generated by Il2CppDumper Rust v5.5")?;
    writeln!(file, "// ============================================================================\n")?;

    let type_def_image_map = build_type_def_image_map(metadata);
    let method_def_owner_map = build_method_def_owner_map(metadata);

    if config.dump_generics_rgctx {
        dump_rgctx_section(&mut file, metadata, il2cpp, executor, &type_def_image_map)?;
    }

    if config.dump_generics_method_specs {
        dump_method_specs_section(&mut file, metadata, il2cpp, executor)?;
    }

    if config.dump_generics_custom_attributes {
        dump_custom_attributes_section(&mut file, metadata, il2cpp, executor, &type_def_image_map)?;
    }

    if config.dump_generics_string_literals {
        dump_string_literals_section(&mut file, metadata)?;
    }

    if config.dump_generics_metadata_usages {
        dump_metadata_usages_section(&mut file, metadata, il2cpp, executor, &type_def_image_map, &method_def_owner_map)?;
    }

    if config.dump_generics_vtables {
        dump_vtable_section(&mut file, metadata, il2cpp, executor, &type_def_image_map)?;
    }

    if config.dump_generics_interfaces {
        dump_interface_section(&mut file, metadata, il2cpp, executor, &type_def_image_map)?;
    }

    file.flush()?;
    Ok(())
}

fn build_type_def_image_map(metadata: &mut Metadata) -> HashMap<usize, (usize, String)> {
    let mut map = HashMap::new();
    for (img_idx, image_def) in metadata.image_defs.clone().iter().enumerate() {
        let image_name = metadata.get_string_from_index(image_def.name_index)
            .unwrap_or_default();
        let type_end = image_def.type_start as usize + image_def.type_count as usize;
        for type_idx in image_def.type_start as usize..type_end {
            map.insert(type_idx, (img_idx, image_name.clone()));
        }
    }
    map
}

fn build_method_def_owner_map(metadata: &Metadata) -> HashMap<usize, usize> {
    let mut map = HashMap::new();
    for (td_idx, td) in metadata.type_defs.iter().enumerate() {
        let method_end = td.method_start as usize + td.method_count as usize;
        for m_idx in td.method_start as usize..method_end {
            map.insert(m_idx, td_idx);
        }
    }
    map
}

fn dump_rgctx_section(
    file: &mut BufWriter<File>,
    metadata: &mut Metadata,
    il2cpp: &mut Il2Cpp,
    executor: &mut Il2CppExecutor,
    type_def_image_map: &HashMap<usize, (usize, String)>,
) -> Result<()> {
    writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
    writeln!(file, "║  SECTION 1: Runtime Generic Contexts (RGCTX)                                ║")?;
    writeln!(file, "║  Maps concrete generic instantiations used by the IL2CPP runtime             ║")?;
    writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;

    let mut total_rgctx_entries = 0u64;

    for image in &metadata.image_defs.clone() {
        let image_name = metadata.get_string_from_index(image.name_index).unwrap_or_default();
        let mut image_buffer = String::new();

        let type_end = image.type_start as usize + image.type_count as usize;
        for type_idx in image.type_start as usize..type_end {
            let type_def = metadata.type_defs[type_idx].clone();

            if let Some(rgctxs) = executor.get_rgctx_definition_for_type(&image_name, &type_def, metadata, il2cpp) {
                if !rgctxs.is_empty() {
                    let type_name = executor.get_type_def_name(&type_def, type_idx, metadata, il2cpp, true, true);
                    image_buffer.push_str(&format!("  Type: {} (Token: 0x{:08X})\n", type_name, type_def.token));

                    for (i, rgctx) in rgctxs.iter().enumerate() {
                        let formatted = format_rgctx(rgctx, metadata, il2cpp, executor);
                        image_buffer.push_str(&format!("    [{:3}] {} -> {}\n", i, get_rgctx_type_name(rgctx.rgctx_type), formatted));
                        total_rgctx_entries += 1;
                    }
                    image_buffer.push('\n');
                }
            }

            let method_end = type_def.method_start as usize + type_def.method_count as usize;
            for method_idx in type_def.method_start as usize..method_end {
                if method_idx >= metadata.method_defs.len() { break; }
                let method_def = metadata.method_defs[method_idx].clone();

                if let Some(rgctxs) = executor.get_rgctx_definition_for_method(&image_name, &method_def, metadata, il2cpp) {
                    if !rgctxs.is_empty() {
                        let type_name = executor.get_type_def_name(&type_def, type_idx, metadata, il2cpp, true, true);
                        let method_name = metadata.get_string_from_index(method_def.name_index as i32).unwrap_or_else(|_| "?".to_string());

                        let img_name = type_def_image_map.get(&type_idx)
                            .map(|(_, n)| n.clone()).unwrap_or_default();
                        let method_ptr = il2cpp.get_method_pointer(&img_name, &method_def);
                        let rva_str = if method_ptr > 0 {
                            format!(" @ 0x{:08X}", il2cpp.get_rva(method_ptr))
                        } else {
                            String::new()
                        };

                        image_buffer.push_str(&format!(
                            "  Method: {}::{} (Token: 0x{:08X}){}\n",
                            type_name, method_name, method_def.token, rva_str
                        ));

                        for (i, rgctx) in rgctxs.iter().enumerate() {
                            let formatted = format_rgctx(rgctx, metadata, il2cpp, executor);
                            image_buffer.push_str(&format!("    [{:3}] {} -> {}\n", i, get_rgctx_type_name(rgctx.rgctx_type), formatted));
                            total_rgctx_entries += 1;
                        }
                        image_buffer.push('\n');
                    }
                }
            }
        }

        if !image_buffer.is_empty() {
            writeln!(file, "─── Image: {} ───", image_name)?;
            write!(file, "{}", image_buffer)?;
        }
    }

    writeln!(file, "Total RGCTX entries: {}\n", total_rgctx_entries)?;
    Ok(())
}

fn dump_method_specs_section(
    file: &mut BufWriter<File>,
    metadata: &mut Metadata,
    il2cpp: &mut Il2Cpp,
    executor: &mut Il2CppExecutor,
) -> Result<()> {
    writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
    writeln!(file, "║  SECTION 2: Instantiated Generic Methods (MethodSpecs)                      ║")?;
    writeln!(file, "║  Every concrete generic method instantiation with its native address         ║")?;
    writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;

    let mut resolved_count = 0u64;
    let total = il2cpp.method_specs.len();

    for i in 0..total {
        let (type_name, method_name) = executor.get_method_spec_name(i, metadata, il2cpp, true);

        let ptr = il2cpp.method_spec_generic_method_pointers
            .get(&i).copied()
            .unwrap_or(0);

        if ptr != 0 {
            let offset = il2cpp.get_rva(ptr);
            writeln!(file, "  0x{:08X} | {}::{}", offset, type_name, method_name)?;
            resolved_count += 1;
        } else {
            writeln!(file, "             | {}::{}", type_name, method_name)?;
        }
    }

    writeln!(file, "\nTotal MethodSpecs: {} ({} with resolved addresses)\n", total, resolved_count)?;
    Ok(())
}

fn dump_custom_attributes_section(
    file: &mut BufWriter<File>,
    metadata: &mut Metadata,
    il2cpp: &mut Il2Cpp,
    executor: &mut Il2CppExecutor,
    _type_def_image_map: &HashMap<usize, (usize, String)>,
) -> Result<()> {
    writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
    writeln!(file, "║  SECTION 3: Custom Attributes                                               ║")?;
    writeln!(file, "║  Reconstructed [SerializeField], [Header], [Range] etc. for every type       ║")?;
    writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;

    let mut attr_count = 0u64;

    for (img_idx, image) in metadata.image_defs.clone().iter().enumerate() {
        let image_name = metadata.get_string_from_index(image.name_index).unwrap_or_default();
        let mut image_buffer = String::new();

        let type_end = image.type_start as usize + image.type_count as usize;
        for type_idx in image.type_start as usize..type_end {
            let type_def = metadata.type_defs[type_idx].clone();
            let type_name = executor.get_type_def_name(&type_def, type_idx, metadata, il2cpp, true, true);
            let mut type_buffer = String::new();
            let mut type_has_attrs = false;

            let type_attrs = collect_attributes(metadata, img_idx, type_def.custom_attribute_index, type_def.token);
            if !type_attrs.is_empty() {
                type_buffer.push_str(&format!("    [Type] {}\n", type_attrs));
                type_has_attrs = true;
                attr_count += 1;
            }

            for field_offset in 0..type_def.field_count as usize {
                let field_idx = type_def.field_start as usize + field_offset;
                if field_idx >= metadata.field_defs.len() { break; }
                let field_def = metadata.field_defs[field_idx].clone();
                let field_attrs = collect_attributes(metadata, img_idx, field_def.custom_attribute_index, field_def.token);
                if !field_attrs.is_empty() {
                    let field_name = metadata.get_string_from_index(field_def.name_index).unwrap_or_else(|_| "?".to_string());
                    type_buffer.push_str(&format!("    [Field] {} -> {}\n", field_name, field_attrs));
                    type_has_attrs = true;
                    attr_count += 1;
                }
            }

            let method_end = type_def.method_start as usize + type_def.method_count as usize;
            for method_idx in type_def.method_start as usize..method_end {
                if method_idx >= metadata.method_defs.len() { break; }
                let method_def = metadata.method_defs[method_idx].clone();
                let method_attrs = collect_attributes(metadata, img_idx, method_def.custom_attribute_index, method_def.token);
                if !method_attrs.is_empty() {
                    let method_name = metadata.get_string_from_index(method_def.name_index as i32).unwrap_or_else(|_| "?".to_string());
                    type_buffer.push_str(&format!("    [Method] {} -> {}\n", method_name, method_attrs));
                    type_has_attrs = true;
                    attr_count += 1;
                }
            }

            for prop_offset in 0..type_def.property_count as usize {
                let prop_idx = type_def.property_start as usize + prop_offset;
                if prop_idx >= metadata.property_defs.len() { break; }
                let prop_def = metadata.property_defs[prop_idx].clone();
                let prop_attrs = collect_attributes(metadata, img_idx, prop_def.custom_attribute_index, prop_def.token);
                if !prop_attrs.is_empty() {
                    let prop_name = metadata.get_string_from_index(prop_def.name_index).unwrap_or_else(|_| "?".to_string());
                    type_buffer.push_str(&format!("    [Property] {} -> {}\n", prop_name, prop_attrs));
                    type_has_attrs = true;
                    attr_count += 1;
                }
            }

            if type_has_attrs {
                image_buffer.push_str(&format!("  {}\n", type_name));
                image_buffer.push_str(&type_buffer);
                image_buffer.push('\n');
            }
        }

        if !image_buffer.is_empty() {
            writeln!(file, "─── Image: {} ───", image_name)?;
            write!(file, "{}", image_buffer)?;
        }
    }

    writeln!(file, "Total attributed members: {}\n", attr_count)?;
    Ok(())
}

fn collect_attributes(
    metadata: &mut Metadata,
    image_index: usize,
    custom_attribute_index: i32,
    token: u32,
) -> String {
    let attr_idx = match metadata.get_custom_attribute_index(image_index, custom_attribute_index, token) {
        Some(idx) => idx,
        None => return String::new(),
    };

    if metadata.version >= 29.0 {
        let mut buf = String::new();
        custom_attribute_reader::format_custom_attribute_data(&mut buf, metadata, attr_idx, "");
        buf.trim().to_string()
    } else {
        let start = attr_idx;
        if start >= metadata.attribute_type_ranges.len() {
            return String::new();
        }
        let range = metadata.attribute_type_ranges[start].clone();
        let mut names = Vec::new();
        for j in 0..range.count as usize {
            let type_idx = range.start as usize + j;
            if type_idx >= metadata.attribute_types.len() { break; }
            let attr_type_index = metadata.attribute_types[type_idx];
            if attr_type_index >= 0 && (attr_type_index as usize) < metadata.type_defs.len() {
                let td = &metadata.type_defs[attr_type_index as usize];
                let name = metadata.get_string_from_index(td.name_index)
                    .unwrap_or_else(|_| "?".to_string());
                names.push(format!("[{}]", name.replace("Attribute", "")));
            }
        }
        names.join(" ")
    }
}

fn dump_string_literals_section(
    file: &mut BufWriter<File>,
    metadata: &mut Metadata,
) -> Result<()> {
    writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
    writeln!(file, "║  SECTION 4: String Literal Table                                            ║")?;
    writeln!(file, "║  Every hardcoded string in the game binary with its index                    ║")?;
    writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;

    let total = metadata.string_literals.len();
    let mut dumped = 0u64;

    for i in 0..total {
        if let Ok(value) = metadata.get_string_literal_from_index(i) {
            if value.is_empty() { continue; }
            let escaped = value
                .replace('\\', "\\\\")
                .replace('\n', "\\n")
                .replace('\r', "\\r")
                .replace('\t', "\\t")
                .replace('\0', "\\0");
            let truncated = if escaped.len() > 200 {
                format!("{}...", safe_truncate(&escaped, 200))
            } else {
                escaped
            };
            writeln!(file, "  [{:6}] \"{}\"", i, truncated)?;
            dumped += 1;
        }
    }

    writeln!(file, "\nTotal string literals: {} ({} non-empty)\n", total, dumped)?;
    Ok(())
}

fn dump_metadata_usages_section(
    file: &mut BufWriter<File>,
    metadata: &mut Metadata,
    il2cpp: &mut Il2Cpp,
    executor: &mut Il2CppExecutor,
    type_def_image_map: &HashMap<usize, (usize, String)>,
    method_def_owner_map: &HashMap<usize, usize>,
) -> Result<()> {
    writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
    writeln!(file, "║  SECTION 5: Metadata Usage Cross-References                                 ║")?;
    writeln!(file, "║  Maps binary addresses to the types, methods, fields and strings they use    ║")?;
    writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;

    if il2cpp.version >= 27.0 {
        dump_v27_metadata_usages(file, metadata, il2cpp, executor, type_def_image_map, method_def_owner_map)?;
    } else if il2cpp.version > 16.0 {
        dump_legacy_metadata_usages(file, metadata, il2cpp, executor, type_def_image_map, method_def_owner_map)?;
    } else {
        writeln!(file, "  (Not available for IL2CPP version < 16)\n")?;
    }

    Ok(())
}

fn dump_vtable_section(
    file: &mut BufWriter<File>,
    metadata: &mut Metadata,
    il2cpp: &mut Il2Cpp,
    executor: &mut Il2CppExecutor,
    type_def_image_map: &HashMap<usize, (usize, String)>,
) -> Result<()> {
    writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
    writeln!(file, "║  SECTION 6: Virtual Method Tables (VTables)                                  ║")?;
    writeln!(file, "║  Full virtual dispatch tables showing which methods override which slots      ║")?;
    writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;

    let mut total_types_with_vtable = 0u64;
    let mut total_vtable_slots = 0u64;

    for (_img_idx, image) in metadata.image_defs.clone().iter().enumerate() {
        let image_name = metadata.get_string_from_index(image.name_index).unwrap_or_default();
        let mut image_buffer = String::new();

        let type_end = image.type_start as usize + image.type_count as usize;
        for type_idx in image.type_start as usize..type_end {
            let type_def = metadata.type_defs[type_idx].clone();
            if type_def.vtable_count == 0 { continue; }

            let type_name = executor.get_type_def_name(&type_def, type_idx, metadata, il2cpp, true, true);

            let parent_name = if type_def.parent_index >= 0 && (type_def.parent_index as usize) < il2cpp.types.len() {
                let parent_type = il2cpp.types[type_def.parent_index as usize].clone();
                executor.get_type_name(&parent_type, metadata, il2cpp, true, false)
            } else {
                String::new()
            };

            let parent_str = if !parent_name.is_empty() {
                format!(" : {}", parent_name)
            } else {
                String::new()
            };

            let mut slot_dic: BTreeMap<u16, (String, String, u64)> = BTreeMap::new();

            for i in 0..type_def.vtable_count as usize {
                let vtable_index = type_def.vtable_start as usize + i;
                if vtable_index >= metadata.vtable_methods.len() { continue; }

                let encoded = metadata.vtable_methods[vtable_index];
                let usage = (encoded & 0xE0000000) >> 29;
                let index = if metadata.version >= 27.0 {
                    (encoded & 0x1FFFFFFE) >> 1
                } else {
                    encoded & 0x1FFFFFFF
                };

                if usage == 6 {
                    if (index as usize) < il2cpp.method_specs.len() {
                        let spec = il2cpp.method_specs[index as usize].clone();
                        let (spec_type_name, spec_method_name) = executor.get_method_spec_name(index as usize, metadata, il2cpp, true);
                        if let Some(md) = metadata.method_defs.get(spec.method_definition_index as usize).cloned() {
                            if md.slot != 0xFFFF {
                                let ptr = il2cpp.method_spec_generic_method_pointers
                                    .get(&(index as usize)).copied()
                                    .filter(|p| *p > 0)
                                    .map(|p| il2cpp.get_rva(p))
                                    .unwrap_or(0);
                                slot_dic.insert(md.slot, (spec_type_name, spec_method_name, ptr));
                            }
                        }
                    }
                } else {
                    if let Some(md) = metadata.method_defs.get(index as usize).cloned() {
                        if md.slot != 0xFFFF {
                            let declaring_type_idx = md.declaring_type as usize;
                            let declaring_name = if let Some(td) = metadata.type_defs.get(declaring_type_idx).cloned() {
                                executor.get_type_def_name(&td, declaring_type_idx, metadata, il2cpp, true, true)
                            } else {
                                "?".to_string()
                            };
                            let method_name = metadata.get_string_from_index(md.name_index as i32).unwrap_or_else(|_| "?".to_string());
                            let img_name = type_def_image_map.get(&declaring_type_idx)
                                .map(|(_, n)| n.clone()).unwrap_or_default();
                            let method_ptr = il2cpp.get_method_pointer(&img_name, &md);
                            let rva = if method_ptr > 0 { il2cpp.get_rva(method_ptr) } else { 0 };
                            slot_dic.insert(md.slot, (declaring_name, method_name, rva));
                        }
                    }
                }
            }

            if !slot_dic.is_empty() {
                image_buffer.push_str(&format!("  {} (Token: 0x{:08X}){} [{} slots]\n",
                    type_name, type_def.token, parent_str, slot_dic.len()));

                for (slot, (declaring, method, rva)) in &slot_dic {
                    let rva_str = if *rva > 0 {
                        format!(" @ 0x{:08X}", rva)
                    } else {
                        String::new()
                    };
                    image_buffer.push_str(&format!("    [{:4}] {}::{}{}\n", slot, declaring, method, rva_str));
                    total_vtable_slots += 1;
                }
                image_buffer.push('\n');
                total_types_with_vtable += 1;
            }
        }

        if !image_buffer.is_empty() {
            writeln!(file, "─── Image: {} ───", image_name)?;
            write!(file, "{}", image_buffer)?;
        }
    }

    writeln!(file, "Total types with VTables: {} ({} total slots)\n", total_types_with_vtable, total_vtable_slots)?;
    Ok(())
}

fn dump_interface_section(
    file: &mut BufWriter<File>,
    metadata: &mut Metadata,
    il2cpp: &mut Il2Cpp,
    executor: &mut Il2CppExecutor,
    _type_def_image_map: &HashMap<usize, (usize, String)>,
) -> Result<()> {
    writeln!(file, "╔══════════════════════════════════════════════════════════════════════════════╗")?;
    writeln!(file, "║  SECTION 7: Interface Implementation Tables                                  ║")?;
    writeln!(file, "║  Which interfaces each class implements and at what vtable offsets            ║")?;
    writeln!(file, "╚══════════════════════════════════════════════════════════════════════════════╝\n")?;

    let mut total_types_with_interfaces = 0u64;
    let mut total_interface_impls = 0u64;

    for (_img_idx, image) in metadata.image_defs.clone().iter().enumerate() {
        let image_name = metadata.get_string_from_index(image.name_index).unwrap_or_default();
        let mut image_buffer = String::new();

        let type_end = image.type_start as usize + image.type_count as usize;
        for type_idx in image.type_start as usize..type_end {
            let type_def = metadata.type_defs[type_idx].clone();
            if type_def.interfaces_count == 0 && type_def.interface_offsets_count == 0 { continue; }

            let type_name = executor.get_type_def_name(&type_def, type_idx, metadata, il2cpp, true, true);
            let mut type_buffer = String::new();
            let mut has_data = false;

            if type_def.interfaces_count > 0 {
                type_buffer.push_str("    Implements:\n");
                for i in 0..type_def.interfaces_count as usize {
                    let iface_idx = type_def.interfaces_start as usize + i;
                    if iface_idx >= metadata.interface_indices.len() { break; }
                    let interface_type_index = metadata.interface_indices[iface_idx];
                    if interface_type_index >= 0 && (interface_type_index as usize) < il2cpp.types.len() {
                        let iface_type = il2cpp.types[interface_type_index as usize].clone();
                        let iface_name = executor.get_type_name(&iface_type, metadata, il2cpp, true, false);
                        type_buffer.push_str(&format!("      - {}\n", iface_name));
                        total_interface_impls += 1;
                        has_data = true;
                    }
                }
            }

            if type_def.interface_offsets_count > 0 {
                type_buffer.push_str("    VTable Offsets:\n");
                for i in 0..type_def.interface_offsets_count as usize {
                    let offset_idx = type_def.interface_offsets_start as usize + i;
                    if offset_idx >= metadata.interface_offsets.len() { break; }
                    let iface_offset = metadata.interface_offsets[offset_idx].clone();
                    if iface_offset.type_index >= 0 && (iface_offset.type_index as usize) < il2cpp.types.len() {
                        let iface_type = il2cpp.types[iface_offset.type_index as usize].clone();
                        let iface_name = executor.get_type_name(&iface_type, metadata, il2cpp, true, false);
                        type_buffer.push_str(&format!("      [{:4}] {}\n", iface_offset.offset, iface_name));
                        has_data = true;
                    }
                }
            }

            if has_data {
                image_buffer.push_str(&format!("  {} (Token: 0x{:08X})\n", type_name, type_def.token));
                image_buffer.push_str(&type_buffer);
                image_buffer.push('\n');
                total_types_with_interfaces += 1;
            }
        }

        if !image_buffer.is_empty() {
            writeln!(file, "─── Image: {} ───", image_name)?;
            write!(file, "{}", image_buffer)?;
        }
    }

    writeln!(file, "Total types with interfaces: {} ({} total implementations)\n",
        total_types_with_interfaces, total_interface_impls)?;
    Ok(())
}

fn dump_legacy_metadata_usages(
    file: &mut BufWriter<File>,
    metadata: &mut Metadata,
    il2cpp: &mut Il2Cpp,
    executor: &mut Il2CppExecutor,
    type_def_image_map: &HashMap<usize, (usize, String)>,
    _method_def_owner_map: &HashMap<usize, usize>,
) -> Result<()> {
    if metadata.metadata_usage_dic.is_empty() {
        writeln!(file, "  (No metadata usages found)\n")?;
        return Ok(());
    }

    let usage_dic = metadata.metadata_usage_dic.clone();
    let mut type_info_entries = Vec::new();
    let mut type_ref_entries = Vec::new();
    let mut method_def_entries = Vec::new();
    let mut field_ref_entries = Vec::new();
    let mut string_lit_entries = Vec::new();
    let mut method_spec_entries = Vec::new();

    for (usage_type, entries) in &usage_dic {
        for (dest_index, source_index) in entries {
            let dest = *dest_index as usize;
            if dest >= il2cpp.metadata_usages.len() { continue; }
            let address = il2cpp.metadata_usages[dest];
            if address == 0 { continue; }
            let rva = il2cpp.get_rva(address);
            let src = *source_index as usize;

            match *usage_type {
                1 => {
                    if src < il2cpp.types.len() {
                        let type_ref = il2cpp.types[src].clone();
                        let type_name = executor.get_type_name(&type_ref, metadata, il2cpp, true, false);
                        type_info_entries.push(format!("  0x{:08X} | {}_TypeInfo", rva, type_name));
                    }
                }
                2 => {
                    if src < il2cpp.types.len() {
                        let type_ref = il2cpp.types[src].clone();
                        let type_name = executor.get_type_name(&type_ref, metadata, il2cpp, true, false);
                        type_ref_entries.push(format!("  0x{:08X} | {}_var (Il2CppType*)", rva, type_name));
                    }
                }
                3 => {
                    if let Some(method_def) = metadata.method_defs.get(src).cloned() {
                        if let Some(type_def) = metadata.type_defs.get(method_def.declaring_type as usize).cloned() {
                            let td_idx = method_def.declaring_type as usize;
                            let type_name = executor.get_type_def_name(&type_def, td_idx, metadata, il2cpp, true, true);
                            let method_name = metadata.get_string_from_index(method_def.name_index as i32).unwrap_or_else(|_| "?".to_string());
                            let img_name = type_def_image_map.get(&td_idx).map(|(_, n)| n.clone()).unwrap_or_default();
                            let method_pointer = il2cpp.get_method_pointer(&img_name, &method_def);
                            let target_str = if method_pointer > 0 {
                                format!(" -> 0x{:08X}", il2cpp.get_rva(method_pointer))
                            } else {
                                String::new()
                            };
                            method_def_entries.push(format!("  0x{:08X} | {}.{}(){}", rva, type_name, method_name, target_str));
                        }
                    }
                }
                4 => {
                    if src < metadata.field_refs.len() {
                        let field_ref = metadata.field_refs[src].clone();
                        if (field_ref.type_index as usize) < il2cpp.types.len() {
                            let il2cpp_type = il2cpp.types[field_ref.type_index as usize].clone();
                            let type_name = executor.get_type_name(&il2cpp_type, metadata, il2cpp, true, false);
                            let klass_idx = il2cpp_type.klass_index() as usize;
                            if let Some(td) = metadata.type_defs.get(klass_idx) {
                                let field_idx = td.field_start as usize + field_ref.field_index as usize;
                                if let Some(fd) = metadata.field_defs.get(field_idx) {
                                    let field_name = metadata.get_string_from_index(fd.name_index).unwrap_or_default();
                                    field_ref_entries.push(format!("  0x{:08X} | {}.{}", rva, type_name, field_name));
                                }
                            }
                        }
                    }
                }
                5 => {
                    if let Ok(string_literal) = metadata.get_string_literal_from_index(src) {
                        let escaped = string_literal.replace('\n', "\\n").replace('\r', "\\r");
                        let truncated = if escaped.len() > 120 {
                            format!("{}...", safe_truncate(&escaped, 120))
                        } else {
                            escaped
                        };
                        string_lit_entries.push(format!("  0x{:08X} | [{}] \"{}\"", rva, src, truncated));
                    }
                }
                6 => {
                    if src < il2cpp.method_specs.len() {
                        let (spec_type_name, spec_method_name) = executor.get_method_spec_name(src, metadata, il2cpp, true);
                        let method_address = il2cpp.method_spec_generic_method_pointers
                            .get(&src).copied()
                            .filter(|p| *p > 0)
                            .map(|p| il2cpp.get_rva(p))
                            .unwrap_or(0);
                        let target_str = if method_address > 0 {
                            format!(" -> 0x{:08X}", method_address)
                        } else {
                            String::new()
                        };
                        method_spec_entries.push(format!("  0x{:08X} | {}.{}(){}", rva, spec_type_name, spec_method_name, target_str));
                    }
                }
                _ => {}
            }
        }
    }

    write_usage_subsection(file, "Type Info References (Il2CppClass*)", &type_info_entries)?;
    write_usage_subsection(file, "Type Ref References (Il2CppType*)", &type_ref_entries)?;
    write_usage_subsection(file, "Method Definition References", &method_def_entries)?;
    write_usage_subsection(file, "Field References", &field_ref_entries)?;
    write_usage_subsection(file, "String Literal Cross-References", &string_lit_entries)?;
    write_usage_subsection(file, "Generic Method (MethodSpec) References", &method_spec_entries)?;

    let total = type_info_entries.len() + type_ref_entries.len() + method_def_entries.len()
        + field_ref_entries.len() + string_lit_entries.len() + method_spec_entries.len();
    writeln!(file, "Total metadata usage cross-references: {}\n", total)?;
    Ok(())
}

fn dump_v27_metadata_usages(
    file: &mut BufWriter<File>,
    metadata: &mut Metadata,
    il2cpp: &mut Il2Cpp,
    executor: &mut Il2CppExecutor,
    type_def_image_map: &HashMap<usize, (usize, String)>,
    _method_def_owner_map: &HashMap<usize, usize>,
) -> Result<()> {
    let pointer_size = if il2cpp.is_32bit { 4u64 } else { 8u64 };
    let data_sections = il2cpp.data_sections.clone();

    let mut type_info_entries = Vec::new();
    let mut type_ref_entries = Vec::new();
    let mut method_def_entries = Vec::new();
    let mut field_ref_entries = Vec::new();
    let mut string_lit_entries = Vec::new();
    let mut method_spec_entries = Vec::new();

    for sec in &data_sections {
        let sec_end = std::cmp::min(sec.offset_end, il2cpp.stream.len() as u64).saturating_sub(pointer_size);
        let mut pos = sec.offset;

        while pos < sec_end {
            il2cpp.stream.set_position(pos);
            let metadata_value = if il2cpp.is_32bit {
                il2cpp.stream.read_u32().unwrap_or(0) as u64
            } else {
                il2cpp.stream.read_u64().unwrap_or(0)
            };
            let saved_pos = il2cpp.stream.position();
            pos = saved_pos;

            if metadata_value >= u32::MAX as u64 { continue; }
            let encoded_token = metadata_value as u32;
            let usage = (encoded_token & 0xE0000000) >> 29;
            if usage == 0 || usage > 6 { continue; }
            let decoded_index = (encoded_token & 0x1FFFFFFE) >> 1;
            let expected = ((usage << 29) | (decoded_index << 1)) + 1;
            if metadata_value != expected as u64 { continue; }

            let addr = pos - pointer_size;
            let va = il2cpp.map_rtva(addr);
            if va == 0 { continue; }
            let rva = il2cpp.get_rva(va);

            match usage {
                1 => {
                    if (decoded_index as usize) < il2cpp.types.len() {
                        let type_ref = il2cpp.types[decoded_index as usize].clone();
                        let type_name = executor.get_type_name(&type_ref, metadata, il2cpp, true, false);
                        type_info_entries.push(format!("  0x{:08X} | {}_TypeInfo", rva, type_name));
                    }
                }
                2 => {
                    if (decoded_index as usize) < il2cpp.types.len() {
                        let type_ref = il2cpp.types[decoded_index as usize].clone();
                        let type_name = executor.get_type_name(&type_ref, metadata, il2cpp, true, false);
                        type_ref_entries.push(format!("  0x{:08X} | {}_var (Il2CppType*)", rva, type_name));
                    }
                }
                3 => {
                    if let Some(method_def) = metadata.method_defs.get(decoded_index as usize).cloned() {
                        if let Some(type_def) = metadata.type_defs.get(method_def.declaring_type as usize).cloned() {
                            let td_idx = method_def.declaring_type as usize;
                            let type_name = executor.get_type_def_name(&type_def, td_idx, metadata, il2cpp, true, true);
                            let method_name = metadata.get_string_from_index(method_def.name_index as i32).unwrap_or_else(|_| "?".to_string());
                            let img_name = type_def_image_map.get(&td_idx).map(|(_, n)| n.clone()).unwrap_or_default();
                            let method_pointer = il2cpp.get_method_pointer(&img_name, &method_def);
                            let target_str = if method_pointer > 0 {
                                format!(" -> 0x{:08X}", il2cpp.get_rva(method_pointer))
                            } else {
                                String::new()
                            };
                            method_def_entries.push(format!("  0x{:08X} | {}.{}(){}", rva, type_name, method_name, target_str));
                        }
                    }
                }
                4 => {
                    if (decoded_index as usize) < metadata.field_refs.len() {
                        let field_ref = metadata.field_refs[decoded_index as usize].clone();
                        if (field_ref.type_index as usize) < il2cpp.types.len() {
                            let il2cpp_type = il2cpp.types[field_ref.type_index as usize].clone();
                            let type_name = executor.get_type_name(&il2cpp_type, metadata, il2cpp, true, false);
                            let klass_idx = il2cpp_type.klass_index() as usize;
                            if let Some(td) = metadata.type_defs.get(klass_idx) {
                                let field_idx = td.field_start as usize + field_ref.field_index as usize;
                                if let Some(fd) = metadata.field_defs.get(field_idx) {
                                    let field_name = metadata.get_string_from_index(fd.name_index).unwrap_or_default();
                                    field_ref_entries.push(format!("  0x{:08X} | {}.{}", rva, type_name, field_name));
                                }
                            }
                        }
                    }
                }
                5 => {
                    if let Ok(string_literal) = metadata.get_string_literal_from_index(decoded_index as usize) {
                        let escaped = string_literal.replace('\n', "\\n").replace('\r', "\\r");
                        let truncated = if escaped.len() > 120 {
                            format!("{}...", safe_truncate(&escaped, 120))
                        } else {
                            escaped
                        };
                        string_lit_entries.push(format!("  0x{:08X} | [{}] \"{}\"", rva, decoded_index, truncated));
                    }
                }
                6 => {
                    if (decoded_index as usize) < il2cpp.method_specs.len() {
                        let (spec_type_name, spec_method_name) = executor.get_method_spec_name(decoded_index as usize, metadata, il2cpp, true);
                        let method_address = il2cpp.method_spec_generic_method_pointers
                            .get(&(decoded_index as usize)).copied()
                            .filter(|p| *p > 0)
                            .map(|p| il2cpp.get_rva(p))
                            .unwrap_or(0);
                        let target_str = if method_address > 0 {
                            format!(" -> 0x{:08X}", method_address)
                        } else {
                            String::new()
                        };
                        method_spec_entries.push(format!("  0x{:08X} | {}.{}(){}", rva, spec_type_name, spec_method_name, target_str));
                    }
                }
                _ => {}
            }

            if il2cpp.stream.position() != saved_pos {
                il2cpp.stream.set_position(saved_pos);
            }
        }
    }

    write_usage_subsection(file, "Type Info References (Il2CppClass*)", &type_info_entries)?;
    write_usage_subsection(file, "Type Ref References (Il2CppType*)", &type_ref_entries)?;
    write_usage_subsection(file, "Method Definition References", &method_def_entries)?;
    write_usage_subsection(file, "Field References", &field_ref_entries)?;
    write_usage_subsection(file, "String Literal Cross-References", &string_lit_entries)?;
    write_usage_subsection(file, "Generic Method (MethodSpec) References", &method_spec_entries)?;

    let total = type_info_entries.len() + type_ref_entries.len() + method_def_entries.len()
        + field_ref_entries.len() + string_lit_entries.len() + method_spec_entries.len();
    writeln!(file, "Total metadata usage cross-references: {}\n", total)?;
    Ok(())
}

fn write_usage_subsection(
    file: &mut BufWriter<File>,
    title: &str,
    entries: &[String],
) -> Result<()> {
    if entries.is_empty() { return Ok(()); }
    writeln!(file, "── {} ({}) ──", title, entries.len())?;
    for entry in entries {
        writeln!(file, "{}", entry)?;
    }
    writeln!(file)?;
    Ok(())
}

fn get_rgctx_type_name(t: i64) -> &'static str {
    match t {
        1 => "TYPE       ",
        2 => "CLASS      ",
        3 => "METHOD     ",
        4 => "ARRAY      ",
        5 => "CONSTRAINED",
        _ => "UNKNOWN    ",
    }
}

fn format_rgctx(
    rgctx: &crate::il2cpp::structures::Il2CppRGCTXDefinition,
    metadata: &mut Metadata,
    il2cpp: &mut Il2Cpp,
    executor: &mut Il2CppExecutor,
) -> String {
    let t = rgctx.rgctx_type;

    if t == 1 || t == 2 || t == 4 {
        let type_idx = rgctx.type_index();
        if type_idx >= 0 && (type_idx as usize) < il2cpp.types.len() {
            let il2cpp_type = il2cpp.types[type_idx as usize].clone();
            return executor.get_type_name(&il2cpp_type, metadata, il2cpp, true, false);
        }
        return format!("InvalidTypeIndex({})", type_idx);
    } else if t == 3 || t == 5 {
        let method_idx = rgctx.method_index();
        if method_idx >= 0 && (method_idx as usize) < il2cpp.method_specs.len() {
            let (t_name, m_name) = executor.get_method_spec_name(method_idx as usize, metadata, il2cpp, true);
            return format!("{}::{}", t_name, m_name);
        }
        return format!("InvalidMethodIndex({})", method_idx);
    }

    format!("UnknownData({})", rgctx.rgctx_type)
}
