// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

use super::field_meta::parse_field_meta;
use super::util::{
    classify_trait_object_field, create_wrapper_types_arc, create_wrapper_types_rc,
    determine_field_ref_mode, extract_type_name, gen_struct_version_hash_ts, get_field_accessor,
    get_field_name, get_filtered_source_fields_iter, get_option_inner_primitive_name,
    get_primitive_writer_method_with_encoding, get_struct_name, get_type_id_by_type_ast,
    is_debug_enabled, is_direct_primitive_type, is_option_encoding_primitive, FieldRefMode,
    StructField,
};
use crate::util::SourceField;
use fory_core::types::TypeId;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::Field;

pub fn gen_reserved_space(fields: &[&Field]) -> TokenStream {
    let reserved_size_expr: Vec<_> = fields.iter().map(|field| {
        let ty = &field.ty;
        match classify_trait_object_field(ty) {
            StructField::BoxDyn => {
                quote! {
                    fory_core::types::SIZE_OF_REF_AND_TYPE
                }
            }
            StructField::RcDyn(trait_name) => {
                let types = create_wrapper_types_rc(&trait_name);
                let wrapper_ty = types.wrapper_ty;
                quote! {
                    <#wrapper_ty as fory_core::Serializer>::fory_reserved_space() + fory_core::types::SIZE_OF_REF_AND_TYPE
                }
            }
            StructField::ArcDyn(trait_name) => {
                let types = create_wrapper_types_arc(&trait_name);
                let wrapper_ty = types.wrapper_ty;
                quote! {
                    <#wrapper_ty as fory_core::Serializer>::fory_reserved_space() + fory_core::types::SIZE_OF_REF_AND_TYPE
                }
            }
            StructField::VecRc(trait_name) => {
                let types = create_wrapper_types_rc(&trait_name);
                let wrapper_ty = types.wrapper_ty;
                quote! {
                    <Vec<#wrapper_ty> as fory_core::Serializer>::fory_reserved_space() + fory_core::types::SIZE_OF_REF_AND_TYPE
                }
            }
            StructField::VecArc(trait_name) => {
                let types = create_wrapper_types_arc(&trait_name);
                let wrapper_ty = types.wrapper_ty;
                quote! {
                    <Vec<#wrapper_ty> as fory_core::Serializer>::fory_reserved_space() + fory_core::types::SIZE_OF_REF_AND_TYPE
                }
            }
            StructField::VecBox(_) => {
                // Vec<Box<dyn Any>> uses standard Vec serialization
                quote! {
                    <#ty as fory_core::Serializer>::fory_reserved_space() + fory_core::types::SIZE_OF_REF_AND_TYPE
                }
            }
            StructField::HashMapBox(_, _) => {
                // HashMap<K, Box<dyn Any>> uses standard HashMap serialization
                quote! {
                    <#ty as fory_core::Serializer>::fory_reserved_space() + fory_core::types::SIZE_OF_REF_AND_TYPE
                }
            }
            StructField::HashMapRc(key_ty, trait_name) => {
                let types = create_wrapper_types_rc(&trait_name);
                let wrapper_ty = types.wrapper_ty;
                quote! {
                    <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_reserved_space() + fory_core::types::SIZE_OF_REF_AND_TYPE
                }
            }
            StructField::HashMapArc(key_ty, trait_name) => {
                let types = create_wrapper_types_arc(&trait_name);
                let wrapper_ty = types.wrapper_ty;
                quote! {
                    <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_reserved_space() + fory_core::types::SIZE_OF_REF_AND_TYPE
                }
            }
            StructField::Forward => {
                quote! {
                    <#ty as fory_core::Serializer>::fory_reserved_space() + fory_core::types::SIZE_OF_REF_AND_TYPE
                }
            }
            _ => {
                quote! {
                    <#ty as fory_core::Serializer>::fory_reserved_space() + fory_core::types::SIZE_OF_REF_AND_TYPE
                }
            }
        }
    }).collect();
    if reserved_size_expr.is_empty() {
        quote! { 0 }
    } else {
        quote! { #(#reserved_size_expr)+* }
    }
}

pub fn gen_write_type_info() -> TokenStream {
    quote! {
        fory_core::serializer::struct_::write_type_info::<Self>(context)
    }
}

/// Generate write code for a field using index-based access (supports tuple structs)
pub fn gen_write_field_with_index(field: &Field, index: usize, use_self: bool) -> TokenStream {
    let value_ts = get_field_accessor(field, index, use_self);
    let field_name = get_field_name(field, index);
    gen_write_field_impl(field, &value_ts, &field_name, use_self)
}

pub fn gen_write_field(field: &Field, ident: &Ident, use_self: bool) -> TokenStream {
    let value_ts = if use_self {
        quote! { self.#ident }
    } else {
        quote! { #ident }
    };
    let field_name = ident.to_string();
    gen_write_field_impl(field, &value_ts, &field_name, use_self)
}

fn gen_write_field_impl(
    field: &Field,
    value_ts: &TokenStream,
    field_name: &str,
    use_self: bool,
) -> TokenStream {
    let ty = &field.ty;
    // Determine RefMode from field meta (respects #[fory(ref=false)] attribute)
    let ref_mode = determine_field_ref_mode(field);
    let base = match classify_trait_object_field(ty) {
        StructField::BoxDyn => {
            // Box<dyn Trait> - respect field meta for ref mode
            quote! {
                <#ty as fory_core::Serializer>::fory_write(&#value_ts, context, #ref_mode, true, false)?;
            }
        }
        StructField::RcDyn(trait_name) => {
            // Rc<dyn Trait> - respect field meta for ref mode
            let types = create_wrapper_types_rc(&trait_name);
            let wrapper_ty = types.wrapper_ty;
            let trait_ident = types.trait_ident;
            quote! {
                let wrapper = #wrapper_ty::from(#value_ts.clone() as std::rc::Rc<dyn #trait_ident>);
                <#wrapper_ty as fory_core::Serializer>::fory_write(&wrapper, context, #ref_mode, true, false)?;
            }
        }
        StructField::ArcDyn(trait_name) => {
            // Arc<dyn Trait> - respect field meta for ref mode
            let types = create_wrapper_types_arc(&trait_name);
            let wrapper_ty = types.wrapper_ty;
            let trait_ident = types.trait_ident;
            quote! {
                let wrapper = #wrapper_ty::from(#value_ts.clone() as std::sync::Arc<dyn #trait_ident>);
                <#wrapper_ty as fory_core::Serializer>::fory_write(&wrapper, context, #ref_mode, true, false)?;
            }
        }
        StructField::VecRc(trait_name) => {
            // Vec<Rc<dyn Trait>> - respect field meta for ref mode
            let types = create_wrapper_types_rc(&trait_name);
            let wrapper_ty = types.wrapper_ty;
            let trait_ident = types.trait_ident;
            quote! {
                let wrapper_vec: Vec<#wrapper_ty> = #value_ts.iter()
                    .map(|item| #wrapper_ty::from(item.clone() as std::rc::Rc<dyn #trait_ident>))
                    .collect();
                <Vec<#wrapper_ty> as fory_core::Serializer>::fory_write(&wrapper_vec, context, #ref_mode, false, true)?;
            }
        }
        StructField::VecArc(trait_name) => {
            // Vec<Arc<dyn Trait>> - respect field meta for ref mode
            let types = create_wrapper_types_arc(&trait_name);
            let wrapper_ty = types.wrapper_ty;
            let trait_ident = types.trait_ident;
            quote! {
                let wrapper_vec: Vec<#wrapper_ty> = #value_ts.iter()
                    .map(|item| #wrapper_ty::from(item.clone() as std::sync::Arc<dyn #trait_ident>))
                    .collect();
                <Vec<#wrapper_ty> as fory_core::Serializer>::fory_write(&wrapper_vec, context, #ref_mode, false, true)?;
            }
        }
        StructField::VecBox(_) => {
            // Vec<Box<dyn Any>> - respect field meta for ref mode
            if ref_mode == FieldRefMode::None {
                quote! {
                    <#ty as fory_core::Serializer>::fory_write_data_generic(&#value_ts, context, true)?;
                }
            } else {
                quote! {
                    <#ty as fory_core::Serializer>::fory_write(&#value_ts, context, #ref_mode, false, true)?;
                }
            }
        }
        StructField::HashMapBox(_, _) => {
            // HashMap<K, Box<dyn Any>> - respect field meta for ref mode
            if ref_mode == FieldRefMode::None {
                quote! {
                    <#ty as fory_core::Serializer>::fory_write_data_generic(&#value_ts, context, true)?;
                }
            } else {
                quote! {
                    <#ty as fory_core::Serializer>::fory_write(&#value_ts, context, #ref_mode, false, true)?;
                }
            }
        }
        StructField::HashMapRc(key_ty, trait_name) => {
            // HashMap<K, Rc<dyn Trait>> - respect field meta for ref mode
            let types = create_wrapper_types_rc(&trait_name);
            let wrapper_ty = types.wrapper_ty;
            let trait_ident = types.trait_ident;
            quote! {
                let wrapper_map: std::collections::HashMap<#key_ty, #wrapper_ty> = #value_ts.iter()
                    .map(|(k, v)| (k.clone(), #wrapper_ty::from(v.clone() as std::rc::Rc<dyn #trait_ident>)))
                    .collect();
                <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_write(&wrapper_map, context, #ref_mode, false, true)?;
            }
        }
        StructField::HashMapArc(key_ty, trait_name) => {
            // HashMap<K, Arc<dyn Trait>> - respect field meta for ref mode
            let types = create_wrapper_types_arc(&trait_name);
            let wrapper_ty = types.wrapper_ty;
            let trait_ident = types.trait_ident;
            quote! {
                let wrapper_map: std::collections::HashMap<#key_ty, #wrapper_ty> = #value_ts.iter()
                    .map(|(k, v)| (k.clone(), #wrapper_ty::from(v.clone() as std::sync::Arc<dyn #trait_ident>)))
                    .collect();
                <std::collections::HashMap<#key_ty, #wrapper_ty> as fory_core::Serializer>::fory_write(&wrapper_map, context, #ref_mode, false, true)?;
            }
        }
        StructField::Forward => {
            // Forward types (trait objects, forward references) - polymorphic, always need type info
            quote! {
                <#ty as fory_core::Serializer>::fory_write(&#value_ts, context, #ref_mode, true, false)?;
            }
        }
        _ => {
            let type_id = get_type_id_by_type_ast(ty);
            let meta = parse_field_meta(field).unwrap_or_default();

            // Check if this is Option<u32> or Option<u64> with encoding attributes
            // These need special inline handling because the generic Option<T> serializer
            // doesn't know about field-level encoding attributes.
            if is_option_encoding_primitive(ty, &meta) {
                let inner_name = get_option_inner_primitive_name(ty).unwrap();
                let writer_method = get_primitive_writer_method_with_encoding(inner_name, &meta);
                let writer_ident = syn::Ident::new(writer_method, proc_macro2::Span::call_site());
                // For Option<primitive>, write null flag first, then value if Some
                quote! {
                    if let Some(v) = &#value_ts {
                        context.writer.write_i8(fory_core::RefFlag::NotNullValue as i8);
                        context.writer.#writer_ident(*v);
                    } else {
                        context.writer.write_i8(fory_core::RefFlag::Null as i8);
                    }
                }
            }
            // Check if this is a direct primitive type that can use direct writer calls
            // Only apply when ref_mode is None (no ref tracking needed)
            else if ref_mode == FieldRefMode::None && is_direct_primitive_type(ty) {
                let type_name = extract_type_name(ty);
                if type_name == "String" {
                    // String: call fory_write_data directly
                    quote! {
                        <#ty as fory_core::Serializer>::fory_write_data(&#value_ts, context)?;
                    }
                } else {
                    // Numeric primitives: use direct buffer methods
                    // For u32/u64, consider encoding attributes
                    let writer_method =
                        get_primitive_writer_method_with_encoding(&type_name, &meta);
                    let writer_ident =
                        syn::Ident::new(writer_method, proc_macro2::Span::call_site());
                    // For primitives:
                    // - use_self=true: #value_ts is `self.field`, which is T (copy happens automatically)
                    // - use_self=false: #value_ts is `field` from pattern match on &self, which is &T
                    let value_expr = if use_self {
                        quote! { #value_ts }
                    } else {
                        quote! { *#value_ts }
                    };
                    quote! {
                        context.writer.#writer_ident(#value_expr);
                    }
                }
            } else if type_id == TypeId::LIST as u32
                || type_id == TypeId::SET as u32
                || type_id == TypeId::MAP as u32
            {
                // For collections - respect field meta for ref mode
                if ref_mode == FieldRefMode::None {
                    quote! {
                        <#ty as fory_core::Serializer>::fory_write_data_generic(&#value_ts, context, true)?;
                    }
                } else {
                    quote! {
                        <#ty as fory_core::Serializer>::fory_write(&#value_ts, context, #ref_mode, false, true)?;
                    }
                }
            } else {
                // Custom types (struct/enum/ext) - always need mode-dependent type info logic
                // Determine write_type_info based on mode:
                // - compatible=true: use need_to_write_type_for_field (struct types need type info)
                // - compatible=false: use fory_is_polymorphic
                // This applies regardless of ref_mode because Java always writes type info
                // for struct-type fields in compatible mode, even for non-nullable fields.
                quote! {
                    let write_type_info = if context.is_compatible() {
                        fory_core::types::need_to_write_type_for_field(
                            <#ty as fory_core::Serializer>::fory_static_type_id()
                        )
                    } else {
                        <#ty as fory_core::Serializer>::fory_is_polymorphic()
                    };
                    <#ty as fory_core::Serializer>::fory_write(&#value_ts, context, #ref_mode, write_type_info, false)?;
                }
            }
        }
    };

    if is_debug_enabled() && use_self {
        let struct_name = get_struct_name().expect("struct context not set");
        let struct_name_lit = syn::LitStr::new(&struct_name, proc_macro2::Span::call_site());
        let field_name_lit = syn::LitStr::new(field_name, proc_macro2::Span::call_site());
        quote! {
            fory_core::serializer::struct_::struct_before_write_field(
                #struct_name_lit,
                #field_name_lit,
                (&#value_ts) as &dyn std::any::Any,
                context,
            );
            #base
            fory_core::serializer::struct_::struct_after_write_field(
                #struct_name_lit,
                #field_name_lit,
                (&#value_ts) as &dyn std::any::Any,
                context,
            );
        }
    } else {
        base
    }
}

pub fn gen_write_data(source_fields: &[SourceField<'_>]) -> TokenStream {
    let fields: Vec<&Field> = source_fields.iter().map(|sf| sf.field).collect();
    let write_fields_ts: Vec<_> = get_filtered_source_fields_iter(source_fields)
        .map(|sf| gen_write_field_with_index(sf.field, sf.original_index, true))
        .collect();

    let version_hash_ts = gen_struct_version_hash_ts(&fields);
    quote! {
        if context.is_check_struct_version() {
            let version_hash: i32 = #version_hash_ts;
            context.writer.write_i32(version_hash);
        }
        #(#write_fields_ts)*
        Ok(())
    }
}

pub fn gen_write() -> TokenStream {
    quote! {
        fory_core::serializer::struct_::write::<Self>(self, context, ref_mode, write_type_info)
    }
}
