//! Nameres-specific procedural macro data and helpers.

use hir_expand::name::{AsName, Name};
use tt::{Leaf, TokenTree};

use crate::attr::Attrs;

#[derive(Debug, PartialEq, Eq)]
pub(super) struct ProcMacroDef {
    pub(super) name: Name,
    pub(super) kind: ProcMacroKind,
}

#[derive(Debug, PartialEq, Eq)]
pub(super) enum ProcMacroKind {
    CustomDerive { helpers: Box<[Name]> },
    FnLike,
    Attr,
}

impl Attrs {
    #[rustfmt::skip]
    pub(super) fn parse_proc_macro_decl(&self, func_name: &Name) -> Option<ProcMacroDef> {
        if self.by_key("proc_macro").exists() {
            Some(ProcMacroDef { name: func_name.clone(), kind: ProcMacroKind::FnLike })
        } else if self.by_key("proc_macro_attribute").exists() {
            Some(ProcMacroDef { name: func_name.clone(), kind: ProcMacroKind::Attr })
        } else if self.by_key("proc_macro_derive").exists() {
            let derive = self.by_key("proc_macro_derive").tt_values().next().unwrap();

            match &*derive.token_trees {
                // `#[proc_macro_derive(Trait)]`
                [TokenTree::Leaf(Leaf::Ident(trait_name))] => Some(ProcMacroDef {
                    name: trait_name.as_name(),
                    kind: ProcMacroKind::CustomDerive { helpers: Box::new([]) },
                }),

                // `#[proc_macro_derive(Trait, attibutes(helper1, helper2, ...))]`
                [
                    TokenTree::Leaf(Leaf::Ident(trait_name)),
                    TokenTree::Leaf(Leaf::Punct(comma)),
                    TokenTree::Leaf(Leaf::Ident(attributes)),
                    TokenTree::Subtree(helpers)
                ] if comma.char == ',' && attributes.text == "attributes" =>
                {
                    let helpers = helpers.token_trees.iter()
                        .filter(|tt| !matches!(tt, TokenTree::Leaf(Leaf::Punct(comma)) if comma.char == ','))
                        .map(|tt| {
                            match tt {
                                TokenTree::Leaf(Leaf::Ident(helper)) => Some(helper.as_name()),
                                _ => None
                            }
                        })
                        .collect::<Option<Box<[_]>>>()?;

                    Some(ProcMacroDef {
                        name: trait_name.as_name(),
                        kind: ProcMacroKind::CustomDerive { helpers },
                    })
                }

                _ => {
                    log::trace!("malformed `#[proc_macro_derive]`: {}", derive);
                    None
                }
            }
        } else {
            None
        }
    }
}
