//! HeroScript derive macro implementations.
//!
//! This module provides the implementation for `ToHeroScript` and `FromHeroScript`
//! derive macros.

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{
    Data, DeriveInput, Fields, GenericArgument, Ident, PathArguments, Type, parse_macro_input,
};

/// Implement the `ToHeroScript` derive macro.
///
/// This generates an implementation of the `ToHeroScript` trait for a struct,
/// which converts the struct fields to HeroScript parameters.
pub fn impl_to_heroscript(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let name_str = name.to_string();
    let name_lower = name_str.to_lowercase();

    let fields = match &input.data {
        Data::Struct(data_struct) => match &data_struct.fields {
            Fields::Named(fields) => &fields.named,
            _ => {
                return syn::Error::new_spanned(
                    &input,
                    "ToHeroScript can only be derived for structs with named fields",
                )
                .to_compile_error()
                .into();
            }
        },
        _ => {
            return syn::Error::new_spanned(&input, "ToHeroScript can only be derived for structs")
                .to_compile_error()
                .into();
        }
    };

    // Generate field setters for to_params
    let field_setters: Vec<TokenStream2> = fields
        .iter()
        .filter_map(|field| {
            let field_name = field.ident.as_ref()?;
            let field_name_str = field_name.to_string();

            // Skip fields that start with underscore
            if field_name_str.starts_with('_') {
                return None;
            }

            let setter = generate_field_setter(field_name, &field_name_str, &field.ty);
            Some(setter)
        })
        .collect();

    let expanded = quote! {
        impl herolib_core::heroscript::ToHeroScript for #name {
            fn to_params(&self) -> herolib_core::heroscript::Params {
                let mut params = herolib_core::heroscript::Params::default();
                #(#field_setters)*
                params
            }

            fn default_actor() -> &'static str {
                #name_lower
            }

            fn default_action() -> &'static str {
                "define"
            }
        }
    };

    TokenStream::from(expanded)
}

