use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::{AttrStyle, Attribute, Expr, Ident, LitStr, parse::ParseStream, punctuated::Punctuated};

syn::custom_keyword!(param);
syn::custom_keyword!(default);
syn::custom_keyword!(name);

#[derive(Debug, PartialEq, Eq)]
pub enum Attr {
    Param,
    Default(Option<Expr>),
    Name(String),
}

impl Attr {
    pub fn parse(input: ParseStream) -> syn::Result<Self> {
        let lookahead = input.lookahead1();
        if lookahead.peek(param) {
            let _: param = input.parse()?;
            Ok(Attr::Param)
        } else if lookahead.peek(default) {
            let _: default = input.parse()?;
            if !input.is_empty() && !input.lookahead1().peek(syn::Token![,]) {
                let _: syn::Token![=] = input.parse()?;
                let value: Expr = input.parse()?;
                Ok(Attr::Default(Some(value)))
            } else {
                Ok(Attr::Default(None))
            }
        } else if lookahead.peek(name) {
            let _: name = input.parse()?;
            let _: syn::Token![=] = input.parse()?;
            let name: LitStr = input.parse()?;
            Ok(Attr::Name(name.value()))
        } else {
            Err(lookahead.error())
        }
    }
}

pub struct Attrs(Vec<Attr>);

impl Attrs {
    pub fn parse(all: &[Attribute]) -> Self {
        Self(
            all.iter()
                .filter(|a| matches!(a.style, AttrStyle::Outer) && a.path().is_ident("scfg"))
                .flat_map(|a| a.parse_args_with(Attrs::parse_stream).unwrap())
                .collect(),
        )
    }

    fn parse_stream(input: ParseStream) -> syn::Result<impl IntoIterator<Item = Attr> + use<>> {
        Punctuated::<_, syn::Token![,]>::parse_terminated_with(input, Attr::parse)
    }

    pub fn default_value(&self, name: &Ident) -> Option<TokenStream> {
        self.0.iter().find_map(|a| match a {
            Attr::Default(None) => Some(quote! { #name.unwrap_or_default() }),
            Attr::Default(Some(e)) => Some(e.into_token_stream()),
            _ => None,
        })
    }

    pub fn field_name(&self) -> Option<String> {
        self.0.iter().find_map(|a| match a {
            Attr::Name(n) => Some(n.clone()),
            _ => None,
        })
    }

    pub fn contains(&self, a: &Attr) -> bool {
        self.0.contains(a)
    }
}
