/* 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 https://mozilla.org/MPL/2.0/. */
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt::Display;

// Getting a metric instance and associated metadata, based on an ID, is
// non-trivial. This module provides a set of traits, functions, macros, and
// everything else that Rust provides, to implement this.
//
// In short, the implementation goes like this:

// Metric types implement a trait called `MetricMetadataGetterImpl`, via a
// macro, that describes how to look up a metric by its id from the various
// metric maps in `metrics.rs` and `factory.rs`. In some situations, such as
// labeled metrics which do not have metric ids but instead have submetric
// ids, looking up a metric isn't possible in this way: we need to have some
// way to get one metric, and then query it for another metric. This is where
// `BaseMetric` and `BaseMetricResult` come in. Types that implement
// `BaseMetric` provides a way to a) describe the type of the base metric
// (c.f. labeled submetrics), and b) look up a base metric instance, given a
// sub-metric instance. This can be necessary for (e.g.) child metrics that
// just store a label and metric ID.

// This all happens within the implementations in `get_base_metric_metadata_by_id` and
// `get_sub_metric_metadata_by_id`, defined within `define_metric_metadata_getter`. If this
// fails in some way, a `LookupError` will be returned.

// Once we have a metric instance, within this macro, we need to look up the
// associated metadata. This is done through the `MetricNamer` trait, that
// describes (for a given metric type), how to access the metadata. In
// parents, this is often through an `inner` glean instance, and in children,
// through a `meta` member. This is often pretty standard, so a macro
// `define_metric_namer` is provided to implement this for the majority of
// metrics. Metrics that don't split into just `Parent` and `Child` variants
// (like `BooleanMetric`) have to implement this themselves.

// `get_metadata` then returns a `MetricMetadata` struct instance,
// containing owned instances of the category/name/(optional) label. It would
// be nicer to do this with just referenced string types, but this becomes
// tricky to do while also satisfying the borrow checker. Label strings, for
// instance, can come from metrics, their metadata, or the labeled hashmap.
// Finding a way to keep all three sufficiently "in scope" is tricky, so we
// pay the cost of doing some more copies to keep the code a little simpler.

// Finally, the `MetricMetadataGetterImpl` definitions return this metadata,
// which can then be used to write the various category/name/label fields
// within a profiler marker.

// Note: This procedure does not work for `Object` and `Event` metric types.
// We rely on the fact that all other metric types have instances that are
// stored in `metrics.rs` (for static metrics) and `factory.rs` (for dynamic
// metrics), which we can use some form of metric ID to index when we stream
// a profiler marker. Object, and Event, metrics are not stored in maps in
// the same way, as they are indexed by a variety of types, and Rust does not
// support storing hetrogenous types in dynamic containers. Because of this,
// names and categories for `Object` and `Event` are extracted from the JS
// Metric lookup tables, using `lookup_canonical_metric_name`. These names
// are in the JS conjugation, as opposed to the YAML conjugation for other
// metric types.

/// Define a useful set of Error values that should make it eaiser to trace
/// lookup errors through the various bits of machinery.
#[derive(Debug)]
pub enum LookupError {
    FOGMetricMapWasUninit,
    FOGMetricMapLookupFailed,
    FOGSubmetricMapWasUninit,
    FOGSubmetricMapLockWasPoisoned,
    FOGSubmetricLookupFailed,
    JOGMetricMapWasUninit,
    JOGMetricMapLockWasPoisoned,
    JOGMetricMapLookupFailed,
    ReverseSubmetricLookupFailed,
    LookupUnlabledBySubId,
    SubmetricIdIsDynamic,
    LabeledBaseMetricIsNotDynamic,
    SubMetricLookupFailed,
    FOGMetricIdLookupFailed,
    NoBaseMetricForThisLabeledType,
}

