pub(crate) mod attrs;

use crate::rich::attrs::*;

use quote::{format_ident, quote};
use syn::parse::Parse;

#[derive(Debug)]
enum FieldType {
    Option(syn::Type),
    Normal(syn::Type),
}

#[derive(Debug)]
pub(crate) struct Field {
    attrs: Vec<Attribute>,
    name: syn::Ident,
    ty: FieldType,
}

impl Field {
    fn from_syn_field(field: &syn::Field) -> Result<Self, syn::Error> {
        // get name
        let name = field
            .ident
            .clone()
            .ok_or_else(|| syn::Error::new_spanned(field, "expected field ident"))?;

        // get type
        let ty = field.ty.clone();
        let ty = match ty {
            syn::Type::Path(syn::TypePath {
                path: syn::Path { ref segments, .. },
                ..
            }) => {
                let last_segment = segments.iter().last();
                match last_segment {
                    Some(syn::PathSegment {
                        ident,
                        arguments:
                            syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
                                args,
                                ..
                            }),
                    }) => {
                        if ident != "Option" {
                            FieldType::Normal(ty)
                        } else {
                            let first_arg = args.into_iter().nth(0);
                            match first_arg {
                                Some(syn::GenericArgument::Type(ty)) => {
                                    FieldType::Option(ty.clone())
                                }
                                _ => FieldType::Normal(ty),
                            }
                        }
                    }
                    _ => FieldType::Normal(ty),
                }
            }
            _ => FieldType::Normal(ty),
        };

        // get attributes
        // TODO: should this return error when attribute passed more than once?
        let mut attrs = vec![];
        for attr in field.attrs.iter() {
            if attr.path.is_ident("rich") {
                let result = attr
                    .parse_args_with(|parse_stream: syn::parse::ParseStream| {
                        parse_stream.parse_terminated::<_, syn::Token![,]>(Attribute::parse)
                    })?
                    .into_iter()
                    .collect::<Vec<Attribute>>();
                attrs.extend(result);
            }
        }

        Ok(Field { attrs, name, ty })
    }
}

pub fn expand_derive_rich(
    input: &syn::DeriveInput,
) -> Result<proc_macro2::TokenStream, syn::Error> {
    let result = match input.data {
        syn::Data::Enum(_) => panic!("doesn't work with enums yet"),
        syn::Data::Struct(ref s) => rich_for_struct(input, &s.fields),
        syn::Data::Union(_) => panic!("doesn't work with unions yet"),
    };
    result.into()
}

fn rich_for_struct(
    input: &syn::DeriveInput,
    fields: &syn::Fields,
) -> Result<proc_macro2::TokenStream, syn::Error> {
    match *fields {
        syn::Fields::Named(ref fields) => rich_impl(&input, &fields.named),
        syn::Fields::Unnamed(_) => panic!("doesn't work with tuple struct yet"),
        syn::Fields::Unit => panic!("doesn't work with unit struct yet"),
    }
}

fn rich_impl(
    input: &syn::DeriveInput,
    fields: &syn::punctuated::Punctuated<syn::Field, syn::Token![,]>,
) -> Result<proc_macro2::TokenStream, syn::Error> {
    let name = &input.ident;
    let fields = fields
        .iter()
        .map(|f| Field::from_syn_field(f))
        .collect::<Vec<Result<Field, syn::Error>>>()
        .into_iter()
        .collect::<Result<Vec<Field>, syn::Error>>()?;

    let fns = fields
        .iter()
        .map(|field| {
            let field_name = &field.name;
            let ty = &field.ty;
            let mut fns = field
                .attrs
                .iter()
                .map(|attr| match attr {
                    Attribute::WriteFn(write_fn) => write_fn.get_fn(field),
                    Attribute::ReadFn(read_fn) => read_fn.get_fn(field),
                    Attribute::ValueFns(value_fns) => value_fns.get_fns(field),
                })
                .collect::<Vec<proc_macro2::TokenStream>>();

            // add unset function if the field type is Option<_> and have WriteFn or ValueFns
            let has_write_fn_or_value_fns = field.attrs.iter().any(|a| match a {
                Attribute::WriteFn(_) | Attribute::ValueFns(_) => true,
                _ => false,
            });

            let is_option = if let FieldType::Option(_) = ty {
                true
            } else {
                false
            };

            if is_option && has_write_fn_or_value_fns {
                let unset_fn_name = format_ident!("unset_{}", field_name);
                let docs = format!("Set `None` to `self.{0}`", field_name);
                let unset_fn = quote! {
                    #[doc = #docs]
                    pub fn #unset_fn_name(&mut self) -> &mut Self {
                        self.#field_name = None;
                        self
                    }
                };
                fns.push(unset_fn);
            }

            fns
        })
        .flatten()
        .collect::<Vec<proc_macro2::TokenStream>>();

    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    Ok(quote! {
        impl #impl_generics #name #ty_generics #where_clause {
            #( #fns )*
        }
    })
}
