use crate::{dependencies::Libraries, error::Error, interpreter::ParsedMacroStore};
use quote::ToTokens;
use std::collections::HashMap;
use syn::parse::Parser;

pub const ALLOWED_MACROS: [&str; 8] = [
    "arcis_static_panic",
    "debug_assert",
    "debug_assert_eq",
    "debug_assert_ne",
    "eprint",
    "eprintln",
    "print",
    "println",
];

pub fn check_macro(expr: &syn::Macro) -> Result<String, Error> {
    let Some(ident) = expr.path.get_ident() else {
        return Err(Error::new_spanned(
            &expr.path,
            format!(
                "Unsupported macro {}. Only supported macros are {:?}.",
                expr.path.to_token_stream(),
                ALLOWED_MACROS
            ),
        ));
    };
    let macro_name = ident.to_string();
    if !ALLOWED_MACROS.contains(&macro_name.as_str()) {
        return Err(Error::new_spanned(
            &expr.path,
            format!(
                "Unsupported macro {}. Only supported macros are {:?}.",
                expr.path.to_token_stream(),
                ALLOWED_MACROS
            ),
        ));
    }
    Ok(macro_name)
}
pub fn parse_macro(expr: &syn::Macro) -> Result<Vec<syn::Expr>, Error> {
    check_macro(expr)?;
    let res: syn::punctuated::Punctuated<syn::Expr, syn::token::Comma> =
        syn::punctuated::Punctuated::parse_separated_nonempty.parse2(expr.tokens.clone())?;
    Ok(res.into_iter().collect())
}

#[derive(Debug, Default)]
pub struct MacroParser {
    contents: Vec<Vec<syn::Expr>>,
    finder: HashMap<syn::Macro, usize>,
    errors: Vec<Error>,
}

impl MacroParser {
    pub fn finish(&self) -> ParsedMacroStore<'_> {
        self.finder
            .iter()
            .map(|(m, v)| (m.clone(), self.contents[*v].as_slice()))
            .collect()
    }
    pub fn visit_all(
        &mut self,
        module: &syn::ItemMod,
        mock_library: &[syn::File],
        standard_library: &[syn::File],
        dependencies: &Libraries,
    ) {
        syn::visit::visit_item_mod(self, module);
        for lib in mock_library {
            syn::visit::visit_file(self, lib);
        }
        for lib in standard_library {
            syn::visit::visit_file(self, lib);
        }
        for lib in dependencies.file_iter() {
            syn::visit::visit_file(self, lib);
        }
    }
}

impl<'ast> syn::visit::Visit<'ast> for MacroParser {
    fn visit_macro(&mut self, m: &'ast syn::Macro) {
        match parse_macro(m) {
            Ok(a) => {
                let idx = self.contents.len();
                self.contents.push(a);
                self.finder.insert(m.clone(), idx);
            }
            Err(e) => self.errors.push(e),
        }
        syn::visit::visit_macro(self, m);
    }
}
