use std::collections::{HashMap, HashSet};
use std::fmt::Write as FmtWrite;
use super::cpp_type_dependency_graph::{
    CppCompilerLayout, CppTypeDependencyGraph, GraphField, GraphType, GraphTypeKind,
};
use super::cpp_type_model::{CppTypeGroup, CppTypeGroupRegistry};
use crate::error::{Error, Result};

static C_PRIMITIVE_TYPES: &[&str] = &[
    "void", "bool", "char", "int8_t", "uint8_t", "int16_t", "uint16_t",
    "int32_t", "uint32_t", "int64_t", "uint64_t", "float", "double",
    "intptr_t", "uintptr_t", "Il2CppChar",
];

fn needs_struct_prefix(type_name: &str) -> bool {
    let base = type_name.trim_end_matches('*');
    !C_PRIMITIVE_TYPES.contains(&base)
}

fn strip_pointer(type_name: &str) -> (&str, u32) {
    let bytes = type_name.as_bytes();
    let mut depth: u32 = 0;
    let mut end = bytes.len();
    while end > 0 && bytes[end - 1] == b'*' {
        depth += 1;
        end -= 1;
    }
    let trimmed = type_name[..end].trim_end();
    (trimmed, depth)
}

#[derive(Debug, Clone)]
pub struct CppField {
    pub type_name: String,
    pub field_name: String,
    pub is_value_type: bool,
    pub is_custom_type: bool,
}

#[derive(Debug, Clone)]
pub struct CppVTableEntry {
    pub method_name: Option<String>,
}

#[derive(Debug, Clone)]
pub struct CppRGCTXEntry {
    pub rgctx_type: i64,
    pub type_name: Option<String>,
    pub class_name: Option<String>,
    pub method_name: Option<String>,
}

#[derive(Debug, Clone)]
pub struct CppTypeDecl {
    pub name: String,
    pub is_value_type: bool,
    pub parent: Option<String>,
    pub instance_fields: Vec<CppField>,
    pub static_fields: Vec<CppField>,
    pub vtable: Vec<CppVTableEntry>,
    pub rgctxs: Vec<CppRGCTXEntry>,
}

pub struct CppHeaderEmitter {
    pub compiler_layout: CppCompilerLayout,
    pub is_32bit: bool,
    pub is_pe: bool,
    pub forward_declared: HashSet<String>,
    pub emitted: HashSet<String>,
}

impl CppHeaderEmitter {
    pub fn new(compiler_layout: CppCompilerLayout, is_32bit: bool, is_pe: bool) -> Self {
        Self {
            compiler_layout,
            is_32bit,
            is_pe,
            forward_declared: HashSet::new(),
            emitted: HashSet::new(),
        }
    }

    fn classify_field(field: &CppField, known_value_types: &HashSet<String>) -> GraphField {
        let (base_name, pointer_depth) = strip_pointer(&field.type_name);
        let is_pointer = pointer_depth > 0;
        let element_name = base_name.to_string();
        let is_custom = field.is_custom_type;
        let terminal_is_value = is_custom && known_value_types.contains(&element_name);

        let (element_kind, pointer_terminal_kind) = if is_pointer {
            if !is_custom {
                (GraphTypeKind::Primitive, Some(GraphTypeKind::Primitive))
            } else if terminal_is_value {
                (GraphTypeKind::ValueStruct, Some(GraphTypeKind::ValueStruct))
            } else {
                (GraphTypeKind::ReferenceClass, Some(GraphTypeKind::ReferenceClass))
            }
        } else if !is_custom {
            (GraphTypeKind::Primitive, None)
        } else if field.is_value_type || terminal_is_value {
            (GraphTypeKind::ValueStruct, None)
        } else {
            (GraphTypeKind::ReferenceClass, None)
        };

        GraphField {
            element_name: element_name.clone(),
            element_kind,
            is_instance: true,
            is_pointer,
            pointer_depth,
            is_array: false,
            array_element_name: None,
            array_element_kind: None,
            enum_underlying_name: None,
            pointer_terminal_kind,
            pointer_terminal_name: if is_pointer { Some(element_name) } else { None },
        }
    }

