// Copyright (c) 2024 Jonas Schäfer <jonas@zombofant.net>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

//! # Parse Rust attributes
//!
//! This module is concerned with parsing attributes from the Rust "meta"
//! annotations on structs, enums, enum variants and fields.

use core::fmt;
use core::hash::{Hash, Hasher};

use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};
use syn::{meta::ParseNestedMeta, spanned::Spanned, *};

use rxml_validation::NcName;

use crate::error_message::PrettyPath;

/// XML core namespace URI (for the `xml:` prefix)
pub const XMLNS_XML: &str = "http://www.w3.org/XML/1998/namespace";
/// XML namespace URI (for the `xmlns:` prefix)
pub const XMLNS_XMLNS: &str = "http://www.w3.org/2000/xmlns/";

macro_rules! reject_key {
    ($key:ident not on $not_allowed_on:literal $(only on $only_allowed_on:literal)?) => {
        if let Some(ref $key) = $key {
            #[allow(unused_imports)]
            use syn::spanned::Spanned as _;
            return Err(Error::new(
                $key.span(),
                concat!(
                    "`",
                    stringify!($key),
                    "` is not allowed on ",
                    $not_allowed_on,
                    $(
                        " (only on ",
                        $only_allowed_on,
                        ")",
                    )?
                ),
            ));
        }
    };

    ($key:ident flag not on $not_allowed_on:literal $(only on $only_allowed_on:literal)?) => {
        if let Flag::Present(ref $key) = $key {
            return Err(Error::new(
                *$key,
                concat!(
                    "`",
                    stringify!($key),
                    "` is not allowed on ",
                    $not_allowed_on,
                    $(
                        " (only on ",
                        $only_allowed_on,
                        ")",
                    )?
                ),
            ));
        }
    };

    ($key:ident vec not on $not_allowed_on:literal $(only on $only_allowed_on:literal)?) => {
        if let Some(ref $key) = $key.first() {
            return Err(Error::new(
                $key.span(),
                concat!(
                    "`",
                    stringify!($key),
                    "` is not allowed on ",
                    $not_allowed_on,
                    $(
                        " (only on ",
                        $only_allowed_on,
                        ")",
                    )?
                ),
            ));
        }
    };
}

pub(crate) use reject_key;

/// Value for the `#[xml(namespace = ..)]` attribute.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum NamespaceRef {
    /// The XML namespace is specified as a string literal.
    LitStr(LitStr),

    /// The XML namespace is specified as a path.
    Path(Path),
}

impl NamespaceRef {
    pub(crate) fn fudge(value: &str, span: Span) -> Self {
        Self::LitStr(LitStr::new(value, span))
    }
}

impl fmt::Display for NamespaceRef {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::LitStr(s) => write!(f, "{}", s.value()),
            Self::Path(ref p) => write!(f, "<{}>", PrettyPath(p)),
        }
    }
}

impl syn::parse::Parse for NamespaceRef {
    fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
        if input.peek(syn::LitStr) {
            Ok(Self::LitStr(input.parse()?))
        } else {
            Ok(Self::Path(input.parse()?))
        }
    }
}

impl quote::ToTokens for NamespaceRef {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::LitStr(ref lit) => lit.to_tokens(tokens),
            Self::Path(ref path) => path.to_tokens(tokens),
        }
    }
}

/// Value for the `#[xml(name = .. )]` attribute.
#[derive(Debug, Clone)]
pub(crate) enum NameRef {
    /// The XML name is specified as a string literal.
    Literal {
        /// The validated XML name.
        value: NcName,

        /// The span of the original [`syn::LitStr`].
        span: Span,
    },

    /// The XML name is specified as a path.
    Path(Path),
}

impl NameRef {
    pub(crate) fn fudge(value: NcName, span: Span) -> Self {
        Self::Literal { value, span }
    }
}

impl fmt::Display for NameRef {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            Self::Literal { value, .. } => write!(f, "{}", value.as_str()),
            Self::Path(ref p) => write!(f, "<{}>", PrettyPath(p)),
        }
    }
}

impl Hash for NameRef {
    fn hash<H: Hasher>(&self, h: &mut H) {
        match self {
            Self::Literal { ref value, .. } => value.hash(h),
            Self::Path(ref path) => path.hash(h),
        }
    }
}

impl PartialEq for NameRef {
    fn eq(&self, other: &NameRef) -> bool {
        match self {
            Self::Literal {
                value: ref my_value,
                ..
            } => match other {
                Self::Literal {
                    value: ref other_value,
                    ..
                } => my_value == other_value,
                _ => false,
            },
            Self::Path(ref my_path) => match other {
                Self::Path(ref other_path) => my_path == other_path,
                _ => false,
            },
        }
    }
}

impl Eq for NameRef {}

impl syn::parse::Parse for NameRef {
    fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
        if input.peek(syn::LitStr) {
            let s: LitStr = input.parse()?;
            let span = s.span();
            match NcName::try_from(s.value()) {
                Ok(value) => Ok(Self::Literal { value, span }),
                Err(e) => Err(Error::new(span, format!("not a valid XML name: {}", e))),
            }
        } else {
            let p: Path = input.parse()?;
            Ok(Self::Path(p))
        }
    }
}

impl quote::ToTokens for NameRef {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::Literal { ref value, span } => {
                let span = *span;
                let value = value.as_str();
                let value = quote_spanned! { span=> #value };
                // SAFETY: self.0 is a known-good NcName, so converting it to an
                // NcNameStr is known to be safe.
                // NOTE: we cannot use `quote_spanned! { self.span=> }` for the unsafe
                // block as that would then in fact trip a `#[deny(unsafe_code)]` lint
                // at the use site of the macro.
                tokens.extend(quote! {
                    unsafe { ::xso::exports::rxml::NcNameStr::from_str_unchecked(#value) }
                })
            }
            Self::Path(ref path) => path.to_tokens(tokens),
        }
    }
}

/// Represents the amount constraint used with child elements.
///
/// Currently, this only supports "one" (literal `1`) or "any amount" (`..`).
/// In the future, we might want to add support for any range pattern for
/// `usize` and any positive integer literal.
#[derive(Debug)]
pub(crate) enum AmountConstraint {
    /// Equivalent to `1`
    #[allow(dead_code)]
    FixedSingle(Span),

    /// Equivalent to `..`.
    Any(Span),
}

impl syn::parse::Parse for AmountConstraint {
    fn parse(input: syn::parse::ParseStream<'_>) -> Result<Self> {
        if input.peek(LitInt) && !input.peek2(token::DotDot) && !input.peek2(token::DotDotEq) {
            let lit: LitInt = input.parse()?;
            let value: usize = lit.base10_parse()?;
            if value == 1 {
                Ok(Self::FixedSingle(lit.span()))
            } else {
                Err(Error::new(lit.span(), "only `1` and `..` are allowed here"))
            }
        } else {
            let p: PatRange = input.parse()?;
            if let Some(attr) = p.attrs.first() {
                return Err(Error::new_spanned(attr, "attributes not allowed here"));
            }
            if let Some(start) = p.start.as_ref() {
                return Err(Error::new_spanned(
                    start,
                    "only full ranges (`..`) are allowed here",
                ));
            }
            if let Some(end) = p.end.as_ref() {
                return Err(Error::new_spanned(
                    end,
                    "only full ranges (`..`) are allowed here",
                ));
            }
            Ok(Self::Any(p.span()))
        }
    }
}

/// Represents a boolean flag from a `#[xml(..)]` attribute meta.
#[derive(Clone, Copy, Debug)]
pub(crate) enum Flag {
    /// The flag is not set.
    Absent,

    /// The flag was set.
    Present(
        /// The span of the syntax element which enabled the flag.
        ///
        /// This is used to generate useful error messages by pointing at the
        /// specific place the flag was activated.
        #[allow(dead_code)]
        Span,
    ),
}

impl Flag {
    /// Return true if the flag is set, false otherwise.
    pub(crate) fn is_set(&self) -> bool {
        match self {
            Self::Absent => false,
            Self::Present(_) => true,
        }
    }

    /// Like `Option::take`, but for flags.
    pub(crate) fn take(&mut self) -> Self {
        let mut result = Flag::Absent;
        core::mem::swap(&mut result, self);
        result
    }
}

impl<T: Spanned> From<T> for Flag {
    fn from(other: T) -> Flag {
        Flag::Present(other.span())
    }
}

/// A pair of `namespace` and `name` keys.
#[derive(Debug, Default, PartialEq, Eq, Hash)]
pub(crate) struct QNameRef {
    /// The XML namespace supplied.
    pub(crate) namespace: Option<NamespaceRef>,

    /// The XML name supplied.
    pub(crate) name: Option<NameRef>,
}

impl QNameRef {
    /// Attempt to incrementally parse this QNameRef.
    ///
    /// If `meta` contains either `namespace` or `name` keys, they are
    /// processed and either `Ok(None)` or an error is returned.
    ///
    /// If no matching key is found, `Ok(Some(meta))` is returned for further
    /// processing.
    fn parse_incremental_from_meta<'x>(
        &mut self,
        meta: ParseNestedMeta<'x>,
    ) -> Result<Option<ParseNestedMeta<'x>>> {
        if meta.path.is_ident("name") {
            if self.name.is_some() {
                return Err(Error::new_spanned(meta.path, "duplicate `name` key"));
            }
            let value = meta.value()?;
            let name_span = value.span();
            let (new_namespace, new_name) = parse_prefixed_name(value)?;
            if let Some(new_namespace) = new_namespace {
                if let Some(namespace) = self.namespace.as_ref() {
                    let mut error = Error::new(
                        name_span,
                        "cannot combine `namespace` key with prefixed `name`",
                    );
                    error.combine(Error::new_spanned(namespace, "`namespace` was set here"));
                    return Err(error);
                }
                self.namespace = Some(new_namespace);
            }
            self.name = Some(new_name);
            Ok(None)
        } else if meta.path.is_ident("namespace") {
            if self.namespace.is_some() {
                return Err(Error::new_spanned(
                    meta.path,
                    "duplicate `namespace` key or `name` key has prefix",
                ));
            }
            self.namespace = Some(meta.value()?.parse()?);
            Ok(None)
        } else {
            Ok(Some(meta))
        }
    }
}

/// Identifies XML content to discard.
#[derive(Debug)]
pub(crate) enum DiscardSpec {
    /// `#[xml(discard(attribute..))]`
    Attribute {
        /// The span of the nested meta from which this was parsed.
        ///
        /// This is useful for error messages.
        span: Span,

        /// The value assigned to `namespace` and `name` fields inside
        /// `#[xml(discard(attribute(..)))]`, if any.
        qname: QNameRef,
    },

    /// `#[xml(discard(text))]`
    Text {
        /// The span of the nested meta from which this was parsed.
        ///
        /// This is useful for error messages.
        span: Span,
    },
}

impl DiscardSpec {
    pub(crate) fn span(&self) -> Span {
        match self {
            Self::Attribute { ref span, .. } => *span,
            Self::Text { ref span, .. } => *span,
        }
    }
}

impl TryFrom<XmlFieldMeta> for DiscardSpec {
    type Error = syn::Error;

    fn try_from(other: XmlFieldMeta) -> Result<Self> {
        match other {
            XmlFieldMeta::Attribute {
                span,
                kind: AttributeKind::Generic(qname),
                default_,
                type_,
                codec,
            } => {
                reject_key!(default_ flag not on "discard specifications" only on "fields");
                reject_key!(type_ not on "discard specifications" only on "fields");
                reject_key!(codec not on "discard specifications" only on "fields");
                Ok(Self::Attribute { span, qname })
            }
            XmlFieldMeta::Text { span, type_, codec } => {
                reject_key!(type_ not on "discard specifications" only on "fields");
                reject_key!(codec not on "discard specifications" only on "fields");
                Ok(Self::Text { span })
            }
            other => Err(Error::new(
                other.span(),
                "cannot discard this kind of child",
            )),
        }
    }
}

/// Wrapper around `QNameRef` which saves additional span information.
#[derive(Debug)]
pub(crate) struct SpannedQNameRef {
    /// The span which created the (potentially empty) ref.
    pub span: Span,

    /// The ref itself.
    pub qname: QNameRef,
}

impl SpannedQNameRef {
    pub(crate) fn span(&self) -> Span {
        self.span
    }
}

/// Contents of an `#[xml(..)]` attribute on a struct, enum variant, or enum.
#[derive(Debug)]
pub(crate) struct XmlCompoundMeta {
    /// The span of the `#[xml(..)]` meta from which this was parsed.
    ///
    /// This is useful for error messages.
    pub(crate) span: Span,

    /// The value assigned to `namespace` and `name` fields inside
    /// `#[xml(..)]`, if any.
    pub(crate) qname: QNameRef,

    /// The debug flag.
    pub(crate) debug: Flag,

    /// The value assigned to `builder` inside `#[xml(..)]`, if any.
    pub(crate) builder: Option<Ident>,

    /// The value assigned to `iterator` inside `#[xml(..)]`, if any.
    pub(crate) iterator: Option<Ident>,

    /// The value assigned to `on_unknown_attribute` inside `#[xml(..)]`, if
    /// any.
    pub(crate) on_unknown_attribute: Option<Ident>,

    /// The value assigned to `on_unknown_child` inside `#[xml(..)]`, if
    /// any.
    pub(crate) on_unknown_child: Option<Ident>,

    /// The exhaustive flag.
    pub(crate) exhaustive: Flag,

    /// The transparent flag.
    pub(crate) transparent: Flag,

    /// Items to discard.
    pub(crate) discard: Vec<DiscardSpec>,

    /// The value assigned to `deserialize_callback` inside `#[xml(..)]`, if any.
    pub(crate) deserialize_callback: Option<Path>,

    /// The value assigned to `attribute` inside `#[xml(..)]`, if any.
    pub(crate) attribute: Option<SpannedQNameRef>,

    /// The value assigned to `value` inside `#[xml(..)]`, if any.
    pub(crate) value: Option<LitStr>,
}

impl XmlCompoundMeta {
    /// Parse the meta values from a `#[xml(..)]` attribute.
    ///
    /// Undefined options or options with incompatible values are rejected
    /// with an appropriate compile-time error.
    fn parse_from_attribute(attr: &Attribute) -> Result<Self> {
        let mut qname = QNameRef::default();
        let mut builder = None;
        let mut iterator = None;
        let mut on_unknown_attribute = None;
        let mut on_unknown_child = None;
        let mut debug = Flag::Absent;
        let mut exhaustive = Flag::Absent;
        let mut transparent = Flag::Absent;
        let mut discard = Vec::new();
        let mut deserialize_callback = None;
        let mut attribute = None;
        let mut value = None;

        attr.parse_nested_meta(|meta| {
            if meta.path.is_ident("debug") {
                if debug.is_set() {
                    return Err(Error::new_spanned(meta.path, "duplicate `debug` key"));
                }
                debug = (&meta.path).into();
                Ok(())
            } else if meta.path.is_ident("builder") {
                if builder.is_some() {
                    return Err(Error::new_spanned(meta.path, "duplicate `builder` key"));
                }
                builder = Some(meta.value()?.parse()?);
                Ok(())
            } else if meta.path.is_ident("iterator") {
                if iterator.is_some() {
                    return Err(Error::new_spanned(meta.path, "duplicate `iterator` key"));
                }
                iterator = Some(meta.value()?.parse()?);
                Ok(())
            } else if meta.path.is_ident("on_unknown_attribute") {
                if on_unknown_attribute.is_some() {
                    return Err(Error::new_spanned(
                        meta.path,
                        "duplicate `on_unknown_attribute` key",
                    ));
                }
                on_unknown_attribute = Some(meta.value()?.parse()?);
                Ok(())
            } else if meta.path.is_ident("on_unknown_child") {
                if on_unknown_child.is_some() {
                    return Err(Error::new_spanned(
                        meta.path,
                        "duplicate `on_unknown_child` key",
                    ));
                }
                on_unknown_child = Some(meta.value()?.parse()?);
                Ok(())
            } else if meta.path.is_ident("exhaustive") {
                if exhaustive.is_set() {
                    return Err(Error::new_spanned(meta.path, "duplicate `exhaustive` key"));
                }
                exhaustive = (&meta.path).into();
                Ok(())
            } else if meta.path.is_ident("transparent") {
                if transparent.is_set() {
                    return Err(Error::new_spanned(meta.path, "duplicate `transparent` key"));
                }
                transparent = (&meta.path).into();
                Ok(())
            } else if meta.path.is_ident("discard") {
                meta.parse_nested_meta(|meta| {
                    discard.push(XmlFieldMeta::parse_from_meta(meta)?.try_into()?);
                    Ok(())
                })?;
                Ok(())
            } else if meta.path.is_ident("deserialize_callback") {
                if deserialize_callback.is_some() {
                    return Err(Error::new_spanned(
                        meta.path,
                        "duplicate `deserialize_callback` key",
                    ));
                }
                deserialize_callback = Some(meta.value()?.parse()?);
                Ok(())
            } else if meta.path.is_ident("attribute") {
                if attribute.is_some() {
                    return Err(Error::new_spanned(meta.path, "duplicate `attribute` key"));
                }

                let span = meta.path.span();
                let qname = if meta.input.peek(Token![=]) {
                    let (namespace, name) = parse_prefixed_name(meta.value()?)?;
                    QNameRef {
                        name: Some(name),
                        namespace,
                    }
                } else {
                    let mut qname = QNameRef::default();
                    meta.parse_nested_meta(|meta| {
                        match qname.parse_incremental_from_meta(meta)? {
                            None => Ok(()),
                            Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")),
                        }
                    })?;
                    qname
                };

                attribute = Some(SpannedQNameRef { qname, span });
                Ok(())
            } else if meta.path.is_ident("value") {
                if value.is_some() {
                    return Err(Error::new_spanned(meta.path, "duplicate `value` key"));
                }
                value = Some(meta.value()?.parse()?);
                Ok(())
            } else {
                match qname.parse_incremental_from_meta(meta)? {
                    None => Ok(()),
                    Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")),
                }
            }
        })?;

        Ok(Self {
            span: attr.span(),
            qname,
            debug,
            builder,
            iterator,
            on_unknown_attribute,
            on_unknown_child,
            exhaustive,
            transparent,
            discard,
            deserialize_callback,
            attribute,
            value,
        })
    }

