Framework for embedding localizations into Rust types
use proc_macro::TokenStream;
use proc_macro_error2::proc_macro_error;
use quote::quote;

mod fluent;
mod macro_impl;

#[proc_macro_attribute]
#[proc_macro_error]
pub fn localize(attribute: TokenStream, item: TokenStream) -> TokenStream {
    let attribute_stream = proc_macro2::TokenStream::from(attribute);
    let derive_input_stream = proc_macro2::TokenStream::from(item);

    match macro_impl::localize(attribute_stream.clone(), derive_input_stream.clone()) {
        Ok(implementation) => {
            let token_stream = quote! {
                #derive_input_stream

                #implementation
            };

            // Optionally expand the macro for debugging purposes if the "expand" feature is enabled
            #[cfg(feature = "expand")]
            {
                expander::Expander::new("l10n_embed_derive")
                    .write_to_out_dir(token_stream)
                    .unwrap()
            }
            #[cfg(not(feature = "expand"))]
            token_stream
        }
        Err(error) => {
            // Using the `proc_macro_error2` crate to generate errors ignores
            // the token stream returned from this function, so use the `set_dummy` function
            // to make sure a macro error is as self-contained as possible:
            // - Derive input streams are not consumed, so structs/enums don't "disappear" when macro returns error
            // - Generate a shim trait implementation when possible so that editor completions still work as expected
            proc_macro_error2::set_dummy(
                match syn::parse2::<syn::DeriveInput>(derive_input_stream.clone()) {
                    // Generate a minimal `Localize` implementation so the error is self-contained
                    Ok(derive_input) => {
                        let ident = derive_input.ident;
                        let (impl_generics, type_generics, where_clause) =
                            derive_input.generics.split_for_impl();

                        quote! {
                            #derive_input_stream

                            impl #impl_generics ::l10n_embed::Localize for #ident #type_generics #where_clause {
                                fn localize(
                                    &self,
                                    context: &::l10n_embed::Context,
                                    buffer: &mut String,
                                ) {
                                    unimplemented!("Encountered error in derive macro")
                                }
                            }
                        }
                    }
                    // Unable to implement Localize if the ident is unknown, so just return the original input
                    Err(_) => derive_input_stream.clone(),
                },
            );

            // Emit the relevant error message
            macro_impl::error::emit(error, &attribute_stream, &derive_input_stream);

            // `proc_macro_error2` will output the relevant token stream, but the function still needs to return something
            quote!()
        }
    }
    .into()
}