//! [`KeySpecifier`] derive-deftly macro and its support code
//!
//! # STABILITY - NOTHING IN THIS MODULE IS PART OF THE STABLE PUBLIC API
//!
//! The `pub` items in this module are accessible as `$crate::key_specifier_derive`,
//! but `#[doc(hidden)]` is applied at the top level.
//!
//! (Recall that the actual derive-deftly macro
//! `KeySpecifier` ends up in the crate toplevel,
//! so that *does* form part of our public API.)

use std::iter;

use derive_deftly::define_derive_deftly;
use itertools::{EitherOrBoth, Itertools, izip};

use super::*;
use crate::DENOTATOR_SEP;

pub use crate::KeyPathInfoBuilder;
pub use tor_error::{Bug, internal, into_internal};

/// Trait for (only) formatting as a [`KeySpecifierComponent`]
///
/// Like the formatting part of `KeySpecifierComponent`
/// but implemented for Option and &str too.
pub trait RawKeySpecifierComponent {
    /// Append `self`s `KeySpecifierComponent` string representation to `s`
    //
    // This is not quite like `KeySpecifierComponent::to_slug`,
    // since that *returns* a String (effectively) and we *append*.
    // At some future point we may change KeySpecifierComponent,
    // although the current API has the nice feature that
    // the syntax of the appended string is checked before we receive it here.
    fn append_to(&self, s: &mut String) -> Result<(), Bug>;
}
impl<T: KeySpecifierComponent> RawKeySpecifierComponent for T {
    fn append_to(&self, s: &mut String) -> Result<(), Bug> {
        self.to_slug()?.as_str().append_to(s)
    }
}
impl<T: KeySpecifierComponent> RawKeySpecifierComponent for Option<T> {
    fn append_to(&self, s: &mut String) -> Result<(), Bug> {
        let v: &dyn RawKeySpecifierComponent = match self.as_ref() {
            Some(v) => v,
            None => &"*",
        };
        v.append_to(s)
    }
}
impl<'s> RawKeySpecifierComponent for &'s str {
    fn append_to(&self, s: &mut String) -> Result<(), Bug> {
        s.push_str(self);
        Ok(())
    }
}

/// Make a string like `pc/pc/pc/lc_lc_lc`
fn arti_path_string_from_components(
    path_comps: &[&dyn RawKeySpecifierComponent],
    leaf_comps: &[&dyn RawKeySpecifierComponent],
) -> Result<String, Bug> {
    let mut path = String::new();

    for comp in path_comps {
        comp.append_to(&mut path)?;
        path.push('/');
    }
    for (delim, comp) in izip!(
        iter::once(None).chain(iter::repeat(Some(DENOTATOR_SEP))),
        leaf_comps,
    ) {
        if let Some(delim) = delim {
            path.push(delim);
        }
        comp.append_to(&mut path)?;
    }

    Ok(path)
}

/// Make a string like `pc/pc/pc/pd+pd+pd@cd+cd+cd+cd`
fn cert_arti_path_string_from_components(
    subj_comp: &str,
    leaf_comps: &[&dyn RawKeySpecifierComponent],
) -> Result<String, Bug> {
    if leaf_comps.is_empty() {
        return Ok(subj_comp.to_string());
    }

    let mut path = if subj_comp.contains('+') {
        format!("{subj_comp}@")
    } else {
        format!("{subj_comp}+@")
    };

    for (delim, comp) in izip!(
        iter::once(None).chain(iter::repeat(Some(DENOTATOR_SEP))),
        leaf_comps,
    ) {
        if let Some(delim) = delim {
            path.push(delim);
        }
        comp.append_to(&mut path)?;
    }

    Ok(path)
}

/// Make an `ArtiPath` like `pc/pc/pc/lc_lc_lc`
///
/// This is the engine for the `KeySpecifier` macro's `arti_path()` impls.
///
/// The macro-generated code sets up couple of vectors.
/// Each vector entry is a pointer to the field in the original struct,
/// plus a vtable pointer saying what to do with it.
///
/// For fixed elements in the path,
/// the vtable entry's data pointer is a pointer to a constant &str.
///
/// In the macro, this is done by the user-defined expansion `ARTI_FROM_COMPONENTS_ARGS`.
///
/// Doing it this way minimises the amount of macro-generated machine code.
pub fn arti_path_from_components(
    path_comps: &[&dyn RawKeySpecifierComponent],
    leaf_comps: &[&dyn RawKeySpecifierComponent],
) -> Result<ArtiPath, ArtiPathUnavailableError> {
    Ok(arti_path_string_from_components(path_comps, leaf_comps)?
        .try_into()
        .map_err(into_internal!("bad ArtiPath from good components"))?)
}