    /// Search through `attrs` for a single `#[xml(..)]` attribute and parse
    /// it.
    ///
    /// Undefined options or options with incompatible values are rejected
    /// with an appropriate compile-time error.
    ///
    /// If more than one `#[xml(..)]` attribute is found, an error is
    /// emitted.
    ///
    /// If no `#[xml(..)]` attribute is found, `None` is returned.
    pub(crate) fn try_parse_from_attributes(attrs: &[Attribute]) -> Result<Option<Self>> {
        let mut result = None;
        for attr in attrs {
            if !attr.path().is_ident("xml") {
                continue;
            }
            if result.is_some() {
                return Err(syn::Error::new_spanned(
                    attr.path(),
                    "only one #[xml(..)] per struct or enum variant allowed",
                ));
            }
            result = Some(Self::parse_from_attribute(attr)?);
        }
        Ok(result)
    }

    /// Search through `attrs` for a single `#[xml(..)]` attribute and parse
    /// it.
    ///
    /// Undefined options or options with incompatible values are rejected
    /// with an appropriate compile-time error.
    ///
    /// If more than one or no `#[xml(..)]` attribute is found, an error is
    /// emitted.
    pub(crate) fn parse_from_attributes(attrs: &[Attribute]) -> Result<Self> {
        match Self::try_parse_from_attributes(attrs)? {
            Some(v) => Ok(v),
            None => Err(syn::Error::new(
                Span::call_site(),
                "#[xml(..)] attribute required on struct or enum variant",
            )),
        }
    }
}

/// Return true if the tokens the cursor points at are a valid type path
/// prefix.
///
/// This does not advance the parse stream.
///
/// If the tokens *do* look like a type path, a Span which points at the first
/// `<` encountered is returned. This can be used for a helpful error message
/// in case parsing the type path does then fail.
fn maybe_type_path(p: parse::ParseStream<'_>) -> (bool, Option<Span>) {
    // ParseStream cursors do not advance the stream, but they are also rather
    // unwieldy to use. Prepare for a lot of `let .. = ..`.

    let cursor = if p.peek(token::PathSep) {
        // If we have a path separator, we need to skip that initially. We
        // do this by skipping two punctuations. We use unwrap() here because
        // we already know for sure that we see two punctuation items (because
        // of the peek).
        p.cursor().punct().unwrap().1.punct().unwrap().1
    } else {
        // No `::` initially, so we just take what we have.
        p.cursor()
    };

    // Now we loop over `$ident::` segments. If we find anything but a `:`
    // after the ident, we exit. Depending on *what* we find, we either exit
    // true or false, but see for yourself.
    let mut cursor = cursor;
    loop {
        // Here we look for the identifier, but we do not care for its
        // contents.
        let Some((_, new_cursor)) = cursor.ident() else {
            return (false, None);
        };
        cursor = new_cursor;

        // Now we see what actually follows the ident (it must be punctuation
        // for it to be a type path...)
        let Some((punct, new_cursor)) = cursor.punct() else {
            return (false, None);
        };
        cursor = new_cursor;

        match punct.as_char() {
            // Looks like a `foo<..`, we treat that as a type path for the
            // reasons stated in [`parse_codec_expr`]'s doc.
            '<' => return (true, Some(punct.span())),

            // Continue looking ahead: looks like a path separator.
            ':' => (),

            // Anything else (such as `,` (separating another argument most
            // likely), or `.` (a method call?)) we treat as "not a type
            // path".
            _ => return (false, None),
        }

        // If we are here, we saw a `:`. Look for the second one.
        let Some((punct, new_cursor)) = cursor.punct() else {
            return (false, None);
        };
        cursor = new_cursor;

        if punct.as_char() != ':' {
            // If it is not another `:`, it cannot be a type path.
            return (false, None);
        }

        // And round and round and round it goes.
        // We will terminate eventually because the cursor will return None
        // on any of the lookups because parse streams are (hopefully!)
        // finite. Most likely, we'll however encounter a `<` or other non-`:`
        // punctuation first.
    }
}