impl Display for LookupError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            LookupError::FOGMetricMapWasUninit => write!(f, "FOGMetricMapWasUninit"),
            LookupError::FOGMetricMapLookupFailed => write!(f, "FOGMetricMapLookupFailed"),
            LookupError::FOGSubmetricMapWasUninit => write!(f, "FOGSubmetricMapWasUninit"),
            LookupError::FOGSubmetricMapLockWasPoisoned => {
                write!(f, "FOGSubmetricMapLockWasPoisoned")
            }
            LookupError::FOGSubmetricLookupFailed => write!(f, "FOGSubmetricLookupFailed"),
            LookupError::JOGMetricMapWasUninit => write!(f, "JOGMetricMapWasUninit"),
            LookupError::JOGMetricMapLockWasPoisoned => write!(f, "JOGMetricMapLockWasPoisoned"),
            LookupError::JOGMetricMapLookupFailed => write!(f, "JOGMetricMapLookupFailed"),
            LookupError::ReverseSubmetricLookupFailed => write!(f, "ReverseSubmetricLookupFailed"),
            LookupError::LookupUnlabledBySubId => write!(f, "LookupUnlabledBySubId"),
            LookupError::SubmetricIdIsDynamic => write!(f, "SubmetricIdIsDynamic"),
            LookupError::LabeledBaseMetricIsNotDynamic => {
                write!(f, "LabeledBaseMetricIsNotDynamic")
            }
            LookupError::SubMetricLookupFailed => write!(f, "SubMetricLookupFailed"),
            LookupError::FOGMetricIdLookupFailed => write!(f, "FOGMetricIdLookupFailed"),
            LookupError::NoBaseMetricForThisLabeledType => {
                write!(f, "NoBaseMetricForThisLabeledType")
            }
        }
    }
}

impl Error for LookupError {}

pub type LookupResult<T> = std::result::Result<T, LookupError>;

/// Define a structured way to refer to metric metadata, using owned types.
#[derive(Debug)]
pub struct MetricMetadata {
    pub category: String,
    pub name: String,
    pub label: Option<String>,
}

impl MetricMetadata {
    /// Construct a metric identifier from a triple of (category, name, option
    /// (label)) string references, and make copies of each.
    pub fn from_triple(t: (&str, &str, Option<&str>)) -> MetricMetadata {
        MetricMetadata {
            category: t.0.to_owned(),
            name: t.1.to_owned(),
            label: t.2.map(str::to_string),
        }
    }

    /// Construct a metric identifier that contains some error that surfaced
    /// while trying to retrieve the identifiers for this metric.
    pub fn from_error(error: LookupError) -> MetricMetadata {
        MetricMetadata {
            category: "".to_owned(),
            name: error.to_string(),
            label: None,
        }
    }

    /// Construct a metric identifier that contains some error string that
    /// surfaced while trying to retrieve the identifiers for this metric.
    pub fn from_error_str(error: &str) -> MetricMetadata {
        MetricMetadata {
            category: "".to_owned(),
            name: error.to_owned(),
            label: None,
        }
    }

    pub fn with_ref_label(self, other: Option<&str>) -> Self {
        MetricMetadata {
            category: self.category,
            name: self.name,
            label: self.label.or(other.map(str::to_string)),
        }
    }

    pub fn with_owned_label(self, other: Option<String>) -> Self {
        MetricMetadata {
            category: self.category,
            name: self.name,
            label: self.label.or(other),
        }
    }
}

/// Define, for a given metric, how we can extract the metadata from the
/// metric. This may be dependent on specific child metric implementations,
/// so each metric should implement this themselves. Note that the majority
/// of metrics can use the `define_metric_namer` macro given below to
/// implement this trait.
pub trait MetricNamer {
    fn get_metadata(&self) -> MetricMetadata;
}