/// Make a `KeyPathPattern::Arti` like `pc/pc/pc/lc_lc_lc`
pub fn arti_pattern_from_components(
    path_comps: &[&dyn RawKeySpecifierComponent],
    leaf_comps: &[&dyn RawKeySpecifierComponent],
) -> Result<KeyPathPattern, Bug> {
    Ok(KeyPathPattern::Arti(arti_path_string_from_components(
        path_comps, leaf_comps,
    )?))
}

/// Make a `KeyPathPattern::Arti` for a certificate specifier
pub fn cert_arti_pattern_from_components(
    subj_path: &str,
    leaf_comps: &[&dyn RawKeySpecifierComponent],
) -> Result<KeyPathPattern, Bug> {
    Ok(KeyPathPattern::Arti(cert_arti_path_string_from_components(
        subj_path, leaf_comps,
    )?))
}

/// Error returned from [`RawKeySpecifierComponentParser::parse`]
#[derive(Debug)]
#[allow(clippy::exhaustive_enums)] // Not part of public API
pub enum RawComponentParseResult {
    /// This was a field
    ///
    /// The `Option` has been filled with the actual value.
    /// It has an entry in the `keys` argument to [`parse_arti_path`].
    ParsedField,
    /// This was a literal, and it matched
    MatchedLiteral,
    /// Becomes [`ArtiPathError::PatternNotMatched`]
    PatternNotMatched,
    /// `InvalidKeyPathComponentValue`
    Invalid(InvalidKeyPathComponentValue),
}

use RawComponentParseResult as RCPR;

/// Trait for parsing a path component, used by [`parse_arti_path`]
///
/// Implemented for `Option<impl KeySpecifierComponent>`,
/// and guarantees to fill in the Option if it succeeds.
///
/// Also implemented for `&str`: just checks that the string is right,
/// (and, doesn't modify `*self`).
pub trait RawKeySpecifierComponentParser {
    /// Check that `comp` is as expected, and store any results in `self`.
    fn parse(&mut self, comp: &Slug) -> RawComponentParseResult;
}

impl<T: KeySpecifierComponent> RawKeySpecifierComponentParser for Option<T> {
    fn parse(&mut self, comp: &Slug) -> RawComponentParseResult {
        let v = match T::from_slug(comp) {
            Ok(v) => v,
            Err(e) => return RCPR::Invalid(e),
        };
        *self = Some(v);
        RCPR::ParsedField
    }
}
impl<'s> RawKeySpecifierComponentParser for &'s str {
    fn parse(&mut self, comp: &Slug) -> RawComponentParseResult {
        if comp.as_str() == *self {
            RCPR::MatchedLiteral
        } else {
            RCPR::PatternNotMatched
        }
    }
}

/// List of parsers for fields
type Parsers<'p> = [&'p mut dyn RawKeySpecifierComponentParser];

/// Split a string into components and parse each one
fn extract(
    input: Option<&str>,
    delim: char,
    parsers: &mut Parsers,
    keys: &mut &[&str],
) -> Result<(), ArtiPathError> {
    for ent in Itertools::zip_longest(
        input.map(|input| input.split(delim)).into_iter().flatten(),
        parsers,
    ) {
        let EitherOrBoth::Both(comp, parser) = ent else {
            // wrong number of components
            return Err(ArtiPathError::PatternNotMatched);
        };

        // TODO would be nice to avoid allocating again here,
        // but I think that needs an `SlugRef`.
        let comp = Slug::new(comp.to_owned())
            .map_err(ArtiPathSyntaxError::Slug)
            .map_err(ArtiPathError::InvalidArtiPath)?;

        let missing_keys = || internal!("keys list too short, bad args to parse_arti_path");

        match parser.parse(&comp) {
            RCPR::PatternNotMatched => Err(ArtiPathError::PatternNotMatched),
            RCPR::Invalid(error) => Err(ArtiPathError::InvalidKeyPathComponentValue {
                error,
                key: keys.first().ok_or_else(missing_keys)?.to_string(),
                value: comp,
            }),
            RCPR::ParsedField => {
                *keys = keys.split_first().ok_or_else(missing_keys)?.1;
                Ok(())
            }
            RCPR::MatchedLiteral => Ok(()),
        }?;
    }
    Ok(())
}

/// Parse a `KeyPath` as an `ArtiPath` like pc/pc/pc/lc_lc_lc
///
/// `keys` is the field names for each of the path_parsers and leaf_parsers,
/// *but* only the ones which will return `RCPR::ParsedField` (or `::Invalid`).
///
/// As with `arti_path_string_components` etc., we try to minimise
/// the amount of macro-generated machine code.
///
/// The macro-generated impl again assembles two vectors,
/// one for the path components and one for the leaf components.
///
/// For a field, the vector entry is a pointer to `&mut Option<...>`
/// for the field, along with a `RawKeySpecifierComponentParser` vtable entry.
/// (The macro-generated impl must unwrap each of these Options,
/// to assemble the final struct.  In principle this could be avoided with
/// use of `MaybeUninit` and unsafe.)
///
/// For a fixed string component, the vector entry data pointer points to its `&str`.
/// "Parsing" consists of checking that the string is as expected.
///
/// We also need the key names for error reporting.
/// We pass this as a *single* array, and a double-reference to the slice,
/// since that resolves to one pointer to a static structure.
pub fn parse_arti_path(
    arti_path: &ArtiPath,
    keys: &&[&str],
    path_parsers: &mut Parsers,
    leaf_parsers: &mut Parsers,
) -> Result<(), ArtiPathError> {
    let path = arti_path.as_str();

    let (path, leaf) = match path.rsplit_once('/') {
        Some((path, leaf)) => (Some(path), leaf),
        None => (None, path),
    };

    let mut keys: &[&str] = keys;

    extract(path, '/', path_parsers, &mut keys)?;
    extract(Some(leaf), DENOTATOR_SEP, leaf_parsers, &mut keys)?;
    Ok(())
}

/// Parse the denotators from the `ArtiPath` of a certificate.
///
/// The specified `cert_denos` should be the substring of an `ArtiPath`
/// containing the certificate denotator group.
///
/// The `leaf_parses` and `keys` arguments serve the same purpose as in
/// [`parse_arti_path`].
pub fn parse_cert_denotators(
    cert_denos: &str,
    keys: &&[&str],
    leaf_parsers: &mut Parsers,
) -> Result<(), ArtiPathError> {
    let mut keys: &[&str] = keys;

    extract(Some(cert_denos), DENOTATOR_SEP, leaf_parsers, &mut keys)?;

    Ok(())
}

/// Build a `KeyPathInfo` given the information about a key specifier
///
/// Calling pattern, to minimise macro-generated machine code,
/// is similar `arti_path_from_components`.
///
/// The macro-generated code parses the path into its KeySpecifier impl
/// (as an owned value) and then feeds references to the various fields
/// to `describe_via_components`.
pub fn describe_via_components(
    summary: &&str,
    role: &dyn RawKeySpecifierComponent,
    extra_keys: &&[&str],
    extra_info: &[&dyn KeySpecifierComponent],
) -> Result<KeyPathInfo, Bug> {
    let mut info = KeyPathInfoBuilder::default();
    info.summary(summary.to_string());
    info.role({
        let mut s = String::new();
        role.append_to(&mut s)?;
        s
    });
    for (key, value) in izip!(*extra_keys, extra_info) {
        let value = KeySpecifierComponentPrettyHelper(*value).to_string();
        info.extra_info(*key, value);
    }
    info.build()
        .map_err(into_internal!("failed to build KeyPathInfo"))
}

define_derive_deftly! {
    /// A helper for implementing [`KeySpecifier`]s.
    ///
    /// Applies to a struct that has some static components (`prefix`, `role`),
    /// and a number of variable components represented by its fields.
    ///
    /// Implements `KeySpecifier` etc.
    ///
    /// Each field is either a path field (which becomes a component in the `ArtiPath`),
    /// or a denotator (which becomes *part* of the final component in the `ArtiPath`).
    ///
    /// The `prefix` is the first component of the [`ArtiPath`] of the [`KeySpecifier`].
    ///
    /// The role should be the name of the key in the Tor Specifications.
    /// The **lowercased** `role` is used as the _prefix of the last component_
    /// of the [`ArtiPath`] of the specifier.
    /// The `role` is followed by the denotators of the key.
    ///
    /// The denotator fields, if there are any,
    /// should be annotated with `#[denotator]`.
    ///
    /// The declaration order of the fields is important.
    /// The inner components of the [`ArtiPath`] of the specifier are built
    /// from the string representation of its path fields, taken in declaration order,
    /// followed by the encoding of its denotators, also taken in the order they were declared.
    /// As such, all path fields, must implement [`KeySpecifierComponent`].
    /// and all denotators must implement [`KeySpecifierComponent`].
    /// The denotators are separated from the rest of the path, and from each other,
    /// by `+` characters.
    ///
    /// For example, a key specifier with `prefix` `"foo"` and `role` `"bar"`
    /// will have an [`ArtiPath`] of the form
    /// `"foo/<field1_str>/<field2_str>/../bar[+<denotators>]"`.
    ///
    /// A key specifier of this form, with denotators that encode to "d1" and "d2",
    /// would look like this: `"foo/<field1_str>/<field2_str>/../bar+d1+d2"`.
    ///
    /// ### Results of applying this macro
    ///
    /// `#[derive(Deftly)] #[derive_deftly(KeySpecifier)] struct SomeKeySpec ...`
    /// generates:
    ///
    ///  * `impl `[`KeySpecifier`]` for SomeKeySpec`
    ///  * `struct SomeKeySpecPattern`,
    ///    a derived struct which contains an `Option` for each field.
    ///    `None` in the pattern means "any".
    ///  * `impl `[`KeySpecifierPattern`]` for SomeKeySpecPattern`
    ///  * `impl TryFrom<`[`KeyPath`]> for SomeKeySpec`
    ///  * Registration of an impl of [`KeyPathInfoExtractor`]
    ///    (on a private unit struct `SomeKeySpecInfoExtractor`)
    ///
    /// ### Custom attributes
    ///
    ///  * **`#[deftly(prefix)]`** (toplevel):
    ///    Specifies the fixed prefix (the first path component).
    ///    Must be a literal string.
    ///
    ///  * **`#[deftly(role = "...")]`** (toplevel):
    ///    Specifies the role - the initial portion of the leafname.
    ///    This should be the name of the key in the Tor Specifications.
    ///    Must be a literal string.
    ///    This or the field-level `#[deftly(role)]` must be specified.
    ///
    ///  * **`[deftly(role)]` (field):
    ///    Specifies that the role is determined at runtime.
    ///    The field type must implement [`KeyDenotator`].
    ///
    ///  * **`#[deftly(summary = "...")]`** (summary, mandatory):
    ///    Specifies the summary; ends up as the `summary` field in [`KeyPathInfo`].
    ///    (See [`KeyPathInfoBuilder::summary()`].)
    ///    Must be a literal string.
    ///
    ///  * **`#[deftly(denotator)]`** (field):
    ///    Designates a field that should be represented
    ///    in the key file leafname, after the role.
    ///
    ///  * **`#[deftly(ctor_path = VARIANT)]`** (toplevel):
    ///    Specifies that this kind of key has a representation in C Tor keystores,
    ///    and provides the appropriate [`CTorPath`] variant in `VARIANT`.
    ///
    ///    Used for implementing [`CTorKeySpecifier`].
    ///
    ///    If specified, the generated [`KeySpecifier::ctor_path`] implementation
    ///    will return [`CTorPath`]::`VARIANT` populated with the fields extracted
    ///    from this type. Therefore, your type **must** have exactly the same fields
    ///    as the specified `CTorPath` variant.
    ///
    ///    If not specified, the generated [`KeySpecifier::ctor_path`]
    ///    implementation will always return `None`.
    ///
    ///  * **`#[deftly(fixed_path_component = STR)]`** (field):
    ///    Before this field insert a fixed path component `STR`.
    ///    (Can be even used before a denotator component,
    ///    to add a final fixed path component.)
    ///
    ///  * **`#[deftly(keypair_specifier = TYPE)]`** (field):
    ///    If this is the specifier for a public key, the specifier for
    ///    the corresponding keypair type.
    ///
    ///    If not specified, the generated [`KeySpecifier::keypair_specifier`]
    ///    implementation will always return `None`.
    //
    //     NOTE: The `KeySpecifier::keypair_specifier` implementation
    //     of the `ArtiPath` of a public key will always return `None`,
    //     even if the public key specifier it represents has a keypair specifier.
    //
    ///
    export KeySpecifier for struct, beta_deftly, meta_quoted rigorous:

    // A condition that evaluates to `true` for path fields.
    ${defcond F_IS_PATH not(any(fmeta(denotator), fmeta(role)))}
    ${defcond F_IS_ROLE all(fmeta(role), not(tmeta(role)))}

    #[doc = concat!("Pattern matching some or all [`", stringify!($tname), "`]")]
    #[allow(dead_code)] // Not everyone will need the pattern feature
    #[non_exhaustive]
    $tvis struct $<$tname Pattern><$tdefgens>
    where $twheres
    ${vdefbody $vname $(
        ${fattrs doc}
        ///
        /// `None` to match keys with any value for this field.
        $fvis $fname: Option<$ftype>,
    ) }

    // ** MAIN KNOWLEDGE OF HOW THE PATH IS CONSTRUCTED **
    //
    // These two user-defined expansions,
    //   $ARTI_PATH_COMPONENTS
    //   $ARTI_LEAF_COMPONENTS
    // expand to code for handling each path and leaf component,
    // in the order in which they appear in the ArtiPath.
    //
    // The "code for handling", by default, is:
    //   - for a field, take a reference to the field in `self`
    //   - for a fixed component, take a reference to a &'static str
    // in each case with a comma appended.
    // So this is suitable for including in a &[&dyn ...].
    //
    // The call site can override the behaviour by locally redefining,
    // the two user-defined expansions DO_FIELD and DO_LITERAL.
    //
    // DO_FIELD should expand to the code necessary to handle a field.
    // It probably wants to refer to $fname.
    //
    // DO_LITERAL should expand to the code necessary to handle a literal value.
    // When DO_LITERAL is called the user-defined expansion LIT will expand to
    // something like `${fmeta(...) as str}`, which will in turn expand to
    // a string literal.
    //
    // For use sites which want to distinguish the role from other fields:
    // DO_ROLE_FIELD and DO_ROLE_LITERAL are used for the role.
    // They default to expanding $DO_FIELD and $DO_LITERAL respectively.
    //
    // This is the *only* place that knows how ArtiPaths are constructed,
    // when the path syntax is defined using the KeySpecifier d-a macro.
    //
    // The actual code here is necessarily rather abstract.
    ${define ARTI_PATH_COMPONENTS {
        // #[deftly(prefix = ...)]
        ${define LIT ${tmeta(prefix) as str}}
        $DO_LITERAL

        ${for fields {
            // #[deftly(fixed_path_component = ...)]
            ${if fmeta(fixed_path_component) {
                // IWVNI d-a allowed arguments to use-defined expansions, but this will do
                ${define LIT ${fmeta(fixed_path_component) as str}}
                $DO_LITERAL
            }}
            // Path fields
            ${if F_IS_PATH { $DO_FIELD }}
        }}
    }}
    ${define ARTI_LEAF_COMPONENTS {
        ${if tmeta(role) {
            // #[deftly(role = ...)] on the toplevel
            ${define LIT { stringify!(${snake_case ${tmeta(role)}}) }}
            $DO_ROLE_LITERAL
        }}
        ${for fields {
            // #[deftly(role)] on a field
            ${if F_IS_ROLE { $DO_ROLE_FIELD }}
        }}
        ${for fields {
            // #[deftly(denotator)]
            ${if fmeta(denotator) { $DO_FIELD }}
        }}
    }}

    ${define DO_FIELD { &self.$fname, }}
    ${define DO_LITERAL { &$LIT, }}
    ${define DO_ROLE_FIELD { $DO_FIELD }}
    ${define DO_ROLE_LITERAL { $DO_LITERAL }}

    impl<$tgens> $crate::KeySpecifier for $ttype
    where $twheres
    {
        fn arti_path(
            &self,
        ) -> std::result::Result<$crate::ArtiPath, $crate::ArtiPathUnavailableError> {
            use $crate::key_specifier_derive::*;

            arti_path_from_components(
                &[ $ARTI_PATH_COMPONENTS ],
                &[ $ARTI_LEAF_COMPONENTS ],
            )
        }

        fn ctor_path(&self) -> Option<$crate::CTorPath> {
            <Self as $crate::CTorKeySpecifier>::ctor_path(self)
        }

        fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
            ${if tmeta(keypair_specifier) {
                Some(Box::new(std::convert::Into::<
                    ${tmeta(keypair_specifier) as ty}
                >::into(self)))
            } else {
                None
            }}
        }
    }

    impl<$tgens> $crate::KeySpecifierPattern for $<$tname Pattern><$tdefgens>
    where $twheres
    {
        fn arti_pattern(
            &self,
        ) -> std::result::Result<$crate::KeyPathPattern, $crate::key_specifier_derive::Bug> {
            use $crate::key_specifier_derive::*;

            arti_pattern_from_components(
                &[ $ARTI_PATH_COMPONENTS ],
                &[ $ARTI_LEAF_COMPONENTS ],
            )
        }

        fn new_any() -> Self {
            $< $tname Pattern > {
                $( $fname: None, )
            }
        }
    }

    struct $< $tname InfoExtractor >;

    impl<$tgens> $crate::KeyPathInfoExtractor for $< $tname InfoExtractor >
    where $twheres
    {
        fn describe(
            &self,
            path: &$crate::KeyPath,
        ) -> std::result::Result<$crate::KeyPathInfo, $crate::KeyPathError> {
            use $crate::key_specifier_derive::*;

            // Parse this path
            #[allow(unused_variables)] // Unused if no fields
            let spec = $ttype::try_from(path)?;

            // none of this cares about non-role literals
            // all the others three be explicitly defined each time
            ${define DO_LITERAL {}}

            static NON_ROLE_FIELD_KEYS: &[&str] = &[
                ${define DO_FIELD { stringify!($fname), }}
                ${define DO_ROLE_FIELD {}}
                ${define DO_ROLE_LITERAL {}}
                $ARTI_PATH_COMPONENTS
                $ARTI_LEAF_COMPONENTS
            ];

            describe_via_components(
                &${tmeta(summary) as str},

                // role
                ${define DO_FIELD {}}
                ${define DO_ROLE_FIELD { &spec.$fname, }}
                ${define DO_ROLE_LITERAL { &$LIT, }}
                $ARTI_LEAF_COMPONENTS

                &NON_ROLE_FIELD_KEYS,

                &[
                    ${define DO_FIELD { &spec.$fname, }}
                    ${define DO_ROLE_FIELD {}}
                    ${define DO_ROLE_LITERAL {}}
                    $ARTI_PATH_COMPONENTS
                    $ARTI_LEAF_COMPONENTS
                ],
            ).map_err($crate::KeyPathError::Bug)
        }
    }

    impl<$tgens> TryFrom<&$crate::KeyPath> for $tname
    where $twheres
    {
        type Error = $crate::KeyPathError;

        fn try_from(path: &$crate::KeyPath) -> std::result::Result<$tname, Self::Error> {
            use $crate::key_specifier_derive::*;

            static FIELD_KEYS: &[&str] = &[
                ${define DO_LITERAL {}}
                ${define DO_FIELD { stringify!($fname), }}
                $ARTI_PATH_COMPONENTS
                $ARTI_LEAF_COMPONENTS
            ];

            #[allow(unused_mut)] // not needed if there are no fields
            #[allow(unused_variables)] // not needed if there are no fields
            let mut builder =
                <$<$tname Pattern>::<$tgens> as $crate::KeySpecifierPattern>::new_any();

            ${define DO_FIELD { &mut builder.$fname, }}
            ${define DO_LITERAL { &mut $LIT, }}

            #[allow(unused_variables)] // CTorPath is only used with ctor_path(..)
            match path {
                $crate::KeyPath::Arti(path) => {
                    parse_arti_path(
                        path,
                        &FIELD_KEYS,
                        &mut [ $ARTI_PATH_COMPONENTS ],
                        &mut [ $ARTI_LEAF_COMPONENTS ],
                    ).map_err(|err| $crate::KeyPathError::Arti { path: path.clone(), err })?;
                },
                $crate::KeyPath::CTor(path) => {
                    return <Self as $crate::CTorKeySpecifier>::from_ctor_path(path.clone())
                        .map_err(|err| $crate::KeyPathError::CTor { path: path.clone(), err });
                },
                #[allow(unreachable_patterns)] // This is reachable if used outside of tor-keymgr
                &_ => {
                    return Err(internal!("unrecognized key path?!").into());
                }
            };

            #[allow(unused_variables)] // not needed if there are no fields
            let handle_none = || internal!("bad RawKeySpecifierComponentParser impl");

            Ok($tname { $(
                $fname: builder.$fname.ok_or_else(handle_none)?,
            ) })
        }
    }

    ${if tmeta(ctor_path) {

    ${define CTOR_PATH_VARIANT ${tmeta(ctor_path) as path}}

    impl<$tgens> $crate::CTorKeySpecifier for $ttype
    where $twheres
    {
        fn ctor_path(&self) -> Option<$crate::CTorPath> {
            Some($crate::CTorPath :: $CTOR_PATH_VARIANT {
                $( $fname: self.$fname.clone(), )
            })
        }

        fn from_ctor_path(
            path: $crate::CTorPath
        ) -> std::result::Result<Self, $crate::CTorPathError> {

            match path {
                $crate::CTorPath :: $CTOR_PATH_VARIANT { $( $fname, )} => {
                    Ok( Self { $( $fname, ) })
                },
                _ => Err($crate::CTorPathError::KeySpecifierMismatch(stringify!($tname).into())),
            }
        }
    }

    } else {
    impl<$tgens> $crate::CTorKeySpecifier for $ttype
    where $twheres
    {
        fn ctor_path(&self) -> Option<$crate::CTorPath> {
            None
        }

        fn from_ctor_path(
            _: $crate::CTorPath
        ) -> std::result::Result<Self, $crate::CTorPathError> {
            Err($crate::CTorPathError::MissingCTorPath(stringify!($tname).to_string()))
        }
    }

    }}

    // Register the info extractor with `KeyMgr`.
    $crate::inventory::submit!(&$< $tname InfoExtractor > as &dyn $crate::KeyPathInfoExtractor);
}

#[cfg(feature = "experimental-api")]
define_derive_deftly! {
    /// A helper for implementing [`KeyCertificateSpecifier`]s.
    ///
    /// ### Results of applying this macro
    ///
    /// `#[derive(Deftly)] #[derive_deftly(CertSpecifier)] struct SomeCertSpec ...`
    /// generates:
    ///
    ///  * `impl `[`KeyCertificateSpecifier`]` for SomeCertSpec`
    ///  * `struct SomeCertSpecPattern`,
    ///    a derived struct which contains an `Option` for each denotator field,
    ///    and a non-optional field for the subject key `KeyPathPattern`.
    ///    `None` in the pattern means "any".
    ///  * `impl `[`CertSpecifierPattern`]` for SomeCertSpecPattern`
    ///  * `impl TryFrom<`[`KeyPath`]> for SomeCertSpec`
    ///
    ///
    /// ### Custom attributes
    ///
    ///  * **`#[deftly(subject)]`** (mandatory, field):
    ///    Designates a field that represents the subject key specifier.
    ///    This should only be applied to **one** field.
    ///
    ///  * **`#[deftly(denotator)]`** (field):
    ///    Designates a field that should be represented
    ///    in the key file leafname.
    ///    The `ArtiPath` of the certificate is derived from the `ArtiPath`
    ///    of the subject key,  by concatenating the `ArtiPath` of the subject
    ///    key with provided denotators provided.
    ///    If no there are no denotators, the `ArtiPath` of the certificate
    ///    is the same as the `ArtiPath` of the subject key.
    export CertSpecifier beta_deftly, for struct:

    // Ensure exactly one field annotated with #[deftly(subject)]
    ${if not(approx_equal(${for fields { ${when fmeta(subject)} 1 }}, 1))
        { ${error "Exactly one field must be #[deftly(subject)]"} }
    }

    // All fields must be either #[deftly(subject)] or #[deftly(denotator)]
    $(
        ${when not(any(
            fmeta(subject),
            fmeta(denotator),
        ))}

        ${error
            message=${concat $fname " must be #[deftly(subject)] or #[deftly(denotator)]"}
        }
    )

    ${define SUBJ_FNAME
        ${for fields {
            ${if fmeta(subject) {
                &self.$fname
            }}
        }}
    }

    ${define SUBJ_FTYPE
        ${for fields {
            ${if fmeta(subject) {
                $ftype
            }}
        }}
    }

    ${define SUBJ_PATTERN_FTYPE
        ${for fields {
            ${if fmeta(subject) {
                $<$ftype Pattern>
            }}
        }}
    }

    impl<$tgens> $crate::KeyCertificateSpecifier for $tname<$tdefgens>
    where $twheres
    {
        fn cert_denotators(&self) -> Vec<&dyn $crate::KeySpecifierComponent> {
            vec![
                ${for fields {
                    // #[deftly(denotator)]
                    ${if fmeta(denotator) { &self.$fname, }}
                }}
            ]
        }

        fn subject_key_specifier(&self) -> &dyn $crate::KeySpecifier {
            ${SUBJ_FNAME}
        }
    }

    #[doc = concat!("Pattern matching some or all [`", stringify!($tname), "`]")]
    #[allow(dead_code)] // Not everyone will need the pattern feature
    #[non_exhaustive]
    $tvis struct $<$tname Pattern><$tdefgens>
    where $twheres
    {
        ${for fields {
            // The subject key specifier pattern is non-optional,
            // because we derive the fixed part of the pattern from it
            ${if fmeta(subject) {
                ${fattrs doc}
                $fvis $fname: $<$ftype Pattern>,
            }}
            // The denotators are optional
            ${if fmeta(denotator) {
                ${fattrs doc}
                $fvis $fname: Option<$ftype>,
            }}
        }}
    }

    ${define DO_FIELD { &self.$fname, }}

    ${define ARTI_LEAF_COMPONENTS {
        ${for fields {
            // #[deftly(denotator)]
            ${if fmeta(denotator) { $DO_FIELD }}
        }}
    }}

    impl<$tgens> $crate::CertSpecifierPattern for $<$tname Pattern><$tdefgens>
    where $twheres
    {
        type SubjectKeySpecifierPattern = ${SUBJ_PATTERN_FTYPE};

        fn arti_pattern(
            &self,
        ) -> std::result::Result<$crate::KeyPathPattern, $crate::key_specifier_derive::Bug> {
            use $crate::key_specifier_derive::*;
            use $crate::KeyPathPattern::*;
            use $crate::KeySpecifierPattern as _;

            let subj_path_pat = ${SUBJ_FNAME}.arti_pattern()?;
            let subj_path = match subj_path_pat {
                Arti(path) => path,
                _ => {
                    return Err(
                        tor_error::internal!("subject key pattern is not an Arti pattern?!").into()
                    );
                }
            };

            cert_arti_pattern_from_components(
                &subj_path,
                &[ $ARTI_LEAF_COMPONENTS ],
            )
        }

        fn new_any() -> Self {
            // Build the "any" pattern of the subject key.
            let spec =
                < <$<$tname Pattern>::<$tgens> as $crate::CertSpecifierPattern>::SubjectKeySpecifierPattern
                    as $crate::KeySpecifierPattern>::new_any();

            $< $tname Pattern > {
                $(
                    ${if fmeta(subject) { $fname: spec, }}
                )
                $(
                    ${if fmeta(denotator) { $fname: None, }}
                )
            }
        }
    }

    impl<$tgens> TryFrom<&$crate::KeyPath> for $tname
    where $twheres
    {
        type Error = $crate::KeyPathError;

        fn try_from(path: &$crate::KeyPath) -> std::result::Result<$tname, Self::Error> {
            use $crate::key_specifier_derive::*;

            let arti_path = match path {
                $crate::KeyPath::Arti(path) => path,
                &_ => {
                    return Err(tor_error::bad_api_usage!("Cert specifiers never have non-ArtiPaths").into());
                }
            };

            #[allow(unused_mut)] // not needed if there are no fields
            #[allow(unused_variables)] // not needed if there are no fields
            let mut builder =
                <$<$tname Pattern>::<$tgens> as $crate::CertSpecifierPattern>::new_any();

            let subj_key = if let Some((key_path, cert_denos)) = arti_path.split_once($crate::DENOTATOR_GROUP_SEP) {

                // Handle the special case where the subject key ArtiPath has no denotators,
                // but the cert ArtiPath *does*. This translates to paths that contain
                // the slightly odd +@ construction, where + designates the beginning
                // of the denotator section, followed by an empty denotator group.
                let key_path = match key_path.strip_suffix('+') {
                    Some(p) => p,
                    None => key_path,
                };

                let key_arti_path = $crate::ArtiPath::new(key_path.to_string())
                    .map_err(tor_error::into_internal!("cert path contains invalid key ArtiPath?!"))?;

                static FIELD_KEYS: &[&str] = &[
                    ${for fields {
                        // #[deftly(denotator)]
                        ${if fmeta(denotator) { stringify!($fname), }}
                    }}
                ];

                parse_cert_denotators(
                    cert_denos,
                    &FIELD_KEYS,
                    ${define DO_FIELD { &mut builder.$fname, }}
                    &mut [ $ARTI_LEAF_COMPONENTS ],
                ).map_err(|err| $crate::KeyPathError::Arti { path: arti_path.clone(), err })?;

                let key_spec = $crate::KeyPath::Arti(key_arti_path);

                $SUBJ_FTYPE::try_from(&key_spec)?
            } else {
                // Cert has no denotators, so the whole path is the path
                // of the subject key specifier
                $SUBJ_FTYPE::try_from(path)?
            };

            #[allow(unused_variables)] // not needed if there are no fields
            let handle_none = || internal!("bad RawKeySpecifierComponentParser impl");

            Ok($tname {
                ${for fields {
                    // The denotators are optional...
                    ${if fmeta(denotator) {
                        $fname: builder.$fname.ok_or_else(handle_none)?,
                    }}
                    // ...but subject key specifier pattern is not
                    ${if fmeta(subject) {
                        $fname: subj_key,
                    }}
                }}
            })
        }
    }

    // TODO: generate and register a KeyPathInfoExtractor impl for cert specifiers
    // (so that KeyMgr::describe() can describe them)
}

pub use derive_deftly_template_KeySpecifier;

#[cfg(feature = "experimental-api")]
pub use derive_deftly_template_CertSpecifier;