    fn to_graph_type(decl: &CppTypeDecl, known_value_types: &HashSet<String>) -> GraphType {
        let kind = if decl.is_value_type {
            GraphTypeKind::ValueStruct
        } else {
            GraphTypeKind::ReferenceClass
        };

        let mut fields: Vec<GraphField> = decl
            .instance_fields
            .iter()
            .map(|f| Self::classify_field(f, known_value_types))
            .collect();
        for f in fields.iter_mut() {
            f.is_instance = true;
        }

        let mut static_fields: Vec<GraphField> = decl
            .static_fields
            .iter()
            .map(|f| Self::classify_field(f, known_value_types))
            .collect();
        for f in static_fields.iter_mut() {
            f.is_instance = false;
        }

        GraphType {
            name: decl.name.clone(),
            kind,
            parent: decl.parent.clone(),
            enum_underlying_name: None,
            fields,
            static_fields,
        }
    }

    pub fn emit_all_with_groups(
        &mut self,
        types: &[CppTypeDecl],
        groups: Option<&CppTypeGroupRegistry>,
    ) -> Result<String> {
        let known_type_names: HashSet<String> = types.iter().map(|t| t.name.clone()).collect();
        let known_value_types: HashSet<String> = types
            .iter()
            .filter(|t| t.is_value_type)
            .map(|t| t.name.clone())
            .collect();
        let type_map: HashMap<String, &CppTypeDecl> =
            types.iter().map(|t| (t.name.clone(), t)).collect();

        let mut graph = CppTypeDependencyGraph::new();
        let graph_types: Vec<GraphType> = types
            .iter()
            .map(|d| Self::to_graph_type(d, &known_value_types))
            .collect();
        graph.add_types_bulk(&graph_types);

        let ordered_names = graph
            .derive_dependency_order()
            .map_err(|cycle| Error::CyclicDependency(cycle.to_string()))?;
        let fwd_candidates = graph.get_forward_declaration_candidates();

        let total_estimate = types.len() * 512;
        let mut buf = String::with_capacity(total_estimate);

        let mut sorted_fwd: Vec<&String> = fwd_candidates.iter().collect();
        sorted_fwd.sort();
        if !sorted_fwd.is_empty() {
            Self::write_section_banner(&mut buf, CppTypeGroup::RequiredForwardDefinitions.section_header());
            for fwd_name in sorted_fwd {
                if !known_type_names.contains(fwd_name)
                    && self.forward_declared.insert(fwd_name.clone())
                {
                    writeln!(buf, "struct {};", fwd_name).ok();
                }
            }
            writeln!(buf).ok();
        }

        let group_order = [
            CppTypeGroup::TypesFromMethods,
            CppTypeGroup::TypesFromGenericMethods,
            CppTypeGroup::TypesFromUsages,
            CppTypeGroup::UnusedConcreteTypes,
        ];

        if let Some(reg) = groups {
            let assigned: HashMap<String, CppTypeGroup> = ordered_names
                .iter()
                .filter_map(|n| reg.group_of(n).map(|g| (n.clone(), g)))
                .collect();

            for &group in &group_order {
                let mut wrote_banner = false;
                for name in &ordered_names {
                    if assigned.get(name).copied() != Some(group) {
                        continue;
                    }
                    if let Some(decl) = type_map.get(name) {
                        if !self.emitted.insert(decl.name.clone()) {
                            continue;
                        }
                        if !wrote_banner {
                            Self::write_section_banner(&mut buf, group.section_header());
                            wrote_banner = true;
                        }
                        self.emit_type_decl(&mut buf, decl, &type_map);
                        writeln!(buf).ok();
                    }
                }
            }

            let mut wrote_unassigned = false;
            for name in &ordered_names {
                if assigned.contains_key(name) {
                    continue;
                }
                if let Some(decl) = type_map.get(name) {
                    if !self.emitted.insert(decl.name.clone()) {
                        continue;
                    }
                    if !wrote_unassigned {
                        Self::write_section_banner(&mut buf, "Unclassified application types");
                        wrote_unassigned = true;
                    }
                    self.emit_type_decl(&mut buf, decl, &type_map);
                    writeln!(buf).ok();
                }
            }
        } else {
            for name in &ordered_names {
                if let Some(decl) = type_map.get(name) {
                    if self.emitted.insert(decl.name.clone()) {
                        self.emit_type_decl(&mut buf, decl, &type_map);
                        writeln!(buf).ok();
                    }
                }
            }
        }

        Ok(buf)
    }

    fn write_section_banner(buf: &mut String, name: &str) {
        writeln!(buf, "// ******************************************************************************").ok();
        writeln!(buf, "// * {name}").ok();
        writeln!(buf, "// ******************************************************************************").ok();
        writeln!(buf).ok();
    }