/// Helper function for defining the metric namer for metric types, with two
/// distinct overrides for common patterns of metric types.
macro_rules! define_metric_namer {
    // Define a metric namer for metric types that have parent and child
    // metrics, where the child contains some metadata.
    ($metric_type:ident) => {
        impl crate::private::MetricNamer for $metric_type {
            fn get_metadata(&self) -> crate::private::MetricMetadata {
                use glean::MetricIdentifier;
                match self {
                    $metric_type::Parent { inner, .. } => {
                        crate::private::MetricMetadata::from_triple(inner.get_identifiers())
                    }
                    $metric_type::Child(meta) => {
                        crate::private::MetricMetadata::from_triple(meta.get_identifiers())
                    }
                }
            }
        }
    };

    // Define a metric namer for metric types that only have a parent metric,
    // with the child metric used only for IPC, and thus containing no
    // metadata.
    ($metric_type:ident, PARENT_ONLY) => {
        impl crate::private::MetricNamer for $metric_type {
            fn get_metadata(&self) -> crate::private::MetricMetadata {
                use glean::MetricIdentifier;
                match self {
                    $metric_type::Parent { inner, .. } => {
                        crate::private::MetricMetadata::from_triple(inner.get_identifiers())
                    }
                    _ => crate::private::MetricMetadata::from_error_str(concat!(
                        "Cannot get child identifiers for parent only type ",
                        stringify!($metric_type)
                    )),
                }
            }
        }
    };

    // Define a metric namer for labeled metric types. Defer to the underlying
    // base metric to get the actual identifiers/information.
    ($metric_type:ident, LABELED) => {
        impl crate::private::MetricNamer for $metric_type {
            fn get_metadata(&self) -> crate::private::MetricMetadata {
                match self.get_base_metric() {
                    BaseMetricResult::BaseMetric(metric) => metric.get_metadata(),
                    BaseMetricResult::BaseMetricWithLabel(metric, inner_label) => {
                        metric.get_metadata().with_ref_label(Some(inner_label))
                    }
                    BaseMetricResult::IndexLabelPair(id, child_label) => {
                        match <$metric_type as BaseMetric>::BaseMetricT::get_base_metric_metadata_by_id(id) {
                            Ok((metadata, inner_label)) => metadata
                                .with_owned_label(inner_label)
                                .with_ref_label(Some(child_label)),
                            Err(e) => crate::private::MetricMetadata::from_error(e),
                        }
                    }
                    BaseMetricResult::None => crate::private::MetricMetadata::from_error(
                        crate::private::metric_getter::LookupError::NoBaseMetricForThisLabeledType,
                    ),
                }
            }
        }
    };
}

/// Given a metric type and id, try to get the metadata associated with the
/// metric that this refers to, along with a label, if the metric and label
/// live separately. This is non-trivial to implement, so a macro is given
/// for implementing the `MetricMetadataGetterImpl` trait, which will
/// generate implementation for a given metric (and/or submetric) and the
/// right set of metric maps.
pub trait MetricMetadataGetter {
    fn get_metric_metadata_by_id<T: Into<MetricId>>(
        id: T,
    ) -> LookupResult<(MetricMetadata, Option<String>)>
    where
        Self: MetricNamer;
}

// Provide a blanket implementation for MetricMetadataGetter over types that
// implement MetricMetadataGetterImpl. This way, users won't be tempted to
// try and implement it themselves!
impl<MetricT> MetricMetadataGetter for MetricT
where
    MetricT: MetricMetadataGetterImpl + MetricNamer,
{
    fn get_metric_metadata_by_id<T: Into<MetricId>>(
        id: T,
    ) -> LookupResult<(MetricMetadata, Option<String>)> {
        match id.into() {
            MetricId::Id(baseid) => Self::get_base_metric_metadata_by_id(baseid),
            MetricId::SubId(subid) => Self::get_sub_metric_metadata_by_id(subid),
        }
    }
}

/// Implementation trait for MetricMetadataGetter. Metrics should define this.
/// Note, the macro `define_metric_metadata_getter` should be used to define the
/// implementations for this trait. It is non-trivial to implement.
pub trait MetricMetadataGetterImpl {
    fn get_base_metric_metadata_by_id(
        id: BaseMetricId,
    ) -> LookupResult<(MetricMetadata, Option<String>)>
    where
        Self: MetricNamer;
    fn get_sub_metric_metadata_by_id(
        id: SubMetricId,
    ) -> LookupResult<(MetricMetadata, Option<String>)>
    where
        Self: MetricNamer;
}

pub enum BaseMetricResult<'a, BaseMetricT> {
    BaseMetric(&'a BaseMetricT),
    BaseMetricWithLabel(&'a BaseMetricT, &'a str),
    IndexLabelPair(BaseMetricId, &'a str),
    None,
}

/// The BaseMetric trait gives us an interface for interacting with labeled
/// metrics, where we may have an instance of the underlying metric
/// (which knows its own label), or an id and label for a child metric.
pub trait BaseMetric {
    type BaseMetricT;
    fn get_base_metric<'a>(&'a self) -> BaseMetricResult<'a, Self::BaseMetricT>;
}

