//! These types are the basis for writing functions that can emit a set of [`Warning`]s based on the value they are trying to create.
//!
//! The aim is for functions to be as resilient as possible while creating the value and emit commentary on their progress in the form of a growing set of [`Warning`]s.
//!
//! The caller of the function can use the set of [`Warning`]s to decide whether the operation was a success or failure and whether the value can be used or needs to be modified.
//!  
//! A concrete example is the conversion of a JSON [`json::Element`] into a `country::Code`. The [`json::Element`] may be the incorrect type and so the function issues a [`Warning`] and exits as it cannot continue with the given data. The signature of this fn is something like:
//!
//! ```rust ignore
//! // file: country.rs
//!  
//! pub enum Warning {
//!     InvalidType,
//!     ...
//! }
//!
//! pub enum Expect {
//!     Alpha2,
//!     Alpha3
//! }
//!
//! pub enum Code {
//!     fn from_json_element(json: json::Element, expect: Expect) -> Verdict<Code, Warning> {
//!         ...
//!     }
//! }
//! ```
//!
//! A [`Verdict`] is a [`Result`] where both the `Ok` and `Err` variants return a potential set of [`Warning`]s.
//! The `Ok` variant is `Caveat<T>`, where a [`Caveat`] contains a value but potentially contains cautionary details to be taken into account when using the value.
//! Hence, the name.
//!
//! The `Err` variant is `Warnings<W>`, a collection of [`Warning`]s. A [`Warning`] can be converted into an `Error` by the caller. A `Caveat<T>` is more completely described as `Caveat<T, W>` where the `Caveat` contains a value `T` and a set of `Warnings<W>`.
//!
//! All of this is to say that a resilient function can always return [`Warning`]s and the caller can gather them
//! together into a new set or fail.
//!
//! Returning to the example of the [`country::Code`](crate::country::Code), if the [`json::Element`] is the expected string type, then processing continues.
//! The string may contain control chars or escape chars and both these cases will emit a [`Warning`].
//! The string may be made up of three chars when two were expected.
//! This is the interesting case, as some [`country::Code`](crate::country::Code) fields are `alpha-3` where others are `alpha-2`.
//! Processing can still continue, as an `alpha-3` code can be converted to an `alpha-2` simply, while emitting a [`Warning`].
//!
//! The caller can decide whether this is acceptable or not.

#[cfg(test)]
pub(crate) mod test;

#[cfg(test)]
mod test_group_by_elem;

use std::{
    borrow::Cow,
    collections::{btree_map, BTreeMap, HashSet},
    fmt,
    ops::Deref,
    vec,
};

use tracing::{debug, info};

use crate::json;

#[doc(hidden)]
#[macro_export]
macro_rules! from_warning_all {
    ($($source_kind:path => $target_kind:ident::$target_variant:ident),+) => {
        $(
            /// Convert from `Warning` A to B.
            impl From<$source_kind> for $target_kind {
                fn from(warning: $source_kind) -> Self {
                    $target_kind::$target_variant(warning)
                }
            }

            /// Implement a conversion from `warning::Set<A>` to `warning::Set<B>` so that the `Err` variant
            /// of a `Verdict<_, A>` can be converted using the `?` operator to `Verdict<_, B>`.
            ///
            /// `warning::Set::into_other` is used to perform the conversion between set `A` and `B`.
            impl From<$crate::warning::ErrorSet<$source_kind>> for $crate::warning::ErrorSet<$target_kind> {
                fn from(set_a: $crate::warning::ErrorSet<$source_kind>) -> Self {
                    set_a.into_other()
                }
            }

            /// Implement a conversion from `warning::SetDeferred<A>` to `warning::SetDeferred<B>` so that the `Err` variant
            /// of a `VerdictDeferred<_, A>` can be converted using the `?` operator to `VerdictDeferred<_, B>`.
            ///
            /// `warning::SetDeferred::into_other` is used to perform the conversion between set `A` and `B`.
            impl From<$crate::warning::ErrorSetDeferred<$source_kind>> for $crate::warning::ErrorSetDeferred<$target_kind> {
                fn from(set_a: $crate::warning::ErrorSetDeferred<$source_kind>) -> Self {
                    set_a.into_other()
                }
            }
        )+
    };
}

#[derive(Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
pub struct Id(Cow<'static, str>);

impl Id {
    /// Create an `Id` from a `'static str`.
    pub(crate) const fn from_static(s: &'static str) -> Self {
        Self(Cow::Borrowed(s))
    }

    /// Create an `Id` from a `String`.
    pub(crate) const fn from_string(s: String) -> Self {
        Self(Cow::Owned(s))
    }

    /// Return the contained `str`.
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl fmt::Debug for Id {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Debug::fmt(&self.0, f)
    }
}

impl fmt::Display for Id {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(&self.0, f)
    }
}

/// A `Verdict` is a standard [`Result`] with [`Warning`]s potentially issued for both the `Ok` and `Err` variants.
pub type Verdict<T, W> = Result<Caveat<T, W>, ErrorSet<W>>;

/// A `VerdictDeferred` is a standard [`Result`] with [`Warning`]s potentially issued for both the `Ok` and `Err` variants.
///
/// This verdict is considered deferred as the warnings still need to be associated with a [`json::Element`].
///
/// NOTE: The deferred types are used to avoid passing [`json::Element`] references
/// to functions just to create [`Warning`]s.
pub(crate) type VerdictDeferred<T, W> = Result<CaveatDeferred<T, W>, ErrorSetDeferred<W>>;

/// A value that may have associated [`Warning`]s.
///
/// This caveat is considered deferred as the warning still need to be associated with
/// a [`json::Element`] to become [`Warning`]s.
///
/// Even though the value has been created there may be certain caveats you should be aware of before using it.
///
/// NOTE: The deferred types are used to avoid passing [`json::Element`] references
/// to functions just to create [`Warning`]s.
#[derive(Debug)]
pub struct CaveatDeferred<T, W: Warning> {
    /// The value created by the function.
    value: T,

    /// A list of [`Warning`]s or caveats issued when creating the value.
    warnings: SetDeferred<W>,
}

/// A deferred Caveat is simply a value with associated [`Warning`]s that still need to be associated
/// with a [`json::Element`].
///
/// Providing an `impl Deref` makes sense for given that it's an annotated value.
///
/// > The same advice applies to both `deref` traits. In general, `deref` traits
/// > **should** be implemented if:
/// >
/// > 1. a value of the type transparently behaves like a value of the target
/// >    type;
/// > 1. the implementation of the `deref` function is cheap; and
/// > 1. users of the type will not be surprised by any `deref` coercion behavior.
///
/// See: <https://doc.rust-lang.org/std/ops/trait.Deref.html#when-to-implement-deref-or-derefmut>.
impl<T, W> Deref for CaveatDeferred<T, W>
where
    W: Warning,
{
    type Target = T;

    fn deref(&self) -> &T {
        &self.value
    }
}

impl<T, W> CaveatDeferred<T, W>
where
    W: Warning,
{
    /// The only way to create `CaveatDeferred<T>` is if `T` impls `IntoCaveatDeferred`.
    pub(crate) fn new(value: T, warnings: SetDeferred<W>) -> Self {
        Self { value, warnings }
    }

    /// Return the value and any [`Warning`]s stored in the `CaveatDeferred`.
    pub fn into_parts(self) -> (T, SetDeferred<W>) {
        let Self { value, warnings } = self;
        (value, warnings)
    }

    /// Return the value and drop any warnings contained within.
    pub fn ignore_warnings(self) -> T {
        self.value
    }
}

/// A value that may have associated [`Warning`]s.
///
/// Even though the value has been created there may be certain caveats you should be aware of before using it.
#[derive(Debug)]
pub struct Caveat<T, W: Warning> {
    /// The value created by the function.
    value: T,

    /// A list of [`Warning`]s or caveats issued when creating the value.
    warnings: Set<W>,
}

/// A Caveat is simply a value with associated warnings.
/// Providing an `impl Deref` makes sense for given that it's an annotated value.
///
/// > The same advice applies to both `deref` traits. In general, `deref` traits
/// > **should** be implemented if:
/// >
/// > 1. a value of the type transparently behaves like a value of the target
/// >    type;
/// > 1. the implementation of the `deref` function is cheap; and
/// > 1. users of the type will not be surprised by any `deref` coercion behavior.
///
/// See: <https://doc.rust-lang.org/std/ops/trait.Deref.html#when-to-implement-deref-or-derefmut>.
impl<T, W> Deref for Caveat<T, W>
where
    W: Warning,
{
    type Target = T;

    fn deref(&self) -> &T {
        &self.value
    }
}

impl<T, W> Caveat<T, W>
where
    W: Warning,
{
    /// The only way to create `Caveat<T>` is if `T` impls `IntoCaveat`.
    pub(crate) fn new(value: T, warnings: Set<W>) -> Self {
        Self { value, warnings }
    }

    /// Return a ref to the warning Set.
    pub fn warnings(&self) -> &Set<W> {
        &self.warnings
    }

    /// Return the value and any [`Warning`]s stored in the `Caveat`.
    pub fn into_parts(self) -> (T, Set<W>) {
        let Self { value, warnings } = self;
        (value, warnings)
    }

    /// Return the value and drop any warnings contained within.
    pub fn ignore_warnings(self) -> T {
        self.value
    }

    /// Map the value to another target type while retaining the warnings about the source type.
    pub fn map<U, F: FnOnce(T) -> U>(self, op: F) -> Caveat<U, W> {
        let Self { value, warnings } = self;
        Caveat {
            value: op(value),
            warnings,
        }
    }
}

/// Convert a `Caveat`-like type into a `T` by gathering up its [`Warning`]s.
///
/// Gathering warnings into a parent `warning::Set` move's the responsibility of alerting the
/// caller to the existence of those warnings to the owner of the set.
pub(crate) trait GatherWarnings<T, W>
where
    W: Warning,
{
    /// The output type of after all the warnings have been gathered.
    type Output;

    /// Convert a `Caveat`-like type into a `T` by gathering up its [`Warning`]s.
    #[must_use = "If you want to ignore the value use `let _ =`"]
    fn gather_warnings_into<WA>(self, warnings: &mut Set<WA>) -> Self::Output
    where
        W: Into<WA>,
        WA: Warning;
}

/// Convert a `Caveat<T>` into `T` by gathering up its `Warning`s.
impl<T, W> GatherWarnings<T, W> for Caveat<T, W>
where
    W: Warning,
{
    type Output = T;

    /// Convert a `Caveat<T>` into `T` by gathering up its `Warning`s.
    fn gather_warnings_into<WA>(self, warnings: &mut Set<WA>) -> Self::Output
    where
        W: Into<WA>,
        WA: Warning,
    {
        let Self {
            value,
            warnings: inner_warnings,
        } = self;

        let Set(inner_warnings) = inner_warnings;
        let inner_warnings = inner_warnings
            .into_iter()
            .map(|(elem_id, group)| (elem_id, group.into_other()));

        warnings.extend(inner_warnings);

        value
    }
}

/// Convert a `Option<Caveat<T>>` into `Option<T>` by gathering up its `Warning`s.
impl<T, W> GatherWarnings<T, W> for Option<Caveat<T, W>>
where
    W: Warning,
{
    type Output = Option<T>;

    /// Convert a `Caveat` related to type `T` into a `T` by gathering its [`Warning`]s.
    fn gather_warnings_into<WA>(self, warnings: &mut Set<WA>) -> Self::Output
    where
        W: Into<WA>,
        WA: Warning,
    {
        match self {
            Some(cv) => Some(cv.gather_warnings_into(warnings)),
            None => None,
        }
    }
}

/// Convert a `Result<Caveat<T>>` into `Result<T>` by gathering up its `Warning`s.
impl<T, W, E> GatherWarnings<T, W> for Result<Caveat<T, W>, E>
where
    W: Warning,
    E: std::error::Error,
{
    type Output = Result<T, E>;

    /// Convert a `Caveat` related to type `T` into a `T` by gathering its [`Warning`]s.
    fn gather_warnings_into<WA>(self, warnings: &mut Set<WA>) -> Self::Output
    where
        W: Into<WA>,
        WA: Warning,
    {
        match self {
            Ok(cv) => Ok(cv.gather_warnings_into(warnings)),
            Err(err) => Err(err),
        }
    }
}

/// Convert a `Result<Caveat<T>>` into `Result<T>` by gathering up its `Warning`s.
impl<T, W> GatherWarnings<T, W> for Verdict<T, W>
where
    W: Warning,
{
    type Output = Result<T, ErrorSet<W>>;

    /// Convert a `Verdict` into an `Option` by collecting `Warnings` from the `Ok` and `Err` variants
    /// and mapping `Ok` to `Some` and `Err` to `None`.
    fn gather_warnings_into<WA>(self, warnings: &mut Set<WA>) -> Self::Output
    where
        W: Into<WA>,
        WA: Warning,
    {
        match self {
            Ok(cv) => Ok(cv.gather_warnings_into(warnings)),
            Err(err_set) => Err(err_set),
        }
    }
}

/// Convert a `Result` that contains an `ErrorSet` into a `T` by gathering up its [`Warning`]s.
///
/// Gathering warnings into a parent `warning::Set` move's the responsibility of alerting the
/// caller to the existence of those warnings to the owner of the set.
pub(crate) trait DeescalateError<T, W>
where
    W: Warning,
{
    /// Convert a `Caveat`-like type into a `T` by gathering up its [`Warning`]s.
    #[must_use = "If you want to ignore the value use `let _ =`"]
    fn deescalate_error_into<WA>(self, warnings: &mut Set<WA>) -> Option<T>
    where
        W: Into<WA>,
        WA: Warning;
}

/// Convert a `Result<Caveat<T>>` into `Option<T>` by deescalating its [`Error`] and gathering up its [`Warning`]s.
impl<T, W> DeescalateError<T, W> for Verdict<T, W>
where
    W: Warning,
{
    /// Convert a `Verdict` into an `Option` by collecting `Warnings` from the `Ok` and `Err` variants
    /// and mapping `Ok` to `Some` and `Err` to `None`.
    fn deescalate_error_into<WA>(self, warnings: &mut Set<WA>) -> Option<T>
    where
        W: Into<WA>,
        WA: Warning,
    {
        match self {
            Ok(cv) => Some(cv.gather_warnings_into(warnings)),
            Err(err_set) => {
                warnings.deescalate_error(err_set.into_other());
                None
            }
        }
    }
}

/// Convert a `Result<T>` into `Option<T>` by deescalating its [`Error`] and gathering up its [`Warning`]s.
impl<T, W> DeescalateError<T, W> for Result<T, ErrorSet<W>>
where
    W: Warning,
{
    /// Convert a `Verdict` into an `Option` by collecting `Warnings` from the `Ok` and `Err` variants
    /// and mapping `Ok` to `Some` and `Err` to `None`.
    fn deescalate_error_into<WA>(self, warnings: &mut Set<WA>) -> Option<T>
    where
        W: Into<WA>,
        WA: Warning,
    {
        match self {
            Ok(cv) => Some(cv),
            Err(err_set) => {
                warnings.deescalate_error(err_set.into_other());
                None
            }
        }
    }
}

