use quote::ToTokens;
use syn::parse::{Parse, ParseStream};
use syn::spanned::Spanned;
use syn::{parse_quote, Expr, Lit, Meta, Token, Type};

/// Attempts to convert the given literal to a string.
/// Converts ints and floats to their base 10 counterparts.
///
/// Returns `None` if the literal is [`Lit::Verbatim`] or if the literal is [`Lit::ByteStr`]
/// and the byte string could not be converted to UTF-8.
pub fn lit_to_string(lit: Lit) -> Option<String> {
    match lit {
        Lit::Str(l) => Some(l.value()),
        Lit::ByteStr(l) => String::from_utf8(l.value()).ok(),
        Lit::Byte(l) => Some(String::from(l.value() as char)),
        Lit::Char(l) => Some(l.value().to_string()),
        Lit::Int(l) => Some(l.base10_digits().to_string()),
        Lit::Float(l) => Some(l.base10_digits().to_string()),
        Lit::Bool(l) => Some(l.value().to_string()),
        Lit::Verbatim(_) => None,
        _ => None,
    }
}

pub fn format_type_string(ty: &Type) -> String {
    let ty_unformatted = ty.into_token_stream().to_string();
    let ty_unformatted = ty_unformatted.trim();

    // simply remove all whitespace
    let ty_formatted = ty_unformatted.replace(' ', "");

    ty_formatted.to_string()
}

/// Represents the `#[deprecated]` attribute.
///
/// You can use the [`DeprecatedAttribute::from_meta`] function to try to parse an attribute to this struct.
#[derive(Default)]
pub struct DeprecatedAttribute {
    pub since: Option<String>,
    pub note: Option<String>,
}

impl DeprecatedAttribute {
    /// Returns `None` if the given attribute was not a valid form of the `#[deprecated]` attribute.
    pub fn from_meta(meta: &Meta) -> syn::Result<Self> {
        if meta.path() != &parse_quote!(deprecated) {
            return Err(syn::Error::new(
                meta.span(),
                "attribute path is not `deprecated`",
            ));
        }

        match &meta {
            Meta::Path(_) => Ok(Self::default()),
            Meta::NameValue(name_value) => {
                let Expr::Lit(expr_lit) = &name_value.value else {
                    return Err(syn::Error::new(
                        name_value.span(),
                        "literal in `deprecated` value must be a string",
                    ));
                };

                Ok(Self {
                    since: None,
                    note: lit_to_string(expr_lit.lit.clone()).map(|s| s.trim().to_string()),
                })
            }
            Meta::List(list) => {
                let parsed = list.parse_args::<DeprecatedAttributeArgsParser>()?;

                Ok(Self {
                    since: parsed.since.map(|s| s.trim().to_string()),
                    note: parsed.note.map(|s| s.trim().to_string()),
                })
            }
        }
    }
}

mod kw {
    use syn::custom_keyword;
    custom_keyword!(since);
    custom_keyword!(note);
}

struct DeprecatedAttributeArgsParser {
    since: Option<String>,
    note: Option<String>,
}

impl Parse for DeprecatedAttributeArgsParser {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut since: Option<String> = None;
        let mut note: Option<String> = None;

        if input.peek(kw::since) {
            input.parse::<kw::since>()?;
            input.parse::<Token![=]>()?;

            since = lit_to_string(input.parse()?);
        }

        if input.peek(Token![,]) && input.peek2(kw::note) {
            input.parse::<Token![,]>()?;
            input.parse::<kw::note>()?;
            input.parse::<Token![=]>()?;

            note = lit_to_string(input.parse()?);
        }

        Ok(Self { since, note })
    }
}