    pub fn emit_all(&mut self, types: &[CppTypeDecl]) -> Result<String> {
        let known_type_names: HashSet<String> = types.iter().map(|t| t.name.clone()).collect();
        let known_value_types: HashSet<String> = types
            .iter()
            .filter(|t| t.is_value_type)
            .map(|t| t.name.clone())
            .collect();
        let type_map: HashMap<String, &CppTypeDecl> =
            types.iter().map(|t| (t.name.clone(), t)).collect();

        let mut graph = CppTypeDependencyGraph::new();
        let graph_types: Vec<GraphType> = types
            .iter()
            .map(|d| Self::to_graph_type(d, &known_value_types))
            .collect();
        graph.add_types_bulk(&graph_types);

        let ordered_names = graph
            .derive_dependency_order()
            .map_err(|cycle| Error::CyclicDependency(cycle.to_string()))?;
        let fwd_candidates = graph.get_forward_declaration_candidates();

        let total_estimate = types.len() * 512;
        let mut buf = String::with_capacity(total_estimate);

        let mut sorted_fwd: Vec<&String> = fwd_candidates.iter().collect();
        sorted_fwd.sort();
        for fwd_name in sorted_fwd {
            if !known_type_names.contains(fwd_name)
                && self.forward_declared.insert(fwd_name.clone())
            {
                writeln!(buf, "struct {};", fwd_name).ok();
            }
        }
        if !fwd_candidates.is_empty() {
            writeln!(buf).ok();
        }

        for name in &ordered_names {
            if let Some(decl) = type_map.get(name) {
                if self.emitted.insert(decl.name.clone()) {
                    self.emit_type_decl(&mut buf, decl, &type_map);
                    writeln!(buf).ok();
                }
            }
        }

        Ok(buf)
    }

    fn emit_type_decl(
        &mut self,
        buf: &mut String,
        decl: &CppTypeDecl,
        type_map: &HashMap<String, &CppTypeDecl>,
    ) {
        self.emit_fields_struct(buf, decl, type_map);
        self.emit_rgctx_struct(buf, decl);
        self.emit_vtable_struct(buf, decl);
        self.emit_class_struct(buf, decl);
        self.emit_object_struct(buf, decl);
        self.emit_static_fields_struct(buf, decl);
    }

    fn emit_fields_struct(
        &mut self,
        buf: &mut String,
        decl: &CppTypeDecl,
        type_map: &HashMap<String, &CppTypeDecl>,
    ) {
        match self.compiler_layout {
            CppCompilerLayout::GCC => {
                self.emit_fields_struct_gcc(buf, decl, type_map);
            }
            CppCompilerLayout::MSVC => {
                self.emit_fields_struct_msvc(buf, decl);
            }
        }
    }

    fn emit_fields_struct_gcc(
        &mut self,
        buf: &mut String,
        decl: &CppTypeDecl,
        type_map: &HashMap<String, &CppTypeDecl>,
    ) {
        if !decl.is_value_type && decl.parent.is_none() {
            let align = if self.is_32bit { 4 } else { 8 };
            writeln!(
                buf,
                "struct __attribute__((aligned({}))) {}_Fields {{",
                align, decl.name
            )
            .ok();
        } else {
            writeln!(buf, "struct {}_Fields {{", decl.name).ok();
        }

        self.write_flattened_parent_fields(buf, decl, type_map);

        for field in &decl.instance_fields {
            Self::write_field_line(buf, field);
        }

        if decl.instance_fields.is_empty() && decl.parent.is_none() {
            writeln!(buf, "\tuint8_t __padding_empty;").ok();
        }

        writeln!(buf, "}};").ok();
    }

    fn write_flattened_parent_fields(
        &self,
        buf: &mut String,
        decl: &CppTypeDecl,
        type_map: &HashMap<String, &CppTypeDecl>,
    ) {
        if let Some(parent_name) = &decl.parent {
            if let Some(parent_decl) = type_map.get(parent_name) {
                self.write_flattened_parent_fields(buf, parent_decl, type_map);
                for field in &parent_decl.instance_fields {
                    Self::write_field_line(buf, field);
                }
            }
        }
    }

