use crate::dialect::operation::{Attribute, OperationField};
use proc_macro2::TokenStream;
use quote::quote;

pub fn generate_attribute_accessors(attribute: &Attribute) -> TokenStream {
    let getter = generate_getter(attribute);
    let setter = generate_setter(attribute);
    let remover = generate_remover(attribute);

    quote! {
        #getter
        #setter
        #remover
    }
}

fn generate_getter(attribute: &Attribute) -> TokenStream {
    let name = attribute.name();

    let identifier = attribute.singular_identifier();
    let return_type = attribute.return_type();
    let body = if attribute.is_unit() {
        quote! { self.operation.attribute(#name).is_some() }
    } else {
        quote! { Ok(self.operation.attribute(#name)?.try_into()?) }
    };

    quote! {
        #[allow(clippy::needless_question_mark)]
        pub fn #identifier(&self) -> #return_type {
            #body
        }
    }
}

fn generate_setter(attribute: &Attribute) -> TokenStream {
    let name = attribute.name();

    let body = if attribute.is_unit() {
        quote! {
            if value {
                self.operation.set_attribute(#name, Attribute::unit(self.operation.context()));
            } else {
                self.operation.remove_attribute(#name)
            }
        }
    } else {
        quote! {
            self.operation.set_attribute(#name, value.into());
        }
    };

    let identifier = attribute.set_identifier();
    let r#type = attribute.parameter_type();

    quote! {
        pub fn #identifier(&mut self, value: #r#type) {
            #body
        }
    }
}

fn generate_remover(attribute: &Attribute) -> Option<TokenStream> {
    if attribute.is_optional() {
        let name = attribute.name();
        let identifier = attribute.remove_identifier();

        Some(quote! {
            pub fn #identifier(&mut self) -> Result<(), ::melior::Error> {
                self.operation.remove_attribute(#name)
            }
        })
    } else {
        None
    }
}