/// Given the type of a metric, the name of a FOG metric map for said metric,
/// an id, look up the metric from the map by id and return the metadata
/// (if found)
macro_rules! metadata_from_static_map {
    ($metric_type:ident, $metric_map:ident, $metric_id:ident) => {{
        let static_map =
            once_cell::sync::Lazy::get(&crate::metrics::__glean_metric_maps::$metric_map)
                .ok_or(crate::private::LookupError::FOGMetricMapWasUninit)?;
        let metric: &$metric_type = static_map
            .get(&$metric_id)
            .and_then(|thunk: &&once_cell::sync::Lazy<$metric_type>| {
                once_cell::sync::Lazy::get(*thunk)
            })
            .ok_or(crate::private::LookupError::FOGMetricMapLookupFailed)?;
        Ok((metric.get_metadata(), None))
    }};
}

/// Given the name of a JOG metric map, and an id, look up the metric metadata
/// from the map by id, and return it (if found)
macro_rules! metadata_from_dynamic_map {
    ($metric_map:ident, $metric_id:ident) => {{
        // Find the dynamic map (given as part of the macro), and try to read
        // from it. We don't need to force it, as if it hasn't been
        // initialized, we won't have a metric in there to read anyway!
        let dynamic_map =
            once_cell::sync::Lazy::get(&crate::factory::__jog_metric_maps::$metric_map)
                .ok_or(crate::private::LookupError::JOGMetricMapWasUninit)?
                .read()
                .or(Err(
                    crate::private::LookupError::JOGMetricMapLockWasPoisoned,
                ))?;
        let metric: &Self = dynamic_map
            .get(&$metric_id)
            .ok_or(crate::private::LookupError::JOGMetricMapLookupFailed)?;
        Ok((metric.get_metadata(), None))
    }};
}

/// Define a `fn get_base_metric_metadata_by_id` for a given metric type and map.
/// If you have a BaseMetricId, we always get the metadata the same way:
/// from the JOG maps if dynamice, from the compiled maps if static.
///
/// The only exception is for IPC where submetrics have the BaseMetricId of their parent.
/// In that case, don't use this macro.
///
/// (Though this fn could be written without macros, it is part of the trait
/// MetricMetadataGetterImpl which has parts that need macros.)
macro_rules! define_get_base_metric_metadata_by_id {
    ($metric_type:ident, $metric_map:ident) => {
        fn get_base_metric_metadata_by_id(
            id: crate::private::BaseMetricId,
        ) -> crate::private::LookupResult<(crate::private::MetricMetadata, Option<String>)> {
            use crate::private::metric_getter::MetricNamer;
            if id.is_dynamic() {
                metadata_from_dynamic_map!($metric_map, id)
            } else {
                metadata_from_static_map!($metric_type, $metric_map, id)
            }
        }
    };
}

