use proc_macro2::TokenStream;

use quote::quote;
use syn::parse::Error;
use syn::spanned::Spanned;
use syn::DeriveInput;

use crate::smart_default::default_attr::{ConversionStrategy, DefaultAttr};
use crate::smart_default::util::find_only;

pub fn impl_my_derive(input: &DeriveInput) -> Result<TokenStream, Error> {
    let name = &input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

    let (default_expr, doc) = match input.data {
        syn::Data::Struct(ref body) => {
            let (body_assignment, _doc) = default_body_tt(&body.fields)?;
            (
                quote! {
                    #name #body_assignment
                },
                format!("Returns a `{name}` default."),
            )
        }
        syn::Data::Enum(ref body) => {
            let default_variant = find_only(body.variants.iter(), |variant| {
                if let Some(meta) = DefaultAttr::find_in_attributes(&variant.attrs)? {
                    if meta.code.is_none() {
                        Ok(true)
                    } else {
                        Err(Error::new(
                            meta.code.span(),
                            "Attribute #[default] on variants should have no value",
                        ))
                    }
                } else {
                    Ok(false)
                }
            })?
            .ok_or_else(|| Error::new(input.span(), "No default variant"))?;
            let default_variant_name = &default_variant.ident;
            let (body_assignment, _doc) = default_body_tt(&default_variant.fields)?;
            (
                quote! {
                    #name :: #default_variant_name #body_assignment
                },
                format!("Returns a `{name}::{default_variant_name}` default."),
            )
        }
        syn::Data::Union(_) => {
            panic!()
        }
    };
    Ok(quote! {
        #[automatically_derived]
        impl #impl_generics Default for #name #ty_generics #where_clause {
            #[doc = #doc]
            fn default() -> Self {
                #default_expr
            }
        }
    })
}

/// Return a token-tree for the default "body" - the part after the name that contains the values.
/// That is, the `{ ... }` part for structs, the `(...)` part for tuples, and nothing for units.
fn default_body_tt(body: &syn::Fields) -> Result<(TokenStream, String), Error> {
    let mut doc = String::new();
    use std::fmt::Write;
    let body_tt = match body {
        syn::Fields::Named(ref fields) => {
            doc.push_str(" {");
            let result = {
                let field_assignments = fields
                    .named
                    .iter()
                    .map(|field| {
                        let field_name = field.ident.as_ref();
                        let (default_value, default_doc) = field_default_expr_and_doc(field)?;
                        write!(
                            &mut doc,
                            "\n    {}: {},",
                            field_name.expect("field value in struct is empty"),
                            default_doc
                        )
                        .unwrap();
                        // let default_value = default_value.into_token_stream();
                        Ok(quote! { #field_name : #default_value })
                    })
                    .collect::<Result<Vec<_>, Error>>()?;
                quote! {
                    {
                        #( #field_assignments ),*
                    }
                }
            };
            if doc.ends_with(',') {
                doc.pop();
                doc.push('\n');
            };
            doc.push('}');
            result
        }
        syn::Fields::Unnamed(ref fields) => {
            doc.push('(');
            let result = {
                let field_assignments = fields
                    .unnamed
                    .iter()
                    .map(|field| {
                        let (default_value, default_doc) = field_default_expr_and_doc(field)?;
                        write!(&mut doc, "{default_doc}, ").unwrap();
                        Ok(default_value)
                    })
                    .collect::<Result<Vec<TokenStream>, Error>>()?;
                quote! {
                    (
                        #( #field_assignments ),*
                    )
                }
            };
            if doc.ends_with(", ") {
                doc.pop();
                doc.pop();
            };
            doc.push(')');
            result
        }
        &syn::Fields::Unit => quote! {},
    };
    Ok((body_tt, doc))
}

/// Return a default expression for a field based on it's `#[default = "..."]` attribute. Panic
/// if there is more than one, of if there is a `#[default]` attribute without value.
fn field_default_expr_and_doc(field: &syn::Field) -> Result<(TokenStream, String), Error> {
    if let Some(default_attr) = DefaultAttr::find_in_attributes(&field.attrs)? {
        let conversion_strategy = default_attr.conversion_strategy();
        let field_value = default_attr.code.ok_or_else(|| {
            Error::new(field.span(), "Expected #[default = ...] or #[default(...)]")
        })?;

        let field_value = match conversion_strategy {
            ConversionStrategy::NoConversion => field_value,
            ConversionStrategy::Into => quote!((#field_value).into()),
        };

        let field_doc = format!("{field_value}");
        Ok((field_value, field_doc))
    } else {
        Ok((
            quote! {
                Default::default()
            },
            "Default::default()".to_owned(),
        ))
    }
}
