use super::bindgen::ExternEnum;
use syn::TypePath;

/// Checks that the type path last part is exactly `ty`.
///
/// Note that using this function also requires adding a static assertion in the code that the type
/// we expect here is the actual one we'll get at the end.
pub(crate) fn type_path_ends_with(tp: &TypePath, ty: &str) -> bool {
    match tp.path.segments.last() {
        Some(last) => last.ident == ty,
        None => false,
    }
}

pub(crate) fn type_path_is_enum<'a>(
    tp: &TypePath,
    enums: &'a [ExternEnum],
) -> Option<&'a ExternEnum> {
    match tp.path.segments.last() {
        Some(segment) => enums.iter().find(|ee| ee.ident == segment.ident),
        None => None,
    }
}

pub(crate) fn search_enum_attribute(
    attr: &syn::Attribute,
    path_search: &str,
    token_searches: &[&str],
) -> Option<syn::Ident> {
    if let Ok(meta) = attr.parse_meta() {
        // Search for the attribute by its path name
        if let syn::Meta::List(list) = &meta {
            if let Some(path) = list.path.segments.last() {
                if path.ident != path_search {
                    return None;
                }
            } else {
                return None;
            }
        }

        match &meta {
            syn::Meta::List(list) => {
                for nested_meta in &list.nested {
                    if let syn::NestedMeta::Meta(syn::Meta::Path(path)) = nested_meta {
                        for segment in &path.segments {
                            if token_searches.iter().any(|search| segment.ident == search) {
                                return Some(segment.ident.clone());
                            }
                        }
                    }
                }
            }
            _ => return None,
        }
    }
    None
}

pub(crate) fn has_custom_attribute(attr: &syn::Attribute, to_compare: &syn::Attribute) -> bool {
    attr.path == to_compare.path
}

pub(crate) fn extract_generic_type(
    pos: usize,
    expected_len: Option<usize>,
    ty: &syn::TypePath,
) -> syn::parse::Result<Option<syn::TypePath>> {
    use syn::parse::Error;
    use syn::spanned::Spanned;
    use syn::Type;
    match ty.path.segments.last() {
        Some(last) => {
            if let syn::PathArguments::AngleBracketed(args) = &last.arguments {
                if let Some(expected_len) = expected_len {
                    if args.args.len() != expected_len {
                        return Err(Error::new(
                            args.span(),
                            format!("expected {} type arguments", expected_len),
                        ));
                    }
                }
                if pos >= args.args.len() {
                    return Err(Error::new(
                        args.span(),
                        format!("missing {}th type argument", pos),
                    ));
                }
                if let syn::GenericArgument::Type(ty) = &args.args[pos] {
                    match ty {
                        Type::Path(tp) => Ok(Some(tp.clone())),
                        Type::Tuple(t) => {
                            if t.elems.is_empty() {
                                Ok(None)
                            } else {
                                Err(Error::new(ty.span(), "non-unit tuples are not supported"))
                            }
                        }
                        _ => Err(Error::new(ty.span(), "unsupported generic argument")),
                    }
                } else {
                    Err(Error::new(args.args[0].span(), "expected a type argument"))
                }
            } else {
                Err(Error::new(last.span(), "expected a generic"))
            }
        }
        None => Err(Error::new(ty.span(), "expected a generic")),
    }
}

/// Extracts the first generic type from a type path. For instance, for `Vec<u8>` it would return
/// `u8`.
pub(crate) fn extract_first_generic_type(
    ty: &syn::TypePath,
) -> syn::parse::Result<Option<syn::TypePath>> {
    extract_generic_type(0, None, ty)
}

/// Extracts the single generic type from a type path. For instance, for `Vec<u8>` it would return
/// `u8`.
pub(crate) fn extract_single_generic_type(
    ty: &syn::TypePath,
) -> syn::parse::Result<Option<syn::TypePath>> {
    extract_generic_type(0, Some(1), ty)
}