/// Define how to look up the metadata for a given metric, given a metric id.
/// There are subtle differences for /where/ this should be called, depending
/// on the metric (and submetric) which we want to look up:
/// - Given a metric type `SomethingMetric`, which cannot be labeled, this
///   macro should be called in `something.rs`, with an invocation that looks
///   like `define_metric_metadata_getter!(SomethingMetric, SOMETHING_MAP)`
/// - Given a metric type `SomeotherMetric`, which *can* be labeled, but which
///   uses the same type for labeled instances (see `String` for an example),
///   the macro should be callled in `someother.rs`, with an invocation that
///   looks like `define_metric_metadata_getter!
///   (SomeotherMetric,SOMEOTHER_MAP,LABELED_SOMEOTHER_MAP)`
/// - Finally, given a metric type `AnotherMetric`, which can be labeled, and
///   has a different type (`LabeledAnotherMetric`) when labeled (see, for
///   example `MemoryDistribution` and `LabeledMemoryDistribution`), this
///   macro should be called in `labeled_another_metric.rs`, as this is the
///   only place where *both* metric types are in scope. The invocation
///   should look like `define_metric_metadata_getter!
///   (AnotherMetric, LabeledAnotherMetric, ANOTHER_MAP,
///   LABELED_ANOTHER_MAP)
macro_rules! define_metric_metadata_getter {
    // Metric getter for metrics that cannot be labeled
    ($metric_type:ident, $metric_map:ident) => {
        impl crate::private::MetricMetadataGetterImpl for $metric_type {
            define_get_base_metric_metadata_by_id!($metric_type, $metric_map);

            fn get_sub_metric_metadata_by_id(
                _id: crate::private::SubMetricId,
            ) -> crate::private::LookupResult<(crate::private::MetricMetadata, Option<String>)>
            {
                // Unlabeled metrics cannot look up submetrics
                Err(crate::private::LookupError::LookupUnlabledBySubId)
            }
        }
    };

    // Metric getter for metrics that can be labeled, but appear as the same
    // type (e.g. "StringMetric")
    ($metric_type:ident, $metric_map:ident, $_labeled_map:ident) => {
        impl crate::private::MetricMetadataGetterImpl for $metric_type {
            define_get_base_metric_metadata_by_id!($metric_type, $metric_map);

            fn get_sub_metric_metadata_by_id(
                id: crate::private::SubMetricId,
            ) -> crate::private::LookupResult<(crate::private::MetricMetadata, Option<String>)>
            {
                use crate::private::metric_getter::MetricNamer;
                // We should have a non-dynamic ID here, as dynamic
                // metrics always have base metric IDs, but we should
                // check anyway
                if id.is_dynamic() {
                    return Err(crate::private::LookupError::SubmetricIdIsDynamic);
                }
                // Re-use the $metric_map name to find the sub-metric map
                let submetric_map = once_cell::sync::Lazy::get(
                    &crate::metrics::__glean_metric_maps::submetric_maps::$metric_map,
                )
                .ok_or(crate::private::LookupError::FOGSubmetricMapWasUninit)?
                .read()
                .or(Err(
                    crate::private::LookupError::FOGSubmetricMapLockWasPoisoned,
                ))?;

                let metric = submetric_map
                    .get(&id)
                    .map(|arcm: &std::sync::Arc<$metric_type>| arcm.as_ref())
                    .ok_or(crate::private::LookupError::FOGSubmetricLookupFailed)?;
                Ok((metric.get_metadata(), None))
            }
        }
    };

    // Metric getter specifically for CounterMetric and its friends.
    // Doesn't strictly need to be a macro, but let's keep like with like, hm?
    (CounterMetric, $submetric_type: ident, $metric_map:ident, $labeled_map:ident) => {
        impl crate::private::MetricMetadataGetterImpl for CounterMetric {
            define_get_base_metric_metadata_by_id!(CounterMetric, $metric_map);

            fn get_sub_metric_metadata_by_id(
                id: crate::private::SubMetricId,
            ) -> crate::private::LookupResult<(crate::private::MetricMetadata, Option<String>)>
            {
                use crate::private::metric_getter::MetricNamer;
                use crate::private::DualLabeledCounterSubMetric;
                // We should have a non-dynamic ID here, as dynamic
                // metrics always have base metric IDs, but we should
                // check anyway
                if id.is_dynamic() {
                    return Err(crate::private::LookupError::SubmetricIdIsDynamic);
                }
                // Since this CounterMetric is a submetric, there are two possibilities:
                // Possibility 1: LabeledCounter
                get_submetric!(CounterMetric, $submetric_type, $metric_map, id).or_else(|_| {
                    // Possibility 2: DualLabeledCounter
                    get_submetric!(
                        CounterMetric,
                        DualLabeledCounterSubMetric,
                        DUAL_COUNTER_MAP,
                        id
                    )
                })
            }
        }

        define_submetric_metadata_getter!($submetric_type, $metric_map, $labeled_map);
    };

    // Metric getter for metrics that can be labeled, but appear as a
    // DIFFERENT type when they are labeled
    ($metric_type:ident, $submetric_type: ident, $metric_map:ident, $labeled_map:ident) => {
        // Define `MetricMetadataGetter` for the base type, with awareness of the
        // other type (i.e. Counter is aware of LabeledCounter).
        impl crate::private::MetricMetadataGetterImpl for $metric_type {
            define_get_base_metric_metadata_by_id!($metric_type, $metric_map);

            fn get_sub_metric_metadata_by_id(
                id: crate::private::SubMetricId,
            ) -> crate::private::LookupResult<(crate::private::MetricMetadata, Option<String>)>
            {
                use crate::private::metric_getter::MetricNamer;
                // We should have a non-dynamic ID here, as dynamic
                // metrics always have base metric IDs, but we should
                // check anyway
                if id.is_dynamic() {
                    return Err(crate::private::LookupError::SubmetricIdIsDynamic);
                }
                get_submetric!($metric_type, $submetric_type, $metric_map, id)
            }
        }

        define_submetric_metadata_getter!($submetric_type, $metric_map, $labeled_map);
    };
}