/// Convert a `Vec<Caveat<T>>` into `Vec<T>` by gathering up each elements `Warning`s.
impl<T, W> GatherWarnings<T, W> for Vec<Caveat<T, W>>
where
    W: Warning,
{
    type Output = Vec<T>;

    /// Convert a `Caveat` related to type `T` into a `T` by gathering its [`Warning`]s.
    fn gather_warnings_into<WA>(self, warnings: &mut Set<WA>) -> Self::Output
    where
        W: Into<WA>,
        WA: Warning,
    {
        self.into_iter()
            .map(|cv| cv.gather_warnings_into(warnings))
            .collect()
    }
}

/// Convert a `Caveat`-like type into a `T` by gathering up its [`Warning`]s.
///
/// Gathering [`Warning`]s into a parent `warning::SetDeferred` move's the responsibility of alerting the
/// caller to the existence of those [`Warning`]s to the owner of the set.
pub(crate) trait GatherDeferredWarnings<T, W>
where
    W: Warning,
{
    /// The output type of after all the warnings have been gathered.
    type Output;

    /// Convert a `Caveat`-like type into a `T` by gathering up its [`Warning`]s.
    #[must_use = "If you want to ignore the value use `let _ =`"]
    fn gather_deferred_warnings_into<WA>(self, warnings: &mut SetDeferred<WA>) -> Self::Output
    where
        W: Into<WA>,
        WA: Warning;
}

/// Convert a `CaveatDeferred<T>` into `T` by gathering up its [`Warning`]s.
impl<T, W> GatherDeferredWarnings<T, W> for CaveatDeferred<T, W>
where
    W: Warning,
{
    type Output = T;

    /// Convert a `Caveat<T>` into `T` by gathering up its `Warning`s.
    fn gather_deferred_warnings_into<WA>(self, warnings: &mut SetDeferred<WA>) -> Self::Output
    where
        W: Into<WA>,
        WA: Warning,
    {
        let Self {
            value,
            warnings: inner_warnings,
        } = self;

        warnings.extend(inner_warnings);

        value
    }
}

/// Convert a `Option<CaveatDeferred<T>>` into `Option<T>` by gathering up its warning `Warning`s.
impl<T, W> GatherDeferredWarnings<T, W> for Option<CaveatDeferred<T, W>>
where
    W: Warning,
{
    type Output = Option<T>;

    /// Convert a `Caveat` related to type `T` into a `T` by gathering its [`Warning`]s.
    fn gather_deferred_warnings_into<WA>(self, warnings: &mut SetDeferred<WA>) -> Self::Output
    where
        W: Into<WA>,
        WA: Warning,
    {
        match self {
            Some(cv) => Some(cv.gather_deferred_warnings_into(warnings)),
            None => None,
        }
    }
}

/// Convert a `Result<CaveatDeferred<T>>` into `Result<T>` by gathering up its [`Warning`]s.
impl<T, W, E> GatherDeferredWarnings<T, W> for Result<CaveatDeferred<T, W>, E>
where
    W: Warning,
    E: std::error::Error,
{
    type Output = Result<T, E>;

    /// Convert a `Caveat` related to type `T` into a `T` by gathering its [`Warning`]s.
    fn gather_deferred_warnings_into<WA>(self, warnings: &mut SetDeferred<WA>) -> Self::Output
    where
        W: Into<WA>,
        WA: Warning,
    {
        match self {
            Ok(cv) => Ok(cv.gather_deferred_warnings_into(warnings)),
            Err(err) => Err(err),
        }
    }
}

/// Convert a `Result<CaveatDeferred<T>>` into `Result<T>` by gathering up its [`Warning`]s.
impl<T, W> GatherDeferredWarnings<T, W> for VerdictDeferred<T, W>
where
    W: Warning,
{
    type Output = Result<T, ErrorSetDeferred<W>>;

    /// Convert a `VerdictDeferred` into an `Option` by collecting [`Warning`]s from the `Ok` and `Err` variants
    /// and mapping `Ok` to `Some` and `Err` to `None`.
    fn gather_deferred_warnings_into<WA>(self, warnings: &mut SetDeferred<WA>) -> Self::Output
    where
        W: Into<WA>,
        WA: Warning,
    {
        match self {
            Ok(cv) => Ok(cv.gather_deferred_warnings_into(warnings)),
            Err(err_set) => Err(err_set),
        }
    }
}

/// Convert a `Vec<CaveatDeferred<T>>` into `Vec<T>` by gathering up each elements [`Warning`]s.
impl<T, W> GatherDeferredWarnings<T, W> for Vec<CaveatDeferred<T, W>>
where
    W: Warning,
{
    type Output = Vec<T>;

    /// Convert a `CaveatDeferred` related to type `T` into a `T` by gathering its [`Warning`]s.
    fn gather_deferred_warnings_into<WA>(self, warnings: &mut SetDeferred<WA>) -> Self::Output
    where
        W: Into<WA>,
        WA: Warning,
    {
        self.into_iter()
            .map(|cv| cv.gather_deferred_warnings_into(warnings))
            .collect()
    }
}

/// Converts a value `T` into a `Caveat`.
///
/// Each module can use this to whitelist their types for conversion to `Caveat<T>`.
pub trait IntoCaveat: Sized {
    /// Any type can be converted to `Caveat<T>` by supplying a list of [`Warning`]s.
    fn into_caveat<W: Warning>(self, warnings: Set<W>) -> Caveat<Self, W>;
}

/// Converts a value `T` into a `CaveatDeferred`.
///
/// Each module can use this to whitelist their types for conversion to `CaveatDeferred<T>`.
pub(crate) trait IntoCaveatDeferred: Sized {
    /// Any type can be converted to `CaveatDeferred<T>` by supplying a list of [`Warning`]s.
    fn into_caveat_deferred<W: Warning>(self, warnings: SetDeferred<W>) -> CaveatDeferred<Self, W>;
}

/// Allow all types to be converted into `Caveat<T>`.
impl<T> IntoCaveat for T {
    fn into_caveat<W: Warning>(self, warnings: Set<W>) -> Caveat<Self, W> {
        Caveat::new(self, warnings)
    }
}

/// Allow `Vec<T: IntoCaveat>` to be converted into a `CaveatDeferred`.
impl<T> IntoCaveatDeferred for T {
    fn into_caveat_deferred<W: Warning>(self, warnings: SetDeferred<W>) -> CaveatDeferred<Self, W> {
        CaveatDeferred::new(self, warnings)
    }
}

/// `Verdict` specific extension methods for the `Result` type.
pub trait VerdictExt<T, W: Warning> {
    /// Maps a `Verdict<T, E>` to `Verdict<U, E>` by applying a function to a
    /// contained [`Ok`] value, leaving an [`Err`] value untouched.
    fn map_caveat<F, U>(self, op: F) -> Verdict<U, W>
    where
        F: FnOnce(T) -> U;

    /// Discard all warnings in the `Err` variant and keep only the warning that caused the error.
    fn only_error(self) -> Result<Caveat<T, W>, Error<W>>;
}