/// Parse expressions passed to `codec`.
///
/// Those will generally be paths to unit type constructors (such as `Foo`)
/// or references to static values or chains of function calls.
///
/// In the case of unit type constructors for generic types, users may type
/// for example `FixedHex<20>`, thinking they are writing a type path. However,
/// while `FixedHex<20>` is indeed a valid type path, it is not a valid
/// expression for a unit type constructor. Instead it is parsed as
/// `FixedHex < 20` and then a syntax error.
///
/// We however know that `Foo < Bar` is never a valid expression for a type.
/// Thus, we can be smart about this and inject the `::` at the right place
/// automatically.
fn parse_codec_expr(p: parse::ParseStream<'_>) -> Result<(Expr, Option<Error>)> {
    let (maybe_type_path, punct_span) = maybe_type_path(p);
    if maybe_type_path {
        let helpful_error =
            punct_span.map(|span| Error::new(span, "help: try inserting a `::` before this `<`"));
        let mut type_path: TypePath = match p.parse() {
            Ok(v) => v,
            Err(mut e) => match helpful_error {
                Some(help) => {
                    e.combine(help);
                    return Err(e);
                }
                None => return Err(e),
            },
        };
        // We got a type path -- so we now inject the `::` before any `<` as
        // needed.
        for segment in type_path.path.segments.iter_mut() {
            if let PathArguments::AngleBracketed(ref mut arguments) = segment.arguments {
                let span = arguments.span();
                arguments.colon2_token.get_or_insert(token::PathSep {
                    spans: [span, span],
                });
            }
        }
        Ok((
            Expr::Path(ExprPath {
                attrs: Vec::new(),
                qself: type_path.qself,
                path: type_path.path,
            }),
            helpful_error,
        ))
    } else {
        p.parse().map(|x| (x, None))
    }
}

/// Parse an XML name while resolving built-in namespace prefixes.
fn parse_prefixed_name(
    value: syn::parse::ParseStream<'_>,
) -> Result<(Option<NamespaceRef>, NameRef)> {
    if !value.peek(LitStr) {
        // if we don't have a string literal next, we delegate to the default
        // `NameRef` parser.
        return Ok((None, value.parse()?));
    }

    let name: LitStr = value.parse()?;
    let name_span = name.span();
    let (prefix, name) = match name
        .value()
        .try_into()
        .and_then(|name: rxml_validation::Name| name.split_name())
    {
        Ok(v) => v,
        Err(e) => {
            return Err(Error::new(
                name_span,
                format!("not a valid XML name: {}", e),
            ))
        }
    };
    let name = NameRef::Literal {
        value: name,
        span: name_span,
    };
    if let Some(prefix) = prefix {
        let namespace_uri = match prefix.as_str() {
            "xml" => XMLNS_XML,
            "xmlns" => XMLNS_XMLNS,
            other => return Err(Error::new(
                name_span,
                format!("prefix `{}` is not a built-in prefix and cannot be used. specify the desired namespace using the `namespace` key instead.", other)
            )),
        };
        Ok((Some(NamespaceRef::fudge(namespace_uri, name_span)), name))
    } else {
        Ok((None, name))
    }
}

/// XML attribute subtypes for `#[xml(attribute)]` and `#[xml(lang)]`.
#[derive(Debug)]
pub(crate) enum AttributeKind {
    /// Any generic attribute (`#[xml(attribute)]`).
    Generic(QNameRef),

    /// The special `xml:lang` attribute (`#[xml(lang)]`).
    XmlLang,
}

/// Contents of an `#[xml(..)]` attribute on a struct or enum variant member.
#[derive(Debug)]
pub(crate) enum XmlFieldMeta {
    /// `#[xml(attribute)]`, `#[xml(attribute = ..)]` or `#[xml(attribute(..))]`, `#[xml(lang)]`
    Attribute {
        /// The span of the `#[xml(attribute)]` meta from which this was parsed.
        ///
        /// This is useful for error messages.
        span: Span,

        /// Attribute subtype (normal vs. `xml:lang`).
        kind: AttributeKind,

        /// The `default` flag.
        default_: Flag,

        /// An explicit type override, only usable within extracts.
        type_: Option<Type>,

        /// The path to the optional codec type.
        codec: Option<Expr>,
    },

    /// `#[xml(text)]`
    Text {
        /// The span of the `#[xml(text)]` meta from which this was parsed.
        ///
        /// This is useful for error messages.
        span: Span,

        /// The path to the optional codec type.
        codec: Option<Expr>,

        /// An explicit type override, only usable within extracts.
        type_: Option<Type>,
    },

    /// `#[xml(child)`
    Child {
        /// The span of the `#[xml(child)]` meta from which this was parsed.
        ///
        /// This is useful for error messages.
        span: Span,

        /// The `default` flag.
        default_: Flag,

        /// The `n` flag.
        amount: Option<AmountConstraint>,
    },