/// Logic for getting a submetric's metadata when that submetric impls BaseMetric.
macro_rules! get_submetric {
    ($metric_type:ident, $submetric_type: ident, $metric_map:ident, $id:ident) => {
        (|| {
            // Re-use the $metric_map name to find the sub-metric map
            let submetric_map = once_cell::sync::Lazy::get(
                &crate::metrics::__glean_metric_maps::submetric_maps::$metric_map,
            )
            .ok_or(crate::private::LookupError::FOGSubmetricMapWasUninit)?
            .read()
            .or(Err(
                crate::private::LookupError::FOGSubmetricMapLockWasPoisoned,
            ))?;

            let submetric: &$submetric_type = submetric_map
                .get(&$id)
                .map(|arcm: &std::sync::Arc<$submetric_type>| arcm.as_ref())
                .ok_or(crate::private::LookupError::FOGSubmetricLookupFailed)?;

            match submetric.get_base_metric() {
                BaseMetricResult::BaseMetric(metric) => Ok((metric.get_metadata(), None)),
                BaseMetricResult::BaseMetricWithLabel(metric, label) => {
                    Ok((metric.get_metadata(), Some(label.to_string())))
                }
                BaseMetricResult::IndexLabelPair(id, label) => {
                    match $metric_type::get_base_metric_metadata_by_id(id) {
                        Ok((metadata, _)) => Ok((metadata, Some(label.to_string()))),
                        e => e,
                    }
                }
                BaseMetricResult::None => {
                    Err(crate::private::LookupError::NoBaseMetricForThisLabeledType)
                }
            }
        })()
    };
}