/// Used to log the contents of various `Verdict` impls.
#[expect(dead_code, reason = "for debugging")]
pub(crate) trait VerdictTrace<T, W: Warning> {
    /// Log the contents as `info` level.
    fn info_verdict(self, msg: &'static str) -> Self;

    /// Log the contents as `debug` level.
    fn debug_verdict(self, msg: &'static str) -> Self;
}

/// Used to log the contents of various `Result` impls.
#[expect(dead_code, reason = "for debugging")]
pub(crate) trait ResultTrace<T, W: Warning> {
    /// Log the contents as `info` level.
    fn info_result(self, msg: &'static str) -> Self;

    /// Log the contents as `debug` level.
    fn debug_result(self, msg: &'static str) -> Self;
}

impl<T, W: Warning> VerdictExt<T, W> for Verdict<T, W> {
    fn map_caveat<F, U>(self, op: F) -> Verdict<U, W>
    where
        F: FnOnce(T) -> U,
    {
        match self {
            Ok(c) => Ok(c.map(op)),
            Err(w) => Err(w),
        }
    }

    fn only_error(self) -> Result<Caveat<T, W>, Error<W>> {
        match self {
            Ok(c) => Ok(c),
            Err(err_set) => {
                let ErrorSet { error, warnings: _ } = err_set;
                Err(*error)
            }
        }
    }
}

impl<T, W: Warning> VerdictTrace<T, W> for Verdict<T, W>
where
    T: fmt::Debug,
{
    fn info_verdict(self, msg: &'static str) -> Self {
        match self {
            Ok(c) => {
                info!("{msg}: {c:#?}");
                Ok(c)
            }
            Err(err_set) => {
                info!("{msg}: {err_set:#?}");
                Err(err_set)
            }
        }
    }

    fn debug_verdict(self, msg: &'static str) -> Self {
        match self {
            Ok(c) => {
                debug!("{msg}: {c:#?}");
                Ok(c)
            }
            Err(err_set) => {
                debug!("{msg}: {err_set:#?}");
                Err(err_set)
            }
        }
    }
}

impl<T, W: Warning> ResultTrace<T, W> for Result<T, ErrorSet<W>>
where
    T: fmt::Debug,
{
    fn info_result(self, msg: &'static str) -> Self {
        match self {
            Ok(c) => {
                info!("{msg}: {c:#?}");
                Ok(c)
            }
            Err(err_set) => {
                info!("{msg}: {err_set:#?}");
                Err(err_set)
            }
        }
    }

    fn debug_result(self, msg: &'static str) -> Self {
        match self {
            Ok(c) => {
                debug!("{msg}: {c:#?}");
                Ok(c)
            }
            Err(err_set) => {
                debug!("{msg}: {err_set:#?}");
                Err(err_set)
            }
        }
    }
}

/// The warning that caused an operation to fail.
///
/// The [`Warning`] is referred to by the [`json::Element`]s path as a `String`.
#[derive(Debug)]
pub struct Error<W: Warning> {
    /// The `Warning` of warning.
    warning: W,

    /// The path of the element that caused the [`Warning`].
    element: Element,
}

impl<W: Warning> Error<W> {
    /// Return reference to the `Warning`.
    pub fn warning(&self) -> &W {
        &self.warning
    }

    /// Consume the `Error` and return the `Warning`.
    pub fn into_warning(self) -> W {
        self.warning
    }

    /// Return a reference to the [`Element`] that caused the [`Warning`].
    pub fn element(&self) -> &Element {
        &self.element
    }

    /// Return the constituent parts.
    pub fn parts(&self) -> (&W, &Element) {
        (&self.warning, &self.element)
    }

    /// Consume the `Cause` and return the constituent parts.
    pub fn into_parts(self) -> (W, Element) {
        let Self { warning, element } = self;
        (warning, element)
    }

    /// Converts `Error<W>` into `Error<WA>` using the `impl Into<WA> for W`.
    ///
    /// This is used by the [`from_warning_all`] macro.
    fn into_other<WA>(self) -> Error<WA>
    where
        W: Into<WA>,
        WA: Warning,
    {
        let Self { warning, element } = self;
        Error {
            warning: warning.into(),
            element,
        }
    }
}

impl<W: Warning> std::error::Error for Error<W> {}

impl<W: Warning> fmt::Display for Error<W> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "A warning for element at `{}` was upgraded to an `error`: {}",
            self.element.path, self.warning
        )
    }
}

/// Associate a [`json::Element`] with a set of [`Warning`]s contained by a [`VerdictDeferred`].
pub trait WithElement<T, W: Warning> {
    type Output;

    fn with_element(self, element: &json::Element<'_>) -> Self::Output;
}

impl<T, W: Warning> WithElement<T, W> for CaveatDeferred<T, W> {
    type Output = Caveat<T, W>;

    /// Associate a [`json::Element`] with a set of [`Warning`]s.
    fn with_element(self, element: &json::Element<'_>) -> Self::Output {
        let CaveatDeferred { value, warnings } = self;
        let SetDeferred(warnings) = warnings;
        let warnings = if warnings.is_empty() {
            BTreeMap::new()
        } else {
            let warnings = Group {
                element: Element::from_json(element),
                warnings,
            };
            BTreeMap::from([(element.id(), warnings)])
        };

        Caveat {
            value,
            warnings: Set(warnings),
        }
    }
}

impl<T, W: Warning> WithElement<T, W> for VerdictDeferred<T, W> {
    type Output = Verdict<T, W>;

    /// Associate a [`json::Element`] with a set of [`Warning`]s.
    fn with_element(self, element: &json::Element<'_>) -> Self::Output {
        match self {
            Ok(v) => Ok(v.with_element(element)),
            Err(set) => {
                let ErrorSetDeferred { error, warnings } = set;
                // An `ErrorSetDeferred` should have at least one warning in it.
                let warnings = Group {
                    element: Element::from_json(element),
                    warnings,
                };
                let warnings = BTreeMap::from([(element.id(), warnings)]);
                Err(ErrorSet {
                    error: Box::new(Error {
                        warning: error,
                        element: Element::from_json(element),
                    }),
                    warnings,
                })
            }
        }
    }
}

/// A representation of a JSON element that satisfies the needs of most consumers of a [`Warning`].
///
/// This representation avoids the complexity of needing to provide a `'buf` lifetime to the [`json::Element`].
/// This would complicate all warnings types with that lifetime.
///
/// A consumer of warnings wants to group them by [`json::ElemId`] using `Warning::group_by_elem` and then
/// display or report the warnings by path.
///
/// The linter report also wants to highlight the source JSON that a warning refers too.
#[derive(Debug)]
pub struct Element {
    /// The Id of the element that caused the [`Warning`].
    ///
    /// This is used for sorting warnings and can be used to retrieve the [`json::Element`] object.
    pub id: json::ElemId,

    /// The `Span` that delimits the [`json::Element`].
    pub span: json::Span,

    /// The elements path.
    ///
    /// Most consumers of warnings just want this data.
    pub path: json::Path,

    /// The location (line, column) of the beginning of this element in the JSON file.
    pub location: json::Location,
}
impl Element {
    /// Create an owned `Element` from a `json::Element<'buf'>`.
    pub(crate) fn from_json(element: &json::Element<'_>) -> Element {
        Self {
            id: element.id(),
            span: element.span(),
            path: element.path(),
            location: element.location(),
        }
    }
}

/// A Display object for writing a set of warnings.
///
/// The warnings set is formatted as a tree with element paths on the first level
/// and a list of warning ids on the second.
///
/// ```shell
/// $.path.to.json[0].field:
///   - list_of_warning_ids
///   - next_warning_id
///
/// $.next.path.to[1].json.field
///   - list_of_warning_ids
/// ```
pub struct SetWriter<'caller, W: Warning> {
    /// The list of warnings for the [`json::Element`].
    warnings: &'caller Set<W>,

