use quote::quote;
use syn::{
    parse::{Parse, ParseStream, Result},
    Ident, Token,
};

use crate::rich::{common::Visibility, Field, FieldType};

#[derive(Debug)]
pub(crate) struct ShortFn {
    attributes: Vec<syn::Attribute>,
    name: Ident,
    expr: syn::Expr,
}

impl Parse for ShortFn {
    fn parse(input: ParseStream) -> Result<Self> {
        let attributes = input.call(syn::Attribute::parse_outer)?;
        let name = input.parse::<Ident>()?;
        let _ = input.parse::<Token![=]>()?;
        let expr = input.parse::<syn::Expr>()?;
        Ok(Self {
            attributes,
            name,
            expr,
        })
    }
}

#[derive(Debug)]
pub(crate) struct ValueFns {
    take: bool,
    visibility: Option<Visibility>,
    name_exprs: Vec<ShortFn>,
}

impl ValueFns {
    pub fn get_fns(&self, field: &Field) -> proc_macro2::TokenStream {
        let field_name = &field.name;
        let visibility = self
            .visibility
            .as_ref()
            .map(|vis| quote! { #vis })
            .unwrap_or(quote! { pub });

        let fns = self
            .name_exprs
            .iter()
            .map(|short_fn| {
                let fn_name = &short_fn.name;
                let expr = &short_fn.expr;
                let expr = match field.ty {
                    FieldType::Normal(_) => quote! { (#expr).into() },
                    FieldType::Option(_) => quote! { Some((#expr).into()) },
                };
                let attributes = &short_fn.attributes;
                let (self_arg, ret_arg) = if self.take {
                    (quote! { mut self }, quote! { Self })
                } else {
                    (quote! { &mut self }, quote! { &mut Self })
                };
                quote! {
                    #( #attributes )*
                    #visibility fn #fn_name(#self_arg) -> #ret_arg {
                        self.#field_name = #expr;
                        self
                    }
                }
            })
            .collect::<Vec<proc_macro2::TokenStream>>();
        quote! {
            #( #fns )*
        }
    }
}

impl Parse for ValueFns {
    fn parse(input: ParseStream) -> Result<Self> {
        let attr_name = "value_fns";
        if input.parse::<Ident>()? != attr_name {
            return Err(input.error(format!("Expected `{}`", attr_name)));
        }

        let mut take = false;
        let mut visibility = None;
        if input.peek(syn::token::Paren) {
            let inner;
            syn::parenthesized!(inner in input);
            while !inner.is_empty() {
                match inner.parse::<Ident>()?.to_string().as_ref() {
                    "take" => {
                        if take {
                            return Err(
                                inner.error("`value_fns` have received `take` flag more than once")
                            );
                        } else {
                            take = true;
                        }
                    }
                    "vis" => {
                        if let Some(_) = visibility {
                            return Err(inner.error(
                                "`value_fns` attribute have been received visibility value more than once",
                            ));
                        }
                        let _ = inner.parse::<Token![=]>()?;
                        let vis = inner.parse::<Visibility>()?;
                        visibility = Some(vis);
                    }
                    _ => {
                        return Err(inner.error(
                            "`value_fns` attribute only accept `take`, `vis` and `doc` attribute",
                        ))
                    }
                }
            }
        }

        let _ = input.parse::<Token![=]>()?;
        let inner_content;
        let _ = syn::braced!(inner_content in input);
        let name_exprs = inner_content
            .parse_terminated::<_, Token![,]>(ShortFn::parse)?
            .into_iter()
            .collect::<Vec<ShortFn>>();

        let mut fn_names: Vec<&Ident> = vec![];
        for sf in name_exprs.iter() {
            if fn_names.contains(&&sf.name) {
                return Err(
                    inner_content.error(format!("the function name `{}` is duplicated.", sf.name))
                );
            } else {
                fn_names.push(&sf.name)
            }
        }

        Ok(Self {
            take,
            visibility,
            name_exprs,
        })
    }
}