/// Define a MetricMetadataGetterImpl for a submetric type (e.g. LabeledBooleanMetric).
macro_rules! define_submetric_metadata_getter {
    ($submetric_type: ident, $metric_map:ident, $labeled_map:ident) => {
        impl crate::private::MetricMetadataGetterImpl for $submetric_type {
            fn get_base_metric_metadata_by_id(
                id: crate::private::BaseMetricId,
            ) -> crate::private::LookupResult<(crate::private::MetricMetadata, Option<String>)>
            {
                use crate::private::metric_getter::MetricNamer;

                // A base metric id means that we have a labeled JOG metric,
                // so it must be dynamic. Report an error if condition is not
                // met.
                if !id.is_dynamic() {
                    return Err(crate::private::LookupError::LabeledBaseMetricIsNotDynamic);
                }

                // A dynamic ID means that we have a JOG metric
                // Look up the wrapped labeled metric from the dynamic maps.
                //
                // Warning: We acquire the read lock for the labeled map here, and the lock
                // remains held until `dynamic_map` is dropped at the end of this function.
                // Nothing called from this function is allowed grab a write lock on the dynamic
                // map, or there will be a deadlock!
                let dynamic_map =
                    once_cell::sync::Lazy::get(&crate::factory::__jog_metric_maps::$labeled_map)
                        .ok_or(crate::private::LookupError::JOGMetricMapWasUninit)?
                        .read()
                        .or(Err(
                            crate::private::LookupError::JOGMetricMapLockWasPoisoned,
                        ))?;
                let labeled = dynamic_map
                    .get(&id)
                    .ok_or(crate::private::LookupError::JOGMetricMapLookupFailed)?;

                let label_string = {
                    // We can't directly use labeled to get a metric instance, as
                    // we don't know the label. Instead, we use the base metric
                    // id to find the label using the static submetric map.
                    // Warning: We acquire the read lock for LABELED_METRICS_TO_IDS
                    // here, and the lock remains held until `map` is dropped at the end of
                    // this scope.
                    let map =
                        crate::metrics::__glean_metric_maps::submetric_maps::LABELED_METRICS_TO_IDS
                            .read()
                            .or(Err(
                                crate::private::LookupError::FOGSubmetricMapLockWasPoisoned,
                            ))?;

                    // Iterate over the keys of the hash table to find the ID, and extract the
                    // corresponding label.
                    map.keys()
                        .find_map(|(key_id, key_label)| {
                            if *key_id == id {
                                Some(key_label.clone())
                            } else {
                                None
                            }
                        })
                        .ok_or(crate::private::LookupError::ReverseSubmetricLookupFailed)?
                };

                let metadata = labeled.get(&label_string).get_metadata();
                Ok((metadata, Some(label_string)))
            }

            fn get_sub_metric_metadata_by_id(
                id: crate::private::SubMetricId,
            ) -> crate::private::LookupResult<(crate::private::MetricMetadata, Option<String>)>
            {
                use crate::private::metric_getter::MetricNamer;
                // We should have a non-dynamic ID here, as dynamic
                // metrics always have base metric IDs, but we should
                // check anyway
                if id.is_dynamic() {
                    return Err(crate::private::LookupError::SubmetricIdIsDynamic);
                }

                // Re-use the $metric_map name to find the sub-metric map
                let submetric_map = once_cell::sync::Lazy::get(
                    &crate::metrics::__glean_metric_maps::submetric_maps::$metric_map,
                )
                .ok_or(crate::private::LookupError::FOGSubmetricMapWasUninit)?
                .read()
                .or(Err(
                    crate::private::LookupError::FOGSubmetricMapLockWasPoisoned,
                ))?;

                let submetric = submetric_map
                    .get(&id)
                    .map(|arcm: &std::sync::Arc<$submetric_type>| arcm.as_ref())
                    .ok_or(crate::private::LookupError::FOGSubmetricLookupFailed)?;

                Ok((submetric.get_metadata(), None))
            }
        }
    };
}

/// Uniquely identify a metric so that we can look up names, labels (etc) and
/// perform IPC
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize, Serialize)]
pub enum MetricId {
    Id(BaseMetricId),
    SubId(SubMetricId),
}

impl MetricId {
    /// Extract the underlying metric_id, if there is one.
    pub fn base_metric_id(self) -> Option<BaseMetricId> {
        match self {
            MetricId::Id(base_metric_id) => Some(base_metric_id),
            _ => None,
        }
    }

    pub fn is_base_metric_id(&self) -> bool {
        matches!(self, MetricId::Id(_))
    }

    pub fn is_dynamic(self) -> bool {
        *self & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0
    }
}

#[cfg(feature = "with_gecko")]
impl MetricId {
    /// Given a metric getter, retrieve the name and (optionally) label of the
    /// underlying metric. Note, this currently returns the name of the
    /// metric in the so-called "JavaScript conjugation", while labels are
    /// returned in the so-called "YAML conjugation". Bug 1938145 captures
    /// the work for looking up the actual metric, after which Bug 1934880
    /// will allow us to get both in the yaml conjugation.
    pub fn get_identifiers(&self) -> (String, Option<String>) {
        match self {
            MetricId::Id(id) => (id.get_name(), None),
            MetricId::SubId(sid) => match sid.lookup_metric_id_and_label() {
                Some((id, label)) => (id.get_name(), Some(label)),
                None => (String::from("Could not find submetric in maps"), None),
            },
        }
    }
}

impl From<&MetricId> for MetricId {
    fn from(base_metric_id: &MetricId) -> MetricId {
        *base_metric_id
    }
}

impl From<BaseMetricId> for MetricId {
    fn from(base_metric_id: BaseMetricId) -> MetricId {
        MetricId::Id(base_metric_id)
    }
}