    /// The indent to prefix to each warning id.
    indent: &'caller str,
}

impl<'caller, W: Warning> SetWriter<'caller, W> {
    /// Create a new `SetWriter` with a default warning id indent of `"  - "`.
    pub fn new(warnings: &'caller Set<W>) -> Self {
        Self {
            warnings,
            indent: "  - ",
        }
    }

    /// Create a new `SetWriter` with a custom warning id indent.
    pub fn with_indent(warnings: &'caller Set<W>, indent: &'caller str) -> Self {
        Self { warnings, indent }
    }
}

impl<W: Warning> fmt::Debug for SetWriter<'_, W> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(self, f)
    }
}

impl<W: Warning> fmt::Display for SetWriter<'_, W> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut iter = self.warnings.iter();

        {
            // Write the first group without an empty line prefix.
            let Some((element, warnings)) = iter.next().map(|g| g.to_parts()) else {
                return Ok(());
            };

            writeln!(f, "{}", element.path)?;

            for warning in warnings {
                write!(f, "{}{}", self.indent, warning)?;
            }
        }

        // Write the rest of the Groups with am empty line padding.
        for (element, warnings) in iter.map(|g| g.to_parts()) {
            writeln!(f, "\n{}", element.path)?;

            for warning in warnings {
                write!(f, "{}{}", self.indent, warning)?;
            }
        }

        Ok(())
    }
}

/// Each mod defines warnings for the type that it's trying to parse or lint from a [`json::Element`].
///
/// The `Warning` in the mod should impl this trait to take part in the [`Warning`] system.
pub trait Warning: Sized + fmt::Debug + fmt::Display + Send + Sync {
    /// Return the human readable identifier for the [`Warning`].
    ///
    /// This is used in the `auto_test` assertion system.
    /// Changing these strings may require updating `output_price__cdr.json` files.
    fn id(&self) -> Id;
}

/// A transparent container that stores the source line where the `Warning` occurred in a test build.
#[derive(Debug)]
struct Source<W: Warning> {
    #[cfg(test)]
    /// The line in the source code where this `Warning` occurred.
    location: &'static std::panic::Location<'static>,

    /// The warning.
    warning: W,
}

impl<W: Warning> Source<W> {
    #[track_caller]
    /// Create a new `Source` object.
    fn new(warning: W) -> Self {
        #[cfg(test)]
        {
            Self {
                location: std::panic::Location::caller(),
                warning,
            }
        }

        #[expect(
            clippy::cfg_not_test,
            reason = "This is code that is designed for use in tests"
        )]
        #[cfg(not(test))]
        {
            Self { warning }
        }
    }

    /// Discard the debug info and return the inner `Warning`.
    fn into_warning(self) -> W {
        self.warning
    }

    /// Convert the inner `Warning` into another type of `Warning`.
    fn into_other<WA>(self) -> Source<WA>
    where
        W: Into<WA>,
        WA: Warning,
    {
        self.map(Into::into)
    }

    /// Convert the inner `Warning` into another type of `Warning`.
    fn map<F, WA>(self, mut f: F) -> Source<WA>
    where
        F: FnMut(W) -> WA,
        WA: Warning,
    {
        #[cfg(test)]
        {
            let Self {
                location: source,
                warning,
            } = self;
            Source {
                location: source,
                warning: f(warning),
            }
        }

        #[expect(
            clippy::cfg_not_test,
            reason = "This is code that is designed for use in tests"
        )]
        #[cfg(not(test))]
        {
            let Self { warning } = self;
            Source {
                warning: f(warning),
            }
        }
    }
}

impl<W: Warning> Deref for Source<W> {
    type Target = W;

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

/// A set of [`Warning`]s transported through the system using a `VerdictDeferred` or `CaveatDeferred`.
///
///
/// This set is considered deferred as the [`Warning`]s need to be associated with a [`json::Element`]
/// to become [`Warning`]s.
///
/// NOTE: The deferred types are used to avoid passing [`json::Element`] references
/// to functions just to create [`Warning`]s.
#[derive(Debug)]
pub struct SetDeferred<W: Warning>(Vec<Source<W>>);

impl<W: Warning> SetDeferred<W> {
    /// Create a new set of [`Warning`]s.
    pub(crate) fn new() -> Self {
        Self(Vec::new())
    }

    /// Create and add a [`Warning`] to the set while consuming the set into a [`VerdictDeferred`].
    ///
    /// This is designed for use as the last [`Warning`] of a function. The function should exit with the `Err` returned.
    #[track_caller]
    pub(crate) fn bail<T>(self, warning: W) -> VerdictDeferred<T, W> {
        let Self(warnings) = self;
        Err(ErrorSetDeferred {
            error: warning,
            warnings,
        })
    }

    /// Add a single warning to the set.
    #[track_caller]
    pub(crate) fn insert(&mut self, warning: W) {
        self.0.push(Source::new(warning));
    }

    /// Extend this set with the warnings of another.
    ///
    /// The other set's warnings will be converted if necessary.
    fn extend<WA>(&mut self, warnings: SetDeferred<WA>)
    where
        WA: Into<W> + Warning,
    {
        let SetDeferred(warnings) = warnings;
        self.0.extend(warnings.into_iter().map(Source::into_other));
    }
}

/// A set of [`Warning`]s and a [`Warning`] that caused an operation to fail to be represented as an [`Error`].
///
/// This set is transported through the system using a [`VerdictDeferred`]s `Err` variant.
///
/// This set is considered deferred as the [`Warning`]s need to be associated with a [`json::Element`]
/// to become [`Warning`]s.
///
/// NOTE: The deferred types are used to avoid passing [`json::Element`] references
/// to functions just to create [`Warning`]s.
#[derive(Debug)]
pub struct ErrorSetDeferred<W: Warning> {
    /// The `Warning` that caused a function to halt.
    error: W,

    /// The `Warning`s collected up to the halting point.
    warnings: Vec<Source<W>>,
}

impl<W: Warning> ErrorSetDeferred<W> {
    /// Create a new set of [`Warning`]s.
    pub(crate) fn with_warn(warning: W) -> Self {
        Self {
            warnings: Vec::new(),
            error: warning,
        }
    }

    /// Converts `ErrorSetDeferred<W>` into `ErrorSetDeferred<WA>` using the `impl Into<WA> for W`.
    ///
    /// This is used by the [`from_warning_all`] macro.
    pub(crate) fn into_other<WA>(self) -> ErrorSetDeferred<WA>
    where
        W: Into<WA>,
        WA: Warning,
    {
        let Self { error, warnings } = self;
        let warnings = warnings.into_iter().map(Source::into_other).collect();
        ErrorSetDeferred {
            error: error.into(),
            warnings,
        }
    }
}

/// A set of [`Warning`]s transported through the system using a [`Caveat`].
#[derive(Debug)]
pub struct Set<W: Warning>(BTreeMap<json::ElemId, Group<W>>);

impl<W: Warning> Set<W> {
    /// Create a new set of [`Warning`]s.
    pub(crate) fn new() -> Self {
        Self(BTreeMap::new())
    }

    /// Unpack the inner map.
    pub(crate) fn into_inner(self) -> BTreeMap<json::ElemId, Group<W>> {
        self.0
    }

    /// Insert a [`Warning`] defined in a domain module and it's associated [`json::Element`].
    #[track_caller]
    pub(crate) fn insert(&mut self, element: &json::Element<'_>, warning: W) {
        self.insert_warning(element.id(), warning, || Element::from_json(element));
    }