    /// `#[xml(extract)]
    Extract {
        /// The span of the `#[xml(extract)]` meta from which this was parsed.
        ///
        /// This is useful for error messages.
        span: Span,

        /// The namespace/name keys.
        qname: QNameRef,

        /// The `n` flag.
        amount: Option<AmountConstraint>,

        /// The `default` flag.
        default_: Flag,

        /// The `fields` nested meta.
        fields: Vec<XmlFieldMeta>,

        /// The `on_unknown_attribute` value.
        on_unknown_attribute: Option<Ident>,

        /// The `on_unknown_child` value.
        on_unknown_child: Option<Ident>,
    },

    /// `#[xml(element)]`
    Element {
        /// The span of the `#[xml(element)]` meta from which this was parsed.
        ///
        /// This is useful for error messages.
        span: Span,

        /// The `default` flag.
        default_: Flag,

        /// The `n` flag.
        amount: Option<AmountConstraint>,
    },

    /// `#[xml(flag)]
    Flag {
        /// The span of the `#[xml(flag)]` meta from which this was parsed.
        ///
        /// This is useful for error messages.
        span: Span,

        /// The namespace/name keys.
        qname: QNameRef,
    },
}

impl XmlFieldMeta {
    /// Parse a `#[xml(attribute(..))]` meta.
    ///
    /// That meta can have three distinct syntax styles:
    /// - argument-less: `#[xml(attribute)]`
    /// - shorthand: `#[xml(attribute = ..)]`
    /// - full: `#[xml(attribute(..))]`
    fn attribute_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
        if meta.input.peek(Token![=]) {
            // shorthand syntax
            let (namespace, name) = parse_prefixed_name(meta.value()?)?;
            Ok(Self::Attribute {
                span: meta.path.span(),
                kind: AttributeKind::Generic(QNameRef {
                    name: Some(name),
                    namespace,
                }),
                default_: Flag::Absent,
                type_: None,
                codec: None,
            })
        } else if meta.input.peek(syn::token::Paren) {
            // full syntax
            let mut qname = QNameRef::default();
            let mut default_ = Flag::Absent;
            let mut type_ = None;
            let mut codec = None;
            meta.parse_nested_meta(|meta| {
                if meta.path.is_ident("default") {
                    if default_.is_set() {
                        return Err(Error::new_spanned(meta.path, "duplicate `default` key"));
                    }
                    default_ = (&meta.path).into();
                    Ok(())
                } else if meta.path.is_ident("type_") {
                    if type_.is_some() {
                        return Err(Error::new_spanned(meta.path, "duplicate `type_` key"));
                    }
                    type_ = Some(meta.value()?.parse()?);
                    Ok(())
                } else if meta.path.is_ident("codec") {
                    if codec.is_some() {
                        return Err(Error::new_spanned(meta.path, "duplicate `codec` key"));
                    }
                    let (new_codec, helpful_error) = parse_codec_expr(meta.value()?)?;
                    // See the comment at the top of text_from_meta() below for why we
                    // do this.
                    let lookahead = meta.input.lookahead1();
                    if !lookahead.peek(Token![,]) && !meta.input.is_empty() {
                        if let Some(helpful_error) = helpful_error {
                            let mut e = lookahead.error();
                            e.combine(helpful_error);
                            return Err(e);
                        }
                    }
                    codec = Some(new_codec);
                    Ok(())
                } else {
                    match qname.parse_incremental_from_meta(meta)? {
                        None => Ok(()),
                        Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")),
                    }
                }
            })?;
            Ok(Self::Attribute {
                span: meta.path.span(),
                kind: AttributeKind::Generic(qname),
                default_,
                type_,
                codec,
            })
        } else {
            // argument-less syntax
            Ok(Self::Attribute {
                span: meta.path.span(),
                kind: AttributeKind::Generic(QNameRef::default()),
                default_: Flag::Absent,
                type_: None,
                codec: None,
            })
        }
    }

    /// Parse a `#[xml(text)]` meta.
    fn text_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
        if meta.input.peek(Token![=]) {
            let (codec, helpful_error) = parse_codec_expr(meta.value()?)?;
            // A meta value can only be followed by either a `,`, or the end
            // of the parse stream (because of the delimited group ending).
            // Hence we check we are there. And if we are *not* there, we emit
            // an error straight away, with the helpful addition from the
            // `parse_codec_expr` if we have it.
            //
            // If we do not do this, the user gets a rather confusing
            // "expected `,`" message if the `maybe_type_path` guess was
            // wrong.
            let lookahead = meta.input.lookahead1();
            if !lookahead.peek(Token![,]) && !meta.input.is_empty() {
                if let Some(helpful_error) = helpful_error {
                    let mut e = lookahead.error();
                    e.combine(helpful_error);
                    return Err(e);
                }
            }
            Ok(Self::Text {
                span: meta.path.span(),
                type_: None,
                codec: Some(codec),
            })
        } else if meta.input.peek(syn::token::Paren) {
            let mut codec: Option<Expr> = None;
            let mut type_: Option<Type> = None;
            meta.parse_nested_meta(|meta| {
                if meta.path.is_ident("codec") {
                    if codec.is_some() {
                        return Err(Error::new_spanned(meta.path, "duplicate `codec` key"));
                    }
                    let (new_codec, helpful_error) = parse_codec_expr(meta.value()?)?;
                    // See above (at the top-ish of this function) for why we
                    // do this.
                    let lookahead = meta.input.lookahead1();
                    if !lookahead.peek(Token![,]) && !meta.input.is_empty() {
                        if let Some(helpful_error) = helpful_error {
                            let mut e = lookahead.error();
                            e.combine(helpful_error);
                            return Err(e);
                        }
                    }
                    codec = Some(new_codec);
                    Ok(())
                } else if meta.path.is_ident("type_") {
                    if type_.is_some() {
                        return Err(Error::new_spanned(meta.path, "duplicate `type_` key"));
                    }
                    type_ = Some(meta.value()?.parse()?);
                    Ok(())
                } else {
                    Err(Error::new_spanned(meta.path, "unsupported key"))
                }
            })?;
            Ok(Self::Text {
                span: meta.path.span(),
                type_,
                codec,
            })
        } else {
            Ok(Self::Text {
                span: meta.path.span(),
                type_: None,
                codec: None,
            })
        }
    }

    /// Parse a `#[xml(child)]` meta.
    fn child_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
        if meta.input.peek(syn::token::Paren) {
            let mut default_ = Flag::Absent;
            let mut amount = None;
            meta.parse_nested_meta(|meta| {
                if meta.path.is_ident("default") {
                    if default_.is_set() {
                        return Err(Error::new_spanned(meta.path, "duplicate `default` key"));
                    }
                    default_ = (&meta.path).into();
                    Ok(())
                } else if meta.path.is_ident("n") {
                    if amount.is_some() {
                        return Err(Error::new_spanned(meta.path, "duplicate `n` key"));
                    }
                    amount = Some(meta.value()?.parse()?);
                    Ok(())
                } else {
                    Err(Error::new_spanned(meta.path, "unsupported key"))
                }
            })?;
            Ok(Self::Child {
                span: meta.path.span(),
                default_,
                amount,
            })
        } else {
            Ok(Self::Child {
                span: meta.path.span(),
                default_: Flag::Absent,
                amount: None,
            })
        }
    }

    /// Parse a `#[xml(extract)]` meta.
    fn extract_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
        let mut qname = QNameRef::default();
        let mut fields = None;
        let mut amount = None;
        let mut on_unknown_attribute = None;
        let mut on_unknown_child = None;
        let mut default_ = Flag::Absent;
        meta.parse_nested_meta(|meta| {
            if meta.path.is_ident("default") {
                if default_.is_set() {
                    return Err(Error::new_spanned(meta.path, "duplicate `default` key"));
                }
                default_ = (&meta.path).into();
                Ok(())
            } else if meta.path.is_ident("fields") {
                if let Some((fields_span, _)) = fields.as_ref() {
                    let mut error = Error::new_spanned(meta.path, "duplicate `fields` meta");
                    error.combine(Error::new(*fields_span, "previous `fields` meta was here"));
                    return Err(error);
                }
                let mut new_fields = Vec::new();
                meta.parse_nested_meta(|meta| {
                    new_fields.push(XmlFieldMeta::parse_from_meta(meta)?);
                    Ok(())
                })?;
                fields = Some((meta.path.span(), new_fields));
                Ok(())
            } else if meta.path.is_ident("n") {
                if amount.is_some() {
                    return Err(Error::new_spanned(meta.path, "duplicate `n` key"));
                }
                amount = Some(meta.value()?.parse()?);
                Ok(())
            } else if meta.path.is_ident("on_unknown_attribute") {
                if on_unknown_attribute.is_some() {
                    return Err(Error::new_spanned(
                        meta.path,
                        "duplicate `on_unknown_attribute` key",
                    ));
                }
                on_unknown_attribute = Some(meta.value()?.parse()?);
                Ok(())
            } else if meta.path.is_ident("on_unknown_child") {
                if on_unknown_child.is_some() {
                    return Err(Error::new_spanned(
                        meta.path,
                        "duplicate `on_unknown_child` key",
                    ));
                }
                on_unknown_child = Some(meta.value()?.parse()?);
                Ok(())
            } else {
                match qname.parse_incremental_from_meta(meta)? {
                    None => Ok(()),
                    Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")),
                }
            }
        })?;
        let fields = fields.map(|(_, x)| x).unwrap_or_else(Vec::new);
        Ok(Self::Extract {
            span: meta.path.span(),
            default_,
            qname,
            fields,
            amount,
            on_unknown_attribute,
            on_unknown_child,
        })
    }

    /// Parse a `#[xml(element)]` meta.
    fn element_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
        let mut amount = None;
        let mut default_ = Flag::Absent;
        if meta.input.peek(syn::token::Paren) {
            meta.parse_nested_meta(|meta| {
                if meta.path.is_ident("default") {
                    if default_.is_set() {
                        return Err(Error::new_spanned(meta.path, "duplicate `default` key"));
                    }
                    default_ = (&meta.path).into();
                    Ok(())
                } else if meta.path.is_ident("n") {
                    if amount.is_some() {
                        return Err(Error::new_spanned(meta.path, "duplicate `n` key"));
                    }
                    amount = Some(meta.value()?.parse()?);
                    Ok(())
                } else {
                    Err(Error::new_spanned(meta.path, "unsupported key"))
                }
            })?;
        }
        Ok(Self::Element {
            span: meta.path.span(),
            default_,
            amount,
        })
    }

    /// Parse a `#[xml(flag)]` meta.
    fn flag_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
        let mut qname = QNameRef::default();
        if meta.input.peek(syn::token::Paren) {
            meta.parse_nested_meta(|meta| match qname.parse_incremental_from_meta(meta)? {
                None => Ok(()),
                Some(meta) => Err(Error::new_spanned(meta.path, "unsupported key")),
            })?;
        }
        Ok(Self::Flag {
            span: meta.path.span(),
            qname,
        })
    }

    /// Parse a `#[xml(lang)]` meta.
    fn lang_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
        let mut default_ = Flag::Absent;
        let mut type_ = None;
        let mut codec = None;

        if meta.input.peek(syn::token::Paren) {
            meta.parse_nested_meta(|meta| {
                if meta.path.is_ident("default") {
                    if default_.is_set() {
                        return Err(Error::new_spanned(meta.path, "duplicate `default` key"));
                    }
                    default_ = (&meta.path).into();
                    Ok(())
                } else if meta.path.is_ident("type_") {
                    if type_.is_some() {
                        return Err(Error::new_spanned(meta.path, "duplicate `type_` key"));
                    }
                    type_ = Some(meta.value()?.parse()?);
                    Ok(())
                } else if meta.path.is_ident("codec") {
                    if codec.is_some() {
                        return Err(Error::new_spanned(meta.path, "duplicate `codec` key"));
                    }
                    let (new_codec, helpful_error) = parse_codec_expr(meta.value()?)?;
                    // See the comment at the top of text_from_meta() below for why we
                    // do this.
                    let lookahead = meta.input.lookahead1();
                    if !lookahead.peek(Token![,]) && !meta.input.is_empty() {
                        if let Some(helpful_error) = helpful_error {
                            let mut e = lookahead.error();
                            e.combine(helpful_error);
                            return Err(e);
                        }
                    }
                    codec = Some(new_codec);
                    Ok(())
                } else {
                    Err(Error::new_spanned(meta.path, "unsupported key"))
                }
            })?;
        }

        Ok(Self::Attribute {
            span: meta.path.span(),
            kind: AttributeKind::XmlLang,
            default_,
            type_,
            codec,
        })
    }

    /// Parse [`Self`] from a nestd meta, switching on the identifier
    /// of that nested meta.
    fn parse_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
        if meta.path.is_ident("attribute") {
            Self::attribute_from_meta(meta)
        } else if meta.path.is_ident("text") {
            Self::text_from_meta(meta)
        } else if meta.path.is_ident("child") {
            Self::child_from_meta(meta)
        } else if meta.path.is_ident("extract") {
            Self::extract_from_meta(meta)
        } else if meta.path.is_ident("element") {
            Self::element_from_meta(meta)
        } else if meta.path.is_ident("flag") {
            Self::flag_from_meta(meta)
        } else if meta.path.is_ident("lang") {
            Self::lang_from_meta(meta)
        } else {
            Err(Error::new_spanned(meta.path, "unsupported field meta"))
        }
    }

    /// Parse an `#[xml(..)]` meta on a field.
    ///
    /// This switches based on the first identifier within the `#[xml(..)]`
    /// meta and generates an enum variant accordingly.
    ///
    /// Only a single nested meta is allowed; more than one will be
    /// rejected with an appropriate compile-time error.
    ///
    /// If no meta is contained at all, a compile-time error is generated.
    ///
    /// Undefined options or options with incompatible values are rejected
    /// with an appropriate compile-time error.
    pub(crate) fn parse_from_attribute(attr: &Attribute) -> Result<Self> {
        let mut result: Option<Self> = None;

        attr.parse_nested_meta(|meta| {
            if result.is_some() {
                return Err(Error::new_spanned(
                    meta.path,
                    "multiple field type specifiers are not supported",
                ));
            }

            result = Some(Self::parse_from_meta(meta)?);
            Ok(())
        })?;

        if let Some(result) = result {
            Ok(result)
        } else {
            Err(Error::new_spanned(
                attr,
                "missing field type specifier within `#[xml(..)]`",
            ))
        }
    }

    /// Find and parse a `#[xml(..)]` meta on a field.
    ///
    /// This invokes [`Self::parse_from_attribute`] internally on the first
    /// encountered `#[xml(..)]` meta.
    ///
    /// If not exactly one `#[xml(..)]` meta is encountered, an error is
    /// returned. The error is spanned to `err_span`.
    pub(crate) fn parse_from_attributes(attrs: &[Attribute], err_span: &Span) -> Result<Self> {
        let mut result: Option<Self> = None;
        for attr in attrs {
            if !attr.path().is_ident("xml") {
                continue;
            }

            if result.is_some() {
                return Err(Error::new_spanned(
                    attr,
                    "only one #[xml(..)] attribute per field allowed.",
                ));
            }

            result = Some(Self::parse_from_attribute(attr)?);
        }

        if let Some(result) = result {
            Ok(result)
        } else {
            Err(Error::new(*err_span, "missing #[xml(..)] meta on field"))
        }
    }

    /// Return a span which points at the meta which constructed this
    /// XmlFieldMeta.
    pub(crate) fn span(&self) -> Span {
        match self {
            Self::Attribute { ref span, .. } => *span,
            Self::Child { ref span, .. } => *span,
            Self::Text { ref span, .. } => *span,
            Self::Extract { ref span, .. } => *span,
            Self::Element { ref span, .. } => *span,
            Self::Flag { ref span, .. } => *span,
        }
    }

    /// Extract an explicit type specification if it exists.
    pub(crate) fn take_type(&mut self) -> Option<Type> {
        match self {
            Self::Attribute { ref mut type_, .. } => type_.take(),
            Self::Text { ref mut type_, .. } => type_.take(),
            _ => None,
        }
    }
}
