use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Fields, Type, parse_str};

use crate::utils::{
    get_fields_processing_struct, get_pang_bitfield_attrs,
    get_struct_groups, get_inner_type_from_attrs,
};

pub fn generate_field_to_tree(
    accessor: &proc_macro2::TokenStream,
    attrs: &[syn::Attribute],
) -> proc_macro2::TokenStream {
    // Check for inner type annotation
    if let Some(inner_type_str) = get_inner_type_from_attrs(attrs) {
        // Parse the inner type
        let inner_type: Type = parse_str(&inner_type_str)
            .expect(&format!("Failed to parse inner type: {}", inner_type_str));

        // Generate to_tree_as call (note the & before accessor)
        return quote! {
            aglaea::ToTreeAs::<#inner_type>::to_tree_as(&#accessor)
        };
    }

    // Default: just call to_tree()
    quote! { #accessor.to_tree() }
}

pub fn derive_to_tree_impl(input: DeriveInput) -> proc_macro2::TokenStream {
    // Get the name of the struct/enum (e.g., "Header")
    let struct_name = &input.ident;
    let struct_label = struct_name.to_string();

    // Split generics (e.g., <T>) to ensure the generated `impl` block compiles correctly.
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

    // Generate the body of the `to_tree()` function
    let body = match &input.data {
        // ====
        // CASE: Struct
        // Example: struct Header { ver: u8, len: u16 }
        // Target Tree: Node("Header")
        //              |-> Node("Header.ver") -> [ValueNode]
        //              |-> Node("Header.len") -> [ValueNode]
        // ====
        Data::Struct(data) => {
            // Extract all fields (named or unnamed) standardizing access (self.field).
            let raw_fields = get_fields_processing_struct(data);

            let groups = get_struct_groups(data);

            let nodes = groups.iter().map(|group| {
                let node_name = format!("{}.{}", struct_label, group.label);

                if group.is_bitfield {
                    // === Generate bitfield ===
                    let (container_ty, wrapper_ty) = if group.max_bits <= 8 {
                        (quote! { u8 }, quote! { aglaea::U8 })
                    } else if group.max_bits <= 16 {
                        (quote! { u16 }, quote! { aglaea::U16be })
                    } else if group.max_bits <= 32 {
                        (quote! { u32 }, quote! { aglaea::U32be })
                    } else {
                        (quote! { u64 }, quote! { aglaea::U64be })
                    };

                    let merge_steps = group.members.iter().map(|&idx| {
                        let (accessor, _, attrs, _) = &raw_fields[idx];
                        let bf = get_pang_bitfield_attrs(attrs).unwrap();
                        let mask = (1u128 << (bf.end - bf.start)) - 1;
                        let mask_lit = proc_macro2::Literal::u128_unsuffixed(mask);
                        let shift_lit = proc_macro2::Literal::usize_unsuffixed(bf.start);

                        quote! {
                            accumulator |= ((#accessor as #container_ty) & #mask_lit) << #shift_lit;
                        }
                    });

                    quote! {
                        {
                            let mut accumulator: #container_ty = 0;
                            #(#merge_steps)*
                            aglaea::new_node(aglaea::ntc(#node_name, Some(vec![#wrapper_ty(accumulator).to_tree()])))
                        }
                    }
                } else {
                    // === Generate normal fields ===
                    let idx = group.members[0];
                    let (accessor, _, attrs, _) = &raw_fields[idx];
                    let value_tree = crate::tree::generate_field_to_tree(accessor, attrs);
                    quote! {
                        aglaea::new_node(aglaea::ntc(#node_name, Some(vec![#value_tree])))
                    }
                }
            });

            quote! {
                let children = vec![ #(#nodes),* ];
                aglaea::new_node(aglaea::ntc(#struct_label, Some(children)))
            }
        }
        Data::Enum(data) => {
            let match_arms = data.variants.iter().map(|variant| {
                    let variant_ident = &variant.ident;
                    let variant_label = format!("{}.{}", struct_label, variant_ident);

                    // 1. Prepare the pattern matching and the corresponding accessors.
                    let (pattern, accessors, field_attrs_list) = match &variant.fields {
                        Fields::Named(fields) => {
                            let names: Vec<_> = fields.named.iter().map(|f| f.ident.clone().unwrap()).collect();
                            // Visit Named Fields by name
                            let accs: Vec<_> = names.iter().map(|n| quote! { #n }).collect();
                            let attrs: Vec<_> = fields.named.iter().map(|f| &f.attrs).collect();
                            (
                                quote! { Self::#variant_ident { #(#names),* } },
                                accs,
                                attrs,
                            )
                        }
                        Fields::Unnamed(fields) => {
                            let vars: Vec<_> = (0..fields.unnamed.len())
                                .map(|i| format_ident!("_{}", i))
                                .collect();
                            // Visit Unnamed Fields by _0, _1
                            let accs: Vec<_> = vars.iter().map(|v| quote! { #v }).collect();
                            let attrs: Vec<_> = fields.unnamed.iter().map(|f| &f.attrs).collect();
                            (
                                quote! { Self::#variant_ident ( #(#vars),* ) },
                                accs,
                                attrs,
                            )
                        }
                        Fields::Unit => (quote! { Self::#variant_ident }, vec![], vec![]),
                    };

                    // 2. Group fields (Bitfield supported)
                    let groups = crate::utils::group_fields(&variant.fields);

                    // 3. Generate node construction logic.
                    let nodes = groups.iter().map(|group| {
                        // Node name: Enum.Variant.0 or Enum.Variant.field
                        let node_name = format!("{}.{}", variant_label, group.label);

                        if group.is_bitfield {
                            let (container_ty, wrapper_ty) = if group.max_bits <= 8 {
                                (quote!{ u8 }, quote!{ aglaea::U8 })
                            } else if group.max_bits <= 16 {
                                (quote!{ u16 }, quote!{ aglaea::U16be })
                            } else if group.max_bits <= 32 {
                                (quote!{ u32 }, quote!{ aglaea::U32be })
                            } else {
                                (quote!{ u64 }, quote!{ aglaea::U64be })
                            };

                            let merge_steps = group.members.iter().map(|&idx| {
                                let accessor = &accessors[idx];
                                let attrs = field_attrs_list[idx];
                                let bf = get_pang_bitfield_attrs(attrs).unwrap();
                                let mask = (1u128 << (bf.end - bf.start)) - 1;
                                let mask_lit = proc_macro2::Literal::u128_unsuffixed(mask);
                                let shift_lit = proc_macro2::Literal::usize_unsuffixed(bf.start);

                                quote! {
                                    accumulator |= ((*#accessor) as #container_ty & #mask_lit) << #shift_lit;
                                }
                            });

                            quote! {
                                {
                                    let mut accumulator: #container_ty = 0;
                                    #(#merge_steps)*
                                    aglaea::new_node(aglaea::ntc(#node_name, Some(vec![#wrapper_ty(accumulator).to_tree()])))
                                }
                            }
                        } else {
                            let idx = group.members[0];
                            let accessor = &accessors[idx];
                            let attrs = field_attrs_list[idx];

                            let value_tree = crate::tree::generate_field_to_tree(accessor, attrs);

                            quote! {
                                aglaea::new_node(aglaea::ntc(#node_name, Some(vec![#value_tree])))
                            }
                        }
                    });

                    // 4. Assemble the final structure.
                    quote! {
                        #pattern => {
                            let fields_children = vec![
                                #(#nodes),*
                            ];

                            let variant_node = aglaea::new_node(aglaea::ntc(#variant_label, Some(fields_children)));

                            aglaea::new_node(aglaea::ntc(#struct_label, Some(vec![variant_node])))
                        }
                    }
                });

            quote! {
                match self {
                    #(#match_arms),*
                }
            }
        }
        _ => panic!("ToTree only supports Struct and Enum"),
    };

    quote! {
        impl #impl_generics aglaea::ToTree for #struct_name #ty_generics #where_clause {
            fn to_tree(&self) -> std::sync::Arc<aglaea::DerivationTree> {
                #body
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use syn::parse_quote;

    #[test]
    fn test_to_tree_middleware_naming() {
        let input: DeriveInput = parse_quote! {
            struct Header { ver: u8, len: u16 }
        };
        let output = derive_to_tree_impl(input);
        let s = output.to_string();
        // This will generate middleware `Header.ver` and `Header.len`
        println!("{}", s);
        assert!(s.contains("aglaea :: ntc (\"Header.ver\""));
        assert!(s.contains("aglaea :: ntc (\"Header.len\""));
    }
}