    /// Insert [`Warning`] defined in a domain module and it's associated [`Element`].
    ///
    /// Note: The [`Element`] is created lazily.
    #[track_caller]
    fn insert_warning<F>(&mut self, elem_id: json::ElemId, warning: W, f: F)
    where
        F: FnOnce() -> Element,
    {
        use std::collections::btree_map::Entry;

        match self.0.entry(elem_id) {
            Entry::Vacant(entry) => {
                let element = f();
                entry.insert_entry(Group {
                    element,
                    warnings: vec![Source::new(warning)],
                });
            }
            Entry::Occupied(mut entry) => {
                entry.get_mut().warnings.push(Source::new(warning));
            }
        }
    }

    /// Consume the set and insert a [`Warning`] while returning a [`Verdict`].
    ///
    /// This is designed for use as the last [`Warning`] of a function. The function should exit with the `Err` returned.
    #[track_caller]
    pub(crate) fn bail<T>(self, element: &json::Element<'_>, warning: W) -> Verdict<T, W> {
        let Self(warnings) = self;

        Err(ErrorSet {
            error: Box::new(Error {
                warning,
                element: Element::from_json(element),
            }),
            warnings,
        })
    }

    /// Converts `Set<W>` into `Set<WA>` using the `impl Into<WA> for W`.
    ///
    /// This is used by the [`from_warning_all`] macro.
    pub(crate) fn into_other<WA>(self) -> Set<WA>
    where
        W: Into<WA>,
        WA: Warning,
    {
        let Set(warnings) = self;
        let warnings = warnings
            .into_iter()
            .map(|(elem_id, group)| (elem_id, group.into_other()))
            .collect();
        Set(warnings)
    }

    /// Retain only the [`Warning`]s for which `keep` returns `true`.
    ///
    /// Any [`Group`] left without warnings is removed from the set.
    pub(crate) fn retain<F>(&mut self, mut keep: F)
    where
        F: FnMut(&W) -> bool,
    {
        self.0.retain(|_elem_id, group| {
            group.warnings.retain(|source| keep(&source.warning));
            !group.warnings.is_empty()
        });
    }

    /// Return true if the [`Warning`] set is empty.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Return the amount of [`Element`]s in this set.
    ///
    /// Each [`Element`] can have many [`Warning`]s associated with it.
    pub fn len_elements(&self) -> usize {
        self.0.len()
    }

    /// Return the total amount of [`Warning`]s in this set for all [`Element`]s.
    pub fn len_warnings(&self) -> usize {
        self.0
            .values()
            .fold(0, |acc, group| acc.saturating_add(group.warnings.len()))
    }

    /// Return an iterator of [`Warning`]s grouped by [`json::Element`].
    pub fn iter(&self) -> Iter<'_, W> {
        Iter {
            warnings: self.0.iter(),
        }
    }

    /// Return a collection of `Id`s mapped to the paths they occurred at.
    pub fn id_path_map(&self, config: Limit) -> IdPathMap<'_> {
        let report = match config {
            Limit::None => limit_none(&self.0),
            Limit::WarningTypes(max_warning_types) => {
                limit_warning_types(max_warning_types, &self.0)
            }
            Limit::ElemPathsPerId(max_elem_paths_per_warning_id) => {
                limit_elem_paths_per_id(max_elem_paths_per_warning_id, &self.0)
            }
            Limit::All {
                max_warning_types,
                max_elem_paths_per_warning_id,
            } => limit_all(max_warning_types, max_elem_paths_per_warning_id, &self.0),
        };

        let LimitReport {
            elements_filtered,
            warning_distinct_types_filtered,
            warnings,
        } = report;

        IdPathMap {
            total_warnings: self.len_warnings(),
            total_elements: self.len_elements(),
            elements_filtered,
            warning_distinct_types_filtered,
            warnings,
        }
    }

    /// Return a collection of warning messages mapped to the paths they occurred at.
    pub fn msg_path_map(&self, config: Limit) -> MsgPathMap<'_> {
        let IdPathMap {
            total_warnings,
            total_elements,
            elements_filtered,
            warning_distinct_types_filtered,
            warnings,
        } = self.id_path_map(config);

        let warnings = warnings
            .into_iter()
            .map(|(id, paths)| (id.to_string(), paths))
            .collect();

        MsgPathMap {
            total_warnings,
            total_elements,
            elements_filtered,
            warning_distinct_types_filtered,
            warnings,
        }
    }

    /// Return a map of [`json::Element`] paths to a list of [`Warning`].
    ///
    /// This is designed to be used to print out maps of warnings associated with elements.
    /// You can use the debug alternate format `{:#?}` to print the map 'pretty' over multiple lines
    /// with indentation.
    pub fn path_map(&self) -> BTreeMap<&str, Vec<&W>> {
        self.0
            .values()
            .map(|Group { element, warnings }| {
                let path = element.path.as_str();
                let warnings = warnings.iter().map(|w| &**w).collect();
                (path, warnings)
            })
            .collect()
    }

    /// Consume the `Set` and return a map of [`json::Element`] paths to a list of [`Warning`]s.
    ///
    /// This is designed to be used to print out maps of warnings associated with elements.
    pub fn into_path_map(self) -> BTreeMap<json::Path, Vec<W>> {
        self.0
            .into_values()
            .map(|Group { element, warnings }| {
                let warnings = warnings.into_iter().map(Source::into_warning).collect();
                (element.path, warnings)
            })
            .collect()
    }

    /// Return a map of [`json::Element`] paths to a list of [`Warning`] ids as Strings.
    ///
    /// This is designed to be used to print out maps of warnings associated with elements.
    /// You can use the debug alternate format `{:#?}` to print the map 'pretty' over multiple lines
    /// with indentation.
    ///
    /// Note: This representation is also valid JSON and can be copied directly to
    /// a test expectation file.
    pub fn path_id_map(&self) -> BTreeMap<&str, Vec<Id>> {
        self.0
            .values()
            .map(|group| {
                let warnings = group.warnings.iter().map(|w| w.id()).collect();
                (group.element.path.as_str(), warnings)
            })
            .collect()
    }

    /// Return a map of [`json::Element`] paths to a list of [`Warning`] messages as Strings.
    ///
    /// This is designed to be used to print out maps of warnings associated with elements.
    /// You can use the debug alternate format `{:#?}` to print the map 'pretty' over multiple lines
    /// with indentation.
    pub fn path_msg_map(&self) -> BTreeMap<&str, Vec<String>> {
        self.0
            .values()
            .map(|group| {
                let warnings = group.warnings.iter().map(|w| w.to_string()).collect();
                (group.element.path.as_str(), warnings)
            })
            .collect()
    }

    /// Deescalate an [`Error`] by subsuming it back into a `Set`.
    pub(crate) fn deescalate_error(&mut self, err_set: ErrorSet<W>) {
        let ErrorSet { error, warnings } = err_set;
        let Error { warning, element } = *error;
        self.0.extend(warnings);
        self.insert_warning(element.id, warning, || element);
    }

    /// Extend this set with the warnings of another.
    ///
    /// The other set's warnings will be converted if necessary.
    pub(crate) fn extend(&mut self, warnings: impl Iterator<Item = (json::ElemId, Group<W>)>) {
        use std::collections::btree_map::Entry;

        for (elem_id, group) in warnings {
            match self.0.entry(elem_id) {
                Entry::Vacant(entry) => {
                    entry.insert_entry(group);
                }
                Entry::Occupied(mut entry) => {
                    let Group {
                        element: _,
                        warnings,
                    } = group;
                    entry.get_mut().warnings.extend(warnings);
                }
            }
        }
    }
}