    fn emit_fields_struct_msvc(&mut self, buf: &mut String, decl: &CppTypeDecl) {
        if let Some(parent_name) = &decl.parent {
            writeln!(
                buf,
                "struct {}_Fields : {}_Fields {{",
                decl.name, parent_name
            )
            .ok();
        } else if !decl.is_value_type && self.is_pe {
            let align = if self.is_32bit { 4 } else { 8 };
            writeln!(
                buf,
                "struct __declspec(align({})) {}_Fields {{",
                align, decl.name
            )
            .ok();
        } else {
            writeln!(buf, "struct {}_Fields {{", decl.name).ok();
        }

        for field in &decl.instance_fields {
            Self::write_field_line(buf, field);
        }

        if decl.instance_fields.is_empty() && decl.parent.is_none() {
            writeln!(buf, "\tuint8_t __padding_empty;").ok();
        }

        writeln!(buf, "}};").ok();
    }

    fn write_field_line(buf: &mut String, field: &CppField) {
        if field.is_custom_type && needs_struct_prefix(&field.type_name) {
            writeln!(buf, "\tstruct {} {};", field.type_name, field.field_name).ok();
        } else {
            writeln!(buf, "\t{} {};", field.type_name, field.field_name).ok();
        }
    }

    fn emit_rgctx_struct(&self, buf: &mut String, decl: &CppTypeDecl) {
        if decl.rgctxs.is_empty() {
            return;
        }
        writeln!(buf, "struct {}_RGCTXs {{", decl.name).ok();
        for (i, rgctx) in decl.rgctxs.iter().enumerate() {
            match rgctx.rgctx_type as i32 {
                1 => {
                    let tn = rgctx.type_name.as_deref().unwrap_or("unknown");
                    writeln!(buf, "\tIl2CppType* _{i}_{tn};").ok();
                }
                2 => {
                    let cn = rgctx.class_name.as_deref().unwrap_or("unknown");
                    writeln!(buf, "\tIl2CppClass* _{i}_{cn};").ok();
                }
                3 => {
                    let mn = rgctx.method_name.as_deref().unwrap_or("unknown");
                    writeln!(buf, "\tMethodInfo* _{i}_{mn};").ok();
                }
                _ => {}
            }
        }
        writeln!(buf, "}};").ok();
    }

    fn emit_vtable_struct(&self, buf: &mut String, decl: &CppTypeDecl) {
        if decl.vtable.is_empty() {
            return;
        }
        writeln!(buf, "struct {}_VTable {{", decl.name).ok();
        for (i, entry) in decl.vtable.iter().enumerate() {
            let method_name = entry.method_name.as_deref().unwrap_or("unknown");
            writeln!(buf, "\tVirtualInvokeData _{i}_{method_name};").ok();
        }
        writeln!(buf, "}};").ok();
    }

    fn emit_class_struct(&self, buf: &mut String, decl: &CppTypeDecl) {
        writeln!(buf, "struct {}_c {{", decl.name).ok();
        writeln!(buf, "\tIl2CppClass_1 _1;").ok();

        if !decl.static_fields.is_empty() {
            writeln!(buf, "\tstruct {}_StaticFields* static_fields;", decl.name).ok();
        } else {
            writeln!(buf, "\tvoid* static_fields;").ok();
        }

        if !decl.rgctxs.is_empty() {
            writeln!(buf, "\t{}_RGCTXs* rgctx_data;", decl.name).ok();
        } else {
            writeln!(buf, "\tIl2CppRGCTXData* rgctx_data;").ok();
        }

        writeln!(buf, "\tIl2CppClass_2 _2;").ok();

        if !decl.vtable.is_empty() {
            writeln!(buf, "\t{}_VTable vtable;", decl.name).ok();
        } else {
            writeln!(buf, "\tVirtualInvokeData vtable[32];").ok();
        }

        writeln!(buf, "}};").ok();
    }

    fn emit_object_struct(&self, buf: &mut String, decl: &CppTypeDecl) {
        writeln!(buf, "struct {}_o {{", decl.name).ok();
        if !decl.is_value_type {
            writeln!(buf, "\t{}_c *klass;", decl.name).ok();
            writeln!(buf, "\tvoid *monitor;").ok();
        }
        writeln!(buf, "\t{}_Fields fields;", decl.name).ok();
        writeln!(buf, "}};").ok();
    }

    fn emit_static_fields_struct(&self, buf: &mut String, decl: &CppTypeDecl) {
        if decl.static_fields.is_empty() {
            return;
        }
        writeln!(buf, "struct {}_StaticFields {{", decl.name).ok();
        for field in &decl.static_fields {
            Self::write_field_line(buf, field);
        }
        writeln!(buf, "}};").ok();
    }
}