impl From<SubMetricId> for MetricId {
    fn from(submetric_id: SubMetricId) -> MetricId {
        MetricId::SubId(submetric_id)
    }
}

impl std::ops::Deref for MetricId {
    type Target = u32;

    fn deref(&self) -> &Self::Target {
        match self {
            MetricId::Id(BaseMetricId(m)) => m,
            MetricId::SubId(SubMetricId(m)) => m,
        }
    }
}

/// Uniquely identifies a single metric across all metric types.
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize, Serialize)]
#[repr(transparent)]
pub struct BaseMetricId(pub(crate) u32);

impl BaseMetricId {
    pub fn new(id: u32) -> Self {
        Self(id)
    }

    pub fn is_dynamic(self) -> bool {
        self.0 & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0
    }
}

impl malloc_size_of::MallocSizeOf for BaseMetricId {
    #[inline(always)]
    fn size_of(&self, _: &mut malloc_size_of::MallocSizeOfOps) -> usize {
        0
    }
}

#[cfg(feature = "with_gecko")]
impl BaseMetricId {
    // Wraps the result of `lookup_canonical_metric_name` so that it's
    // slightly easier for consumers to use. Also provides a slightly more
    // abstracted interface, so that in future we can use other ways to get
    // the name of a metric.
    pub(crate) fn get_name(&self) -> String {
        String::from(
            self.lookup_canonical_metric_name()
                .unwrap_or("id not found"),
        )
    }
    pub(crate) fn lookup_canonical_metric_name(&self) -> Option<&'static str> {
        #[allow(unused)]
        use std::ffi::{c_char, CStr};
        extern "C" {
            fn FOG_GetMetricIdentifier(id: u32) -> *const c_char;
        }
        // SAFETY: We check to make sure that the returned pointer is not null
        // before trying to construct a string from it. As the string array
        // that `FOG_GetMetricIdentifier` references is statically defined
        // and allocated, we know that any strings will be guaranteed to have
        // a null terminator, and will have the same lifetime as the program,
        // meaning we're safe to return a static lifetime, knowing that they
        // won't be changed "underneath" us. Additionally, we surface any
        // errors from parsing the string as utf8.
        unsafe {
            let raw_name_ptr = FOG_GetMetricIdentifier(**self);
            if raw_name_ptr.is_null() {
                None
            } else {
                match CStr::from_ptr(raw_name_ptr).to_str() {
                    Ok(s) => Some(s),
                    // This is a UTF8 parse error, that we can't handle.
                    Err(_) => None,
                }
            }
        }
    }
}

impl From<u32> for BaseMetricId {
    fn from(id: u32) -> Self {
        Self(id)
    }
}

impl std::ops::Deref for BaseMetricId {
    type Target = u32;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

/// Uniquely identifies a sub-metric across all metric types.
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone, Deserialize, Serialize)]
#[repr(transparent)]
pub struct SubMetricId(pub(crate) u32);

impl malloc_size_of::MallocSizeOf for SubMetricId {
    fn size_of(&self, _ops: &mut malloc_size_of::MallocSizeOfOps) -> usize {
        0
    }
}

impl SubMetricId {
    pub fn new(id: u32) -> Self {
        Self(id)
    }

    pub fn is_dynamic(self) -> bool {
        self.0 & (1 << crate::factory::DYNAMIC_METRIC_BIT) > 0
    }
}

#[cfg(feature = "with_gecko")]
impl SubMetricId {
    /// Given a submetric id, use the glean submetric maps to look up the
    /// underlying metric id, and label. Note that this essentially performs
    /// the reverse of `private::submetric_id_for`.
    pub(crate) fn lookup_metric_id_and_label(&self) -> Option<(BaseMetricId, String)> {
        let map = crate::metrics::__glean_metric_maps::submetric_maps::LABELED_METRICS_TO_IDS
            .read()
            .expect("read lock of submetric ids was poisoned");
        map.iter()
            .find(|(_, &value)| value == *self)
            .map(|(key, _)| key.clone())
    }
}

impl From<u32> for SubMetricId {
    fn from(id: u32) -> Self {
        Self(id)
    }
}

impl std::ops::Deref for SubMetricId {
    type Target = u32;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