/// The outcome of calling the `limit_*` functions related to the [`Set::id_path_map`] function.
#[derive(Debug)]
struct LimitReport<'set> {
    /// The amount of [`json::Element`] paths filtered due to a [`Limit`] being set.
    pub elements_filtered: usize,

    /// The amount of [`Warning`] [`Id`]s filtered due to a [`Limit`] being set.
    ///
    /// Note: This is not a count of how many warnings were filtered. It's a count of how many
    /// types of warnings were filtered. If seven `excessive_precision` warnings are filtered,
    /// this counts as one type, as all the IDs that were filtered are the same.
    pub warning_distinct_types_filtered: usize,

    /// The map of all [`Warning`] [`Id`]s mapped to the [`json::Element`] paths where they occurred.
    pub warnings: BTreeMap<Id, Vec<&'set str>>,
}

/// The logic for the [`Limit::None`] variant.
fn limit_none<W: Warning>(warnings: &BTreeMap<json::ElemId, Group<W>>) -> LimitReport<'_> {
    let mut out = BTreeMap::new();

    for group in warnings.values() {
        let Group { element, warnings } = group;
        let path = element.path.as_str();

        for w in warnings {
            match out.entry(w.id()) {
                btree_map::Entry::Vacant(entry) => {
                    entry.insert(vec![path]);
                }
                btree_map::Entry::Occupied(mut entry) => {
                    entry.get_mut().push(path);
                }
            }
        }
    }

    LimitReport {
        warnings: out,
        elements_filtered: 0,
        warning_distinct_types_filtered: 0,
    }
}

/// The logic for the [`Limit::WarningTypes`] variant.
fn limit_warning_types<W: Warning>(
    max_warning_types: usize,
    warnings: &BTreeMap<json::ElemId, Group<W>>,
) -> LimitReport<'_> {
    let mut out = BTreeMap::new();
    // A set of element paths encountered and filtered. Element paths can be encountered more
    // than once, so a simple `usize` can't be used to count the encounters.
    let mut elements_filtered = HashSet::new();
    // A set of warnings encountered and filtered. Warnings can be encountered more than once,
    // so a simple `usize` can't be used to count the encounters.
    let mut warning_distinct_types_filtered = HashSet::new();

    for group in warnings.values() {
        let Group { element, warnings } = group;
        let path = element.path.as_str();
        // True if any of the warnings are filtered,
        // therefore this element should be considered filtered too.
        let mut filtered = false;

        for w in warnings {
            let len = out.len();
            let id = w.id();

            match out.entry(id.clone()) {
                btree_map::Entry::Vacant(entry) => {
                    if len < max_warning_types {
                        entry.insert(vec![path]);
                    } else {
                        warning_distinct_types_filtered.insert(id);
                        filtered = true;
                    }
                }
                btree_map::Entry::Occupied(mut entry) => {
                    entry.get_mut().push(path);
                }
            }
        }

        if filtered {
            elements_filtered.insert(path);
        }
    }

    LimitReport {
        warnings: out,
        elements_filtered: elements_filtered.len(),
        warning_distinct_types_filtered: warning_distinct_types_filtered.len(),
    }
}

/// The logic for the [`Limit::ElemPathsPerId`] variant.
fn limit_elem_paths_per_id<W: Warning>(
    max_elem_paths_per_warning_id: usize,
    warnings: &BTreeMap<json::ElemId, Group<W>>,
) -> LimitReport<'_> {
    let mut out = BTreeMap::new();
    // A set of element paths encountered and filtered. Element paths can be encountered more
    // than once, so a simple `usize` can't be used to count the encounters.
    let mut elements_filtered = HashSet::new();

    if max_elem_paths_per_warning_id == 0 {
        for group in warnings.values() {
            let Group { element, warnings } = group;

            for w in warnings {
                if let btree_map::Entry::Vacant(entry) = out.entry(w.id()) {
                    entry.insert(vec![]);
                }
            }
            elements_filtered.insert(element.path.as_str());
        }
    } else {
        for group in warnings.values() {
            let Group { element, warnings } = group;
            let path = element.path.as_str();

            for w in warnings {
                let id = w.id();

                match out.entry(id.clone()) {
                    btree_map::Entry::Vacant(entry) => {
                        entry.insert(vec![path]);
                    }
                    btree_map::Entry::Occupied(mut entry) => {
                        if entry.get().len() < max_elem_paths_per_warning_id {
                            entry.get_mut().push(path);
                        } else {
                            elements_filtered.insert(path);
                        }
                    }
                }
            }
        }
    }

    LimitReport {
        warnings: out,
        elements_filtered: elements_filtered.len(),
        warning_distinct_types_filtered: 0,
    }
}

/// The logic for the [`Limit::All`] variant.
fn limit_all<W: Warning>(
    max_warning_types: usize,
    max_elem_paths_per_warning_id: usize,
    warnings: &BTreeMap<json::ElemId, Group<W>>,
) -> LimitReport<'_> {
    let mut out = BTreeMap::new();
    // A set of element paths encountered and filtered. Element paths can be encountered more
    // than once, so a simple `usize` can't be used to count the encounters.
    let mut elements_filtered = HashSet::new();
    // A set of warnings encountered and filtered. Warnings can be encountered more than once,
    // so a simple `usize` can't be used to count the encounters.
    let mut warning_distinct_types_filtered = HashSet::new();

    if max_warning_types > 0 && max_elem_paths_per_warning_id == 0 {
        for group in warnings.values() {
            let Group { element, warnings } = group;
            let path = element.path.as_str();

            for w in warnings {
                let len = out.len();
                let id = w.id();

                if let btree_map::Entry::Vacant(entry) = out.entry(id.clone()) {
                    if len < max_warning_types {
                        entry.insert(vec![]);
                    } else {
                        warning_distinct_types_filtered.insert(id);
                    }
                }
            }

            elements_filtered.insert(path);
        }
    } else {
        for group in warnings.values() {
            let Group { element, warnings } = group;
            let path = element.path.as_str();

            for w in warnings {
                let len = out.len();
                let id = w.id();

                match out.entry(id.clone()) {
                    btree_map::Entry::Vacant(entry) => {
                        if len < max_warning_types {
                            entry.insert(vec![path]);
                        } else {
                            warning_distinct_types_filtered.insert(id);
                            elements_filtered.insert(path);
                        }
                    }
                    btree_map::Entry::Occupied(mut entry) => {
                        if entry.get().len() < max_elem_paths_per_warning_id {
                            entry.get_mut().push(path);
                        } else {
                            elements_filtered.insert(path);
                        }
                    }
                }
            }
        }
    }

    LimitReport {
        warnings: out,
        elements_filtered: elements_filtered.len(),
        warning_distinct_types_filtered: warning_distinct_types_filtered.len(),
    }
}

/// The outcome of calling the [`Set::id_path_map`] function.
#[derive(Debug)]
pub struct IdPathMap<'set> {
    /// The total amount of [`Warning`]s [`Id`]s in the source [`Set`].
    pub total_warnings: usize,

    /// The total amount of [`json::Element`] paths in the source [`Set`].
    pub total_elements: usize,

    /// The amount of [`json::Element`] paths filtered due to a [`Limit`] being set.
    pub elements_filtered: usize,

    /// The amount of [`Warning`] [`Id`]s filtered due to a [`Limit`] being set.
    ///
    /// Note: This is not a count of how many warnings were filtered. It's a count of how many
    /// types of warnings were filtered. If seven `excessive_precision` warnings are filtered,
    /// this counts as one type, as all the IDs that were filtered are the same.
    pub warning_distinct_types_filtered: usize,

    /// The map of all [`Warning`] [`Id`]s mapped to the [`json::Element`] paths where they occurred.
    pub warnings: BTreeMap<Id, Vec<&'set str>>,
}

