use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Ident, ItemStruct, Lit};

#[proc_macro_attribute]
pub fn annotations(attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as ItemStruct);
    let name = input.ident.clone();
    let fields = input
        .fields
        .iter()
        .map(|f| f.ident.clone().unwrap())
        .collect::<Vec<Ident>>();

    // Match on the literal to extract the string value
    let comm_lit = match parse_macro_input!(attr as Lit) {
        Lit::Str(lit_str) => lit_str.value(), // This will give "#" without quotes
        _ => panic!("Expected a string literal"),
    };

    // Generate regex
    let mut reg = format!("^{}|", &comm_lit);
    {
        for field in fields.iter() {
            reg.push_str(&(field.to_string()));
            reg.push_str("\\b");
        }

        reg.push_str(r#"|\w+"#);
    }
    // Example of generated regex:
    // ^#
    // |ann1\b|ann2\b|ann3\b|ann4\b
    // |\w+

    TokenStream::from(quote! {
        #[derive(Default, Debug)]
        #input

        impl std::ops::BitOrAssign for #name{
            fn bitor_assign(&mut self, rhs: Self) {
                // Unfold fields
                // Read more: https://docs.rs/quote/latest/quote/macro.quote.html#interpolation
                #( self.#fields |= rhs.#fields; )*
            }
        }

        impl #name {
            /// Autogenerated by windmill-macros
            pub fn parse(inner_content: &str) -> Self{
                let mut res = Self::default();
                lazy_static::lazy_static! {
                    static ref RE: regex::Regex = regex::Regex::new(#reg).unwrap();
                }
                // Create lines stream
                let mut lines = inner_content.lines();
                'outer: while let Some(line) = lines.next() {
                    // If comment sign(s) on the right place
                    let mut comms = false;
                    // New instance
                    // We will apply it if in line only annotations
                    let mut new = Self::default();

                    'inner: for (i, mat) in RE.find_iter(line).enumerate() {

                        match mat.as_str(){
                            #comm_lit if i == 0 => {
                                comms = true;
                                continue 'inner;
                            },

                            // Will expand into something like:
                            //            "ann1"   => new.ann1    = true,
                            //            "ann2"   => new.ann2    = true,
                            //            "ann3"   => new.ann3    = true,
                            #( stringify!(#fields) => new.#fields = true, )*
                            // Non annotations
                            _ => continue 'outer,
                        };
                    }

                    if !comms {
                        // We dont want to continue if line does not start with #
                        return res;
                    }

                    // Apply changes
                    res |= new;
                }

                res
            }
        }
    })
}