/// Generate a setter expression for a field based on its type.
fn generate_field_setter(field_name: &Ident, field_name_str: &str, ty: &Type) -> TokenStream2 {
    match ty {
        Type::Path(type_path) => {
            let segments = &type_path.path.segments;
            if let Some(segment) = segments.last() {
                let type_name = segment.ident.to_string();

                match type_name.as_str() {
                    // Option<T> - only set if Some
                    "Option" => {
                        if let PathArguments::AngleBracketed(args) = &segment.arguments {
                            if let Some(GenericArgument::Type(inner_ty)) = args.args.first() {
                                let inner_setter = generate_value_to_string(quote! { v }, inner_ty);
                                return quote! {
                                    if let Some(v) = &self.#field_name {
                                        params.set(#field_name_str, &#inner_setter);
                                    }
                                };
                            }
                        }
                        quote! {}
                    }

                    // Vec<T> - join with commas
                    "Vec" => {
                        quote! {
                            if !self.#field_name.is_empty() {
                                let items: Vec<String> = self.#field_name
                                    .iter()
                                    .map(|v| v.to_string())
                                    .collect();
                                params.set(#field_name_str, &items.join(","));
                            }
                        }
                    }

                    // String - direct
                    "String" => {
                        quote! {
                            if !self.#field_name.is_empty() {
                                params.set(#field_name_str, &self.#field_name);
                            }
                        }
                    }

                    // Primitives and other types - use to_string
                    _ => {
                        quote! {
                            params.set(#field_name_str, &self.#field_name.to_string());
                        }
                    }
                }
            } else {
                quote! {
                    params.set(#field_name_str, &self.#field_name.to_string());
                }
            }
        }
        _ => {
            quote! {
                params.set(#field_name_str, &self.#field_name.to_string());
            }
        }
    }
}

/// Generate an expression to convert a value to string based on its type.
fn generate_value_to_string(value: TokenStream2, ty: &Type) -> TokenStream2 {
    match ty {
        Type::Path(type_path) => {
            let segments = &type_path.path.segments;
            if let Some(segment) = segments.last() {
                let type_name = segment.ident.to_string();

                match type_name.as_str() {
                    "String" => quote! { #value.clone() },
                    _ => quote! { #value.to_string() },
                }
            } else {
                quote! { #value.to_string() }
            }
        }
        _ => quote! { #value.to_string() },
    }
}

/// Implement the `FromHeroScript` derive macro.
///
/// This generates an implementation of the `FromHeroScript` trait for a struct,
/// which parses HeroScript parameters into the struct fields.
pub fn impl_from_heroscript(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let name_str = name.to_string();
    let name_lower = name_str.to_lowercase();

    let fields = match &input.data {
        Data::Struct(data_struct) => match &data_struct.fields {
            Fields::Named(fields) => &fields.named,
            _ => {
                return syn::Error::new_spanned(
                    &input,
                    "FromHeroScript can only be derived for structs with named fields",
                )
                .to_compile_error()
                .into();
            }
        },
        _ => {
            return syn::Error::new_spanned(
                &input,
                "FromHeroScript can only be derived for structs",
            )
            .to_compile_error()
            .into();
        }
    };

    // Generate field parsers
    let field_parsers: Vec<TokenStream2> = fields
        .iter()
        .filter_map(|field| {
            let field_name = field.ident.as_ref()?;
            let field_name_str = field_name.to_string();

            // Skip fields that start with underscore
            if field_name_str.starts_with('_') {
                return Some(quote! {
                    #field_name: Default::default(),
                });
            }

            let parser = generate_field_parser(field_name, &field_name_str, &field.ty);
            Some(parser)
        })
        .collect();

    let expanded = quote! {
        impl herolib_core::heroscript::FromHeroScript for #name {
            fn from_params(params: &herolib_core::heroscript::Params) -> herolib_core::heroscript::Result<Self> {
                Ok(Self {
                    #(#field_parsers)*
                })
            }

            fn actor_filter() -> &'static str {
                #name_lower
            }

            fn action_filter() -> &'static str {
                "define"
            }
        }
    };

    TokenStream::from(expanded)
}

/// Generate a parser expression for a field based on its type.
fn generate_field_parser(field_name: &Ident, field_name_str: &str, ty: &Type) -> TokenStream2 {
    match ty {
        Type::Path(type_path) => {
            let segments = &type_path.path.segments;
            if let Some(segment) = segments.last() {
                let type_name = segment.ident.to_string();

                match type_name.as_str() {
                    // Option<T> - use get_opt
                    "Option" => {
                        if let PathArguments::AngleBracketed(args) = &segment.arguments {
                            if let Some(GenericArgument::Type(inner_ty)) = args.args.first() {
                                let inner_parser =
                                    generate_optional_inner_parser(field_name_str, inner_ty);
                                return quote! {
                                    #field_name: #inner_parser,
                                };
                            }
                        }
                        quote! {
                            #field_name: params.get_opt(#field_name_str).map(|s| s.to_string()),
                        }
                    }

                    // Vec<T> - parse comma-separated list
                    "Vec" => {
                        if let PathArguments::AngleBracketed(args) = &segment.arguments {
                            if let Some(GenericArgument::Type(inner_ty)) = args.args.first() {
                                let inner_parser = generate_vec_item_parser(inner_ty);
                                return quote! {
                                    #field_name: params.get_list(#field_name_str)
                                        .unwrap_or_default()
                                        .into_iter()
                                        .filter_map(|s| #inner_parser)
                                        .collect(),
                                };
                            }
                        }
                        quote! {
                            #field_name: params.get_list(#field_name_str).unwrap_or_default(),
                        }
                    }

                    // String
                    "String" => {
                        quote! {
                            #field_name: params.get(#field_name_str).unwrap_or_default().to_string(),
                        }
                    }

                    // bool
                    "bool" => {
                        quote! {
                            #field_name: params.get_default_false(#field_name_str),
                        }
                    }

                    // Integer types
                    "i8" | "i16" | "i32" | "i64" | "isize" => {
                        quote! {
                            #field_name: params.get_int(#field_name_str).unwrap_or_default() as #ty,
                        }
                    }

                    "u8" | "u16" | "u32" | "u64" | "usize" => {
                        quote! {
                            #field_name: params.get_int(#field_name_str).unwrap_or_default() as #ty,
                        }
                    }

                    // Float types
                    "f32" => {
                        quote! {
                            #field_name: params.get_float(#field_name_str).unwrap_or_default() as f32,
                        }
                    }

                    "f64" => {
                        quote! {
                            #field_name: params.get_float(#field_name_str).unwrap_or_default(),
                        }
                    }

                    // Nested struct - must implement FromHeroScript
                    // For nested structs we expect a flattened approach where fields are prefixed
                    _ => {
                        // For now, use Default::default() for complex nested types
                        // A more advanced implementation could handle nested structs
                        quote! {
                            #field_name: Default::default(),
                        }
                    }
                }
            } else {
                quote! {
                    #field_name: Default::default(),
                }
            }
        }
        _ => {
            quote! {
                #field_name: Default::default(),
            }
        }
    }
}

/// Generate parser for Option inner type.
fn generate_optional_inner_parser(field_name_str: &str, ty: &Type) -> TokenStream2 {
    match ty {
        Type::Path(type_path) => {
            let segments = &type_path.path.segments;
            if let Some(segment) = segments.last() {
                let type_name = segment.ident.to_string();

                match type_name.as_str() {
                    "String" => {
                        quote! {
                            params.get_opt(#field_name_str).map(|s| s.to_string())
                        }
                    }
                    "bool" => {
                        quote! {
                            if params.exists(#field_name_str) {
                                Some(params.get_default_false(#field_name_str))
                            } else {
                                None
                            }
                        }
                    }
                    "i8" | "i16" | "i32" | "i64" | "isize" => {
                        quote! {
                            params.get_int(#field_name_str).ok().map(|v| v as #ty)
                        }
                    }
                    "u8" | "u16" | "u32" | "u64" | "usize" => {
                        quote! {
                            params.get_int(#field_name_str).ok().map(|v| v as #ty)
                        }
                    }
                    "f32" => {
                        quote! {
                            params.get_float(#field_name_str).ok().map(|v| v as f32)
                        }
                    }
                    "f64" => {
                        quote! {
                            params.get_float(#field_name_str).ok()
                        }
                    }
                    _ => {
                        quote! {
                            None
                        }
                    }
                }
            } else {
                quote! { None }
            }
        }
        _ => quote! { None },
    }
}

/// Generate parser for Vec item type.
fn generate_vec_item_parser(ty: &Type) -> TokenStream2 {
    match ty {
        Type::Path(type_path) => {
            let segments = &type_path.path.segments;
            if let Some(segment) = segments.last() {
                let type_name = segment.ident.to_string();

                match type_name.as_str() {
                    "String" => {
                        quote! { Some(s.to_string()) }
                    }
                    "i8" | "i16" | "i32" | "i64" | "isize" => {
                        quote! { s.parse::<#ty>().ok() }
                    }
                    "u8" | "u16" | "u32" | "u64" | "usize" => {
                        quote! { s.parse::<#ty>().ok() }
                    }
                    "f32" | "f64" => {
                        quote! { s.parse::<#ty>().ok() }
                    }
                    _ => {
                        quote! { Some(s.to_string()) }
                    }
                }
            } else {
                quote! { Some(s.to_string()) }
            }
        }
        _ => quote! { Some(s.to_string()) },
    }
}

#[cfg(test)]
mod tests {
    // Tests are in a separate integration test file
}