/// The outcome of calling the [`Set::msg_path_map`] function.
#[derive(Debug)]
pub struct MsgPathMap<'set> {
    /// The total amount of [`Warning`]s [`Id`]s in the source [`Set`].
    pub total_warnings: usize,

    /// The total amount of [`json::Element`] paths in the source [`Set`].
    pub total_elements: usize,

    /// The amount of [`json::Element`] paths filtered due to a [`Limit`] being set.
    pub elements_filtered: usize,

    /// The amount of [`Warning`] [`Id`]s filtered due to a [`Limit`] being set.
    ///
    /// Note: This is not a count of how many warnings were filtered. It's a count of how many
    /// types of warnings were filtered. If seven `excessive_precision` warnings are filtered,
    /// this counts as one type, as all the IDs that were filtered are the same.
    pub warning_distinct_types_filtered: usize,

    /// The map of all [`Warning`] [`Id`]s mapped to the [`json::Element`] paths where they occurred.
    pub warnings: BTreeMap<String, Vec<&'set str>>,
}

/// The limiting configuration of the [`Set::path_id_map`] function.
#[derive(Copy, Clone, Debug)]
pub enum Limit {
    /// Don't enforce any limits on [`Warning`] [`Id`]s or [`json::Element`] [`json::Path`]s.
    None,

    /// Forbid more than this amount of [`Warning`] [`Id`]s to be inserted into the map.
    WarningTypes(usize),

    /// Forbid more than this amount of [`json::Element`] [`json::Path`]s to be inserted into the list for each [`Id`].
    ElemPathsPerId(usize),

    All {
        /// Forbid more than this amount of [`Warning`] [`Id`]s to be inserted into the map.
        max_warning_types: usize,

        /// Forbid more than this amount of [`json::Element`] [`json::Path`]s to be inserted into the list for each [`Id`].
        max_elem_paths_per_warning_id: usize,
    },
}

/// A set of [`Warning`]s and a [`Warning`] that caused an operation to fail to be represented as an [`Error`].
///
/// This set is transported through the system using a [`Verdict`]s `Err` variant.
#[derive(Debug)]
pub struct ErrorSet<W: Warning> {
    /// The warning that caused an operation to fail.
    ///
    /// The warning is converted to an [`Error`] so it's ready to take part in Rust's error system.
    error: Box<Error<W>>,

    /// The warnings accumulated up until the failure moment.
    ///
    /// This list does not included the warning that caused the operation to fail.
    warnings: BTreeMap<json::ElemId, Group<W>>,
}

impl<W> ErrorSet<W>
where
    W: Warning,
{
    /// Consume the [`ErrorSet`] and return the [`Error`] and warnings as a `Set`.
    pub fn into_parts(self) -> (Error<W>, Set<W>) {
        let Self { error, warnings } = self;
        (*error, Set(warnings))
    }

    /// Converts `ErrorSet<W>` into `ErrorSet<WA>` using the `impl Into<WA> for K`.
    ///
    /// This is used by the [`from_warning_all`] macro.
    pub(crate) fn into_other<WA>(self) -> ErrorSet<WA>
    where
        W: Into<WA>,
        WA: Warning,
    {
        let Self { error, warnings } = self;
        let warnings = warnings
            .into_iter()
            .map(|(elem_id, group)| (elem_id, group.into_other()))
            .collect();
        ErrorSet {
            error: Box::new(Error::into_other(*error)),
            warnings,
        }
    }
}

/// A group of warning `Warning`s associated with an `Element`.
///
/// This group is emitted from the `IntoGroupByElem` iterator.
/// The warning `Warning`s are owned and so can be moved to another location.
#[derive(Debug)]
pub struct Group<W: Warning> {
    /// The [`json::Element`] that has [`Warning`]s.
    element: Element,

    /// The list of warnings for the [`json::Element`].
    warnings: Vec<Source<W>>,
}

impl<W> Group<W>
where
    W: Warning,
{
    /// Consume the `Group` and return the constituent parts.
    pub fn into_parts(self) -> (Element, Vec<W>) {
        let Self { element, warnings } = self;
        let warnings = warnings.into_iter().map(Source::into_warning).collect();
        (element, warnings)
    }

    /// Map the `Warning` contained in the `Group` to another `Warning` type.
    pub(crate) fn map<F, WA>(self, mut f: F) -> Group<WA>
    where
        F: FnMut(W) -> WA,
        WA: Warning,
    {
        let Self { element, warnings } = self;
        let warnings = warnings
            .into_iter()
            .map(|source| source.map(&mut f))
            .collect();
        Group { element, warnings }
    }

    pub fn to_parts(&self) -> (&Element, Vec<&W>) {
        let Self { element, warnings } = self;
        let warnings = warnings.iter().map(|w| &**w).collect();
        (element, warnings)
    }

    pub fn warnings(&self) -> Vec<&W> {
        self.warnings.iter().map(|w| &**w).collect()
    }

    pub fn into_warnings(self) -> Vec<W> {
        let Self {
            element: _,
            warnings,
        } = self;
        warnings.into_iter().map(Source::into_warning).collect()
    }

    /// Converts `IntoGroup<W>` into `IntoGroup<WA>` using the `impl Into<WA> for K`.
    ///
    /// This is used by the [`from_warning_all`] macro.
    fn into_other<WA>(self) -> Group<WA>
    where
        W: Into<WA>,
        WA: Warning,
    {
        let Self { element, warnings } = self;
        let warnings = warnings.into_iter().map(Source::into_other).collect();
        Group { element, warnings }
    }
}

/// An iterator of borrowed [`Warning`]s grouped by [`json::Element`].
pub struct Iter<'caller, W>
where
    W: Warning,
{
    /// The iterator over every [`Warning`].
    warnings: btree_map::Iter<'caller, json::ElemId, Group<W>>,
}

impl<W> Iter<'_, W> where W: Warning {}

impl<'caller, W: Warning> Iterator for Iter<'caller, W> {
    type Item = &'caller Group<W>;

    fn next(&mut self) -> Option<Self::Item> {
        let (_elem_id, group) = self.warnings.next()?;
        Some(group)
    }
}

/// An iterator of borrowed [`Warning`]s grouped by [`json::Element`].
pub struct IntoIter<W>
where
    W: Warning,
{
    /// The iterator over every [`Warning`].
    warnings: btree_map::IntoIter<json::ElemId, Group<W>>,
}

impl<W: Warning> Iterator for IntoIter<W> {
    type Item = Group<W>;

    fn next(&mut self) -> Option<Self::Item> {
        let (_elem_id, group) = self.warnings.next()?;
        Some(group)
    }
}

impl<W: Warning> IntoIterator for Set<W> {
    type Item = Group<W>;
    type IntoIter = IntoIter<W>;

    fn into_iter(self) -> Self::IntoIter {
        let Set(warnings) = self;
        IntoIter {
            warnings: warnings.into_iter(),
        }
    }
}

impl<'a, W: Warning> IntoIterator for &'a Set<W> {
    type Item = &'a Group<W>;
    type IntoIter = Iter<'a, W>;

    fn into_iter(self) -> Self::IntoIter {
        self.iter()
    }
}
