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

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

#[derive(Debug, Default)]
pub(crate) struct ReadFn {
    attributes: Vec<syn::Attribute>,
    rename: Option<Ident>,
    copy: bool,
    visibility: Option<Visibility>,
}

impl ReadFn {
    pub fn get_fn(&self, field: &Field) -> proc_macro2::TokenStream {
        let field_name = &field.name;
        let read_fn_name = &field.name;
        let option_read_fn_name = format_ident!("get_{}", &field.name);
        let read_fn_name = self.rename.as_ref().unwrap_or(&read_fn_name);
        let visibility = self
            .visibility
            .as_ref()
            .map(|vis| quote! { #vis })
            .unwrap_or(quote! { pub });
        let attributes = &self.attributes;

        match (&field.ty, self.copy) {
            (FieldType::Normal(ty), true) => quote! {
                #( #attributes )*
                #visibility fn #read_fn_name(&self) -> #ty {
                    self.#field_name
                }
            },
            (FieldType::Normal(ty), false) => quote! {
                #( #attributes )*
                #visibility fn #read_fn_name(&self) -> &#ty {
                    &self.#field_name
                }
            },
            (FieldType::Option(ty), true) => quote! {
                #( #attributes )*
                #visibility fn #option_read_fn_name(&self) -> Option<#ty> {
                    self.#field_name.clone()
                }
            },
            (FieldType::Option(ty), false) => quote! {
                #( #attributes )*
                #visibility fn #option_read_fn_name(&self) -> Option<&#ty> {
                    self.#field_name.as_ref()
                }
            },
        }
    }
}

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

        // if there was no inner attrs or this the last attribute, reutrn default WriteFn
        if input.peek(Token![,]) || input.is_empty() {
            return Ok(ReadFn::default());
        }

        let mut read_fn = ReadFn::default();

        let inner;
        syn::parenthesized!(inner in input);

        read_fn.attributes = inner.call(syn::Attribute::parse_outer)?;

        while !inner.is_empty() {
            match inner.parse::<Ident>()?.to_string().as_ref() {
                "rename" => {
                    let _ = inner.parse::<Token![=]>()?;
                    match read_fn.rename {
                        Some(_) => {
                            return Err(
                                inner.error("`read` attribute have been renamed more than once")
                            )
                        }
                        None => read_fn.rename = Some(inner.parse::<Ident>()?),
                    };
                }
                "copy" => {
                    if read_fn.copy {
                        return Err(inner.error("`read` have received `copy` flag more than once"));
                    }
                    read_fn.copy = true;
                }
                "vis" => {
                    if let Some(_) = read_fn.visibility {
                        return Err(inner.error(
                            "`read` attribute have been received visibility value more than once",
                        ));
                    }
                    let _ = inner.parse::<Token![=]>()?;
                    let visibility = inner.parse::<Visibility>()?;
                    read_fn.visibility = Some(visibility);
                }
                _ => {
                    Err(inner.error("`read` attribute only accept `rename` and `copy` attributes"))?
                }
            }
            if inner.peek(Token![,]) {
                inner.parse::<Token![,]>()?;
            }
        }

        Ok(read_fn)
    }
}
