//! Types and functions for parsing JSON in a flexible and pedantic manner.

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

#[cfg(test)]
mod test_path;

#[cfg(test)]
mod test_parse_with_schema;

#[cfg(test)]
mod test_source_json;

#[cfg(test)]
mod test_path_node;

#[cfg(test)]
mod test_path_node_matches_str;

#[cfg(test)]
mod test_path_matches_glob;

pub mod decode;
pub mod parser;
pub(crate) mod schema;
pub(crate) mod walk;
pub mod write;

use std::{
    collections::BTreeMap,
    fmt::{self, Write as _},
    sync::Arc,
};

use tracing::{trace, Level};

use crate::{
    warning::{self, IntoCaveat},
    ReasonableStr, Verdict,
};
use parser::ErrorKind;
use parser::{Parser, Span};

#[doc(inline)]
pub use parser::{line_col, Error, ErrorReport, LineCol};
pub(crate) use parser::{parse, RawStr};

const PATH_SEPARATOR: char = '.';
const PATH_ROOT: &str = "$";

/// Return the `json::Element` If the given field exists(`Some`).
/// Otherwise, add a `Warning::FieldRequired` to the set of `Warning`s and return them as an `Err`.
#[doc(hidden)]
#[macro_export]
macro_rules! required_field_or_bail {
    ($elem:expr, $fields:expr, $field_name:literal, $warnings:expr) => {
        match $fields.get($field_name) {
            Some(field_elem) => field_elem,
            None => {
                return $warnings.bail(
                    Warning::FieldRequired {
                        field_name: $field_name.into(),
                    },
                    $elem,
                );
            }
        }
    };
}

/// Return the `json::Element` If the given field exists(`Some`).
/// Otherwise, add a `Warning::FieldRequired` to the set of `Warning`s and return them as an `Err`.
#[doc(hidden)]
#[macro_export]
macro_rules! required_field {
    ($elem:expr, $fields:expr, $field_name:literal, $warnings:expr) => {{
        let field = $fields.get($field_name);

        if field.is_none() {
            $warnings.insert(
                Warning::FieldRequired {
                    field_name: $field_name.into(),
                },
                $elem,
            );
        }

        field
    }};
}

/// Return the `Field`s of the given `json::Element` if it's a JSON object.
/// Otherwise, add a `Warning::FieldInvalidType` to the set of `Warning`s and return then as an `Err`.
#[doc(hidden)]
#[macro_export]
macro_rules! expect_object_or_bail {
    ($elem:expr, $warnings:expr) => {
        match $elem.as_object_fields() {
            Some(fields) => fields,
            None => {
                return $warnings.bail(
                    Warning::FieldInvalidType {
                        expected_type: json::ValueKind::Object,
                    },
                    $elem,
                );
            }
        }
    };
}

/// Return the `json::Element`s of the given `json::Element` if it's a JSON array.
/// Otherwise, add a `Warning::FieldInvalidType` to the set of `Warning`s and return then as an `Err`.
#[doc(hidden)]
#[macro_export]
macro_rules! expect_array_or_bail {
    ($elem:expr, $warnings:expr) => {
        match $elem.as_array() {
            Some(fields) => fields,
            None => {
                return $warnings.bail(
                    Warning::FieldInvalidType {
                        expected_type: json::ValueKind::Array,
                    },
                    $elem,
                );
            }
        }
    };
}

/// Get a field by name and fail if it doesn't exist.
///
/// Convert the value of the field to the `$target` type.
#[doc(hidden)]
#[macro_export]
macro_rules! parse_required_or_bail {
    ($elem:expr, $fields:expr, $elem_name:literal, $target:ty, $warnings:expr) => {{
        #[expect(clippy::allow_attributes, reason = "The allow attribute is needed here as the callers scope determines if the imports are used or not")]
        #[allow(
            unused,
            reason = "The macro uses the import but maybe the outside scope does too."
        )]
        use $crate::json::FromJson;
        use $crate::warning::GatherWarnings as _;

        let elem = $crate::required_field_or_bail!($elem, $fields, $elem_name, $warnings);
        <$target as FromJson>::from_json(elem)?.gather_warnings_into(&mut $warnings)
    }};
}

/// Get a field by name and return `None` if it doesn't exist.
///
/// Convert the value of the field to the `$target` type.
#[doc(hidden)]
#[macro_export]
macro_rules! parse_required {
    ($elem:expr, $fields:expr, $elem_name:literal, $target:ty, $warnings:expr) => {{
        #[expect(
            clippy::allow_attributes,
            reason = "The allow attribute is needed here as the callers scope determines if the imports are used or not"
        )]
        #[allow(
            unused,
            reason = "The macro uses the import but maybe the outside scope does too."
        )]
        use $crate::json::FromJson;
        use $crate::warning::GatherWarnings as _;

        let elem = $crate::required_field!($elem, $fields, $elem_name, $warnings);

        if let Some(elem) = elem {
            let value =
                <$target as FromJson>::from_json(elem)?.gather_warnings_into(&mut $warnings);
            Some(value)
        } else {
            None
        }
    }};
}

/// Get an optional field by name and convert the field value to the `$target` type using the
/// blanket impl `Option<$target>` that can handle `null` JSON values.
#[doc(hidden)]
#[macro_export]
macro_rules! parse_nullable_or_bail {
    ($fields:expr, $elem_name:literal, $target:ty, $warnings:expr) => {{
        #[expect(
            clippy::allow_attributes,
            reason = "The allow attribute is needed here as the callers scope determines if the imports are used or not"
        )]
        #[allow(
            unused,
            reason = "The macro uses the import but maybe the outside scope does too."
        )]
        use $crate::json::FromJson as _;
        use $crate::warning::GatherWarnings as _;

        match $fields.get($elem_name) {
            Some(elem) => Option::<$target>::from_json(elem)?.gather_warnings_into(&mut $warnings),
            None => None,
        }
    }};
}

/// A trait for converting `Element`s to Rust types.
pub(crate) trait FromJson<'buf>: Sized {
    type Warning: crate::Warning;

    /// Convert the given `Element` to `Self`.
    fn from_json(elem: &Element<'buf>) -> Verdict<Self, Self::Warning>;
}

/// Implement a `null` aware variant of all types that implement `FromJson`.
///
/// If the value of the `json::Element` is `null` then return a `None`.
impl<'buf, T> FromJson<'buf> for Option<T>
where
    T: FromJson<'buf> + IntoCaveat,
{
    type Warning = T::Warning;

    fn from_json(elem: &Element<'buf>) -> Verdict<Self, Self::Warning> {
        let value = elem.as_value();

        if value.is_null() {
            Ok(None.into_caveat(warning::Set::new()))
        } else {
            let v = T::from_json(elem)?;
            Ok(v.map(Some))
        }
    }
}

/// A JSON [`Element`] composed of a `Path` and it's [`Value`].
///
/// The `Span` is included so that the [`Element`]'s source `&str` can be acquired from the source JSON if needed.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Element<'buf> {
    /// Used to reference the Element from `Warning`s.
    id: ElemId,

    /// The `Path` to this [`Element`].
    path_node: PathNodeRef<'buf>,

    /// The `Span` of this [`Element`].
    ///
    /// The `Span` defines the range of bytes that delimits this JSON [`Element`].
    span: Span,

    /// The `Value` of this [`Element`].
    value: Value<'buf>,
}

/// A simple integer index used to ID the given [`Element`] within a JSON file.
///
/// The Id is unique until `usize::MAX` [`Element`]s are parsed.
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Ord, PartialOrd)]
pub(crate) struct ElemId(usize);

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

impl<'buf> Element<'buf> {
    /// Create a new `Element`.
    fn new(id: ElemId, path: PathNodeRef<'buf>, span: Span, value: Value<'buf>) -> Element<'buf> {
        Element {
            id,
            path_node: path,
            span,
            value,
        }
    }

    /// Return the unique Id for this `Element`.
    pub(crate) const fn id(&self) -> ElemId {
        self.id
    }

    /// Return the `Path` to this `Element`.
    pub fn path(&self) -> PathRef<'buf> {
        PathRef(self.path_node())
    }

    /// Return the `PathNode` to this `Element`.
    pub(crate) fn path_node(&self) -> PathNodeRef<'buf> {
        Arc::clone(&self.path_node)
    }

    /// Return the `Span` of the `Element`'s `&str` is the source JSON.
    pub fn span(&self) -> Span {
        self.span
    }

    /// Return the source JSON `&str` of the entire Object field if the `Element` is an Object.
    /// Otherwise, return the span of the [`Value`].
    ///
    /// In the case of an array like `["one", "two"]`, calling this method on the second `Element`
    /// will return `"two"`.
    ///
    /// In the case of an object like `{"one": 1, "two": 2}`, calling this method on the second
    /// `Element` will return `"\"two\": 2"`.
    ///
    /// # Panics
    ///
    /// If a source JSON is used that this `Element` didn't not originate from there is a chance
    /// that this function will panic.
    pub fn source_json(&self, source_json: &'buf str) -> SourceStr<'buf> {
        if let PathNode::Object { key, .. } = *self.path_node {
            // The span of an objects field starts from the start of the key.
            let span = Span {
                start: key.span().start,
                end: self.span.end,
            };
            let field_str = &source_json
                .get(span.start..span.end)
                .expect("The disconnection between the source JSON and the `Element` will be fixed in a future PR");
            let field = RawStr::from_str(field_str, span);
            let (key, value) = field_str
                .split_once(':')
                .expect("An objects field always contains a delimiting `:`");

            SourceStr::Field { field, key, value }
        } else {
            let span = self.span;
            let s = source_json
                .get(span.start..span.end)
                .expect("The disconnection between the source JSON and the `Element` will be fixed in a future PR");
            SourceStr::Value(RawStr::from_str(s, span))
        }
    }

    /// Return the source JSON `&str` for the [`Value`].
    ///
    /// In the case of an array like `["one", "two"]`, calling this method on the second `Element`
    /// will return `"two"`.
    ///
    /// In the case of an object like `{"one": 1, "two": 2}`, calling this method on the second
    /// `Element` will return `"2"`.
    ///
    /// # Panics
    ///
    /// If a source JSON is used that this `Element` didn't not originate from there is a chance
    /// that this function will panic.
    pub fn source_json_value(&self, source_json: &'buf str) -> &'buf str {
        source_json
            .get(self.span.start..self.span.end)
            .expect("The disconnection between the source JSON and the `Element` will be fixed in a future PR")
    }

    /// Return the `Value` of the `Element`.
    pub(crate) fn value(&self) -> &Value<'buf> {
        &self.value
    }

    /// Return the `&Value`.
    pub(crate) fn as_value(&self) -> &Value<'buf> {
        &self.value
    }

    /// Return `Some(&str)` if the `Value` is a `String`.
    pub(crate) fn to_raw_str(&self) -> Option<RawStr<'buf>> {
        self.value.to_raw_str()
    }

    /// Return `Some(&[Field])` if the `Value` is a `Object`.
    pub(crate) fn as_object_fields(&self) -> Option<&[Field<'buf>]> {
        self.value.as_object_fields()
    }

    pub(crate) fn as_array(&self) -> Option<&[Element<'buf>]> {
        self.value.as_array()
    }

    pub fn as_number_str(&self) -> Option<&str> {
        self.value.as_number()
    }

    /// Return true if the `Element`s `Value` is null.
    pub fn is_null(&self) -> bool {
        self.value.is_null()
    }
}

#[derive(Debug)]
pub enum SourceStr<'buf> {
    /// The source `&str` of the given [`Value`].
    Value(RawStr<'buf>),

    /// The key-value pair of the given [`Element`] where the [`PathNode`] is referring to an object.
    Field {
        /// The entire field as a `&str`. This is the `&str` from the beginning of the key to the end of the value.
        field: RawStr<'buf>,

        /// The key excluding the separating `:`.
        key: &'buf str,

        /// The [`Value`] as `&str`.
        value: &'buf str,
    },
}

impl fmt::Display for SourceStr<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            SourceStr::Value(s) => f.write_str(s.as_raw()),
            SourceStr::Field { field, .. } => f.write_str(field.as_raw()),
        }
    }
}

/// The `SourceStr` can be compared with other strings just like a `String`.
impl PartialEq<&str> for SourceStr<'_> {
    fn eq(&self, other: &&str) -> bool {
        match self {
            SourceStr::Value(s) => s.as_raw() == *other,
            SourceStr::Field { field, .. } => field.as_raw() == *other,
        }
    }
}

/// The `SourceStr` can be compared with other strings just like a `String`.
impl PartialEq<String> for SourceStr<'_> {
    fn eq(&self, other: &String) -> bool {
        self.eq(&&**other)
    }
}

impl PartialOrd for Element<'_> {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Element<'_> {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.path_node.cmp(&other.path_node)
    }
}

impl fmt::Display for Element<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} = {}", self.path_node, self.value)
    }
}

/// A JSON `Object`'s field that upholds the invariant that the `Element`'s `Path` is as `Object`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct Field<'buf>(Element<'buf>);

impl<'buf> Field<'buf> {
    pub(crate) fn key(&self) -> RawStr<'buf> {
        let PathNode::Object { key, .. } = *self.0.path_node else {
            unreachable!("A Field is created by the parser when the type is an Object.");
        };

        key
    }

    /// Consume the `Field` and return the inner `Element`.
    pub(crate) fn into_element(self) -> Element<'buf> {
        self.0
    }

    /// Consume the `Field` and return the inner `Element`.
    pub(crate) fn element(&self) -> &Element<'buf> {
        &self.0
    }
}

/// A JSON `Value` that borrows it's content from the source JSON `&str`.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) enum Value<'buf> {
    /// A `"null"` value.
    Null,

    /// A `"true"` value.
    True,

    /// A `"false"` value.
    False,

    /// The value of the `String` has the quotes trimmed.
    String(RawStr<'buf>),

    /// A JSON `Number` in string format.
    ///
    /// The string is not guaranteed to be a valid number. Only that it's not a `null`, `bool` or `string` value.
    /// Convert the string into the number format you want.
    Number(&'buf str),

    /// A JSON `Array` parsed into a `Vec` of `Elements`.
    ///
    /// The inner `Element`'s path also encodes the index.
    /// E.g. `$.elements.2`: This path refers to third OCPI tariff element.
    Array(Vec<Element<'buf>>),

    /// A JSON `Object` where each of the fields are parsed into a `Vec` of `Elements`.
    ///
    /// The inner `Element`'s path encodes the fields key.
    /// E.g. `$.elements.2.restrictions` This path refers to the `restrictions` `Object`
    /// of the third OCPI tariff element.
    Object(Vec<Field<'buf>>),
}

impl<'buf> Value<'buf> {
    pub(crate) fn kind(&self) -> ValueKind {
        match self {
            Value::Null => ValueKind::Null,
            Value::True | Value::False => ValueKind::Bool,
            Value::String(_) => ValueKind::String,
            Value::Number(_) => ValueKind::Number,
            Value::Array(_) => ValueKind::Array,
            Value::Object(_) => ValueKind::Object,
        }
    }

    pub(crate) fn is_null(&self) -> bool {
        matches!(self, Value::Null)
    }

    /// Return true if the `Value` can't contain child elements.
    pub(crate) fn is_scalar(&self) -> bool {
        matches!(
            self,
            Value::Null | Value::True | Value::False | Value::String(_) | Value::Number(_)
        )
    }

    pub(crate) fn as_array(&self) -> Option<&[Element<'buf>]> {
        if let Value::Array(elems) = self {
            Some(elems)
        } else {
            None
        }
    }

    pub(crate) fn as_number(&self) -> Option<&str> {
        if let Value::Number(s) = self {
            Some(s)
        } else {
            None
        }
    }

    /// Return `Some(&str)` if the `Value` is a `String`.
    pub(crate) fn to_raw_str(&self) -> Option<RawStr<'buf>> {
        if let Value::String(s) = self {
            Some(*s)
        } else {
            None
        }
    }

    /// Return `Some(&[Field])` if the `Value` is a `Object`.
    pub(crate) fn as_object_fields(&self) -> Option<&[Field<'buf>]> {
        if let Value::Object(fields) = self {
            Some(fields)
        } else {
            None
        }
    }
}

impl fmt::Display for Value<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Null => write!(f, "null"),
            Self::True => write!(f, "true"),
            Self::False => write!(f, "false"),
            Self::String(s) => write!(f, "{s}"),
            Self::Number(s) => write!(f, "{s}"),
            Self::Array(..) => f.write_str("[...]"),
            Self::Object(..) => f.write_str("{...}"),
        }
    }
}

/// A light-weight type identity for a JSON `Value`'s content.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum ValueKind {
    Null,
    Bool,
    Number,
    String,
    Array,
    Object,
}

impl fmt::Display for ValueKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ValueKind::Null => write!(f, "null"),
            ValueKind::Bool => write!(f, "bool"),
            ValueKind::Number => write!(f, "number"),
            ValueKind::String => write!(f, "string"),
            ValueKind::Array => write!(f, "array"),
            ValueKind::Object => write!(f, "object"),
        }
    }
}

/// Used to distinguish the type of compound object.
///
/// This is used to track which type of object an `Element` is in when parsing.
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub(crate) enum ObjectKind {
    Object,
    Array,
}

pub type RawMap<'buf> = BTreeMap<RawStr<'buf>, Element<'buf>>;
pub type RawRefMap<'a, 'buf> = BTreeMap<RawStr<'buf>, &'a Element<'buf>>;

#[expect(dead_code, reason = "pending use in `tariff::lint`")]
pub(crate) trait FieldsIntoExt<'buf> {
    fn into_map(self) -> RawMap<'buf>;
}

pub(crate) trait FieldsAsExt<'buf> {
    fn as_raw_map(&self) -> RawRefMap<'_, 'buf>;
    fn find_field(&self, key: &str) -> Option<&Field<'buf>>;
}

impl<'buf> FieldsIntoExt<'buf> for Vec<Field<'buf>> {
    fn into_map(self) -> RawMap<'buf> {
        self.into_iter()
            .map(|field| (field.key(), field.into_element()))
            .collect()
    }
}

impl<'buf> FieldsAsExt<'buf> for Vec<Field<'buf>> {
    fn as_raw_map(&self) -> RawRefMap<'_, 'buf> {
        self.iter()
            .map(|field| (field.key(), field.element()))
            .collect()
    }

    fn find_field(&self, key: &str) -> Option<&Field<'buf>> {
        self.iter().find(|field| field.key().as_raw() == key)
    }
}

impl<'buf> FieldsAsExt<'buf> for [Field<'buf>] {
    fn as_raw_map(&self) -> RawRefMap<'_, 'buf> {
        self.iter()
            .map(|field| (field.key(), field.element()))
            .collect()
    }

    fn find_field(&self, key: &str) -> Option<&Field<'buf>> {
        self.iter().find(|field| field.key().as_raw() == key)
    }
}

/// A collection of paths that were unexpected according to the schema used
/// while parsing the JSON for an OCPI object.
#[derive(Clone, Debug)]
pub struct UnexpectedFields<'buf>(Vec<PathNodeRef<'buf>>);

impl fmt::Display for UnexpectedFields<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        if f.alternate() {
            // Print each path on a newline.
            f.write_str("[\n")?;
            for entry in &self.0 {
                writeln!(f, "\t\"{entry}\",")?;
            }
            f.write_str("]\n")?;
        } else {
            // Print all paths on a single line.
            f.write_char('[')?;
            for entry in &self.0 {
                write!(f, "{entry},")?;
            }
            f.write_char(']')?;
        }

        Ok(())
    }
}

impl<'buf> UnexpectedFields<'buf> {
    /// Create an empty `UnexpectedFields` collection.
    pub(crate) fn empty() -> Self {
        Self(vec![])
    }

    /// Create a collection of `UnexpectedFields` from a `Vec`.
    pub(crate) fn from_vec(v: Vec<PathNodeRef<'buf>>) -> Self {
        Self(v)
    }

    /// Return the field paths as a `Vec` of `String`s.
    pub fn to_strings(&self) -> Vec<String> {
        self.0.iter().map(ToString::to_string).collect()
    }

    /// Return the field paths as a `Vec` of `String`s.
    pub fn into_strings(self) -> Vec<String> {
        self.0.into_iter().map(|path| path.to_string()).collect()
    }

    /// Return true if the list of unexpected fields is empty.
    pub fn is_empty(&self) -> bool {
        self.0.is_empty()
    }

    /// Return the number of unexpected fields.
    pub fn len(&self) -> usize {
        self.0.len()
    }

    /// Return an Iterator over the unexpected fields.
    pub fn iter<'a>(&'a self) -> UnexpectedFieldsIter<'a, 'buf> {
        UnexpectedFieldsIter(self.0.iter())
    }
}

impl<'buf> IntoIterator for UnexpectedFields<'buf> {
    type Item = PathRef<'buf>;

    type IntoIter = UnexpectedFieldsIntoIter<'buf>;

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

pub struct UnexpectedFieldsIntoIter<'buf>(std::vec::IntoIter<PathNodeRef<'buf>>);

impl<'buf> Iterator for UnexpectedFieldsIntoIter<'buf> {
    type Item = PathRef<'buf>;

    fn next(&mut self) -> Option<Self::Item> {
        let path_node = self.0.next()?;

        Some(PathRef(path_node))
    }
}

impl<'a, 'buf> IntoIterator for &'a UnexpectedFields<'buf> {
    type Item = PathRef<'buf>;

    type IntoIter = UnexpectedFieldsIter<'a, 'buf>;

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

/// An `Iterator` over each unexpected field as a `PathRef`.
pub struct UnexpectedFieldsIter<'a, 'buf>(std::slice::Iter<'a, PathNodeRef<'buf>>);

impl<'buf> Iterator for UnexpectedFieldsIter<'_, 'buf> {
    type Item = PathRef<'buf>;

    fn next(&mut self) -> Option<Self::Item> {
        let path_node = self.0.next()?;

        Some(PathRef(Arc::clone(path_node)))
    }
}

/// A path to a JSON `Element` where the path components are borrowed from the source JSON `&str`.
///
/// The Display impl outputs strings like:
///
/// - `$` The root is represented by a dollar.
/// - `$.object_key` Dots separate the path elements.
/// - `$.object_key.2` Arrays are represented as integers.
pub struct PathRef<'buf>(PathNodeRef<'buf>);

impl fmt::Debug for PathRef<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{self}")
    }
}

impl<'buf> PathRef<'buf> {
    /// Return an [`Iterator`] over the components of this path.
    pub fn components(&self) -> PathComponents<'buf> {
        PathComponents(PathIter::new(Arc::clone(&self.0)))
    }
}

/// An [`Iterator`] over the components of a path.
pub struct PathComponents<'buf>(PathIter<'buf>);

impl<'buf> Iterator for PathComponents<'buf> {
    type Item = PathComponent<'buf>;

    fn next(&mut self) -> Option<Self::Item> {
        let path_node = self.0.next()?;
        Some(PathComponent(path_node))
    }
}

/// The `PathRef` can be compared with other strings just like a `String`.
impl PartialEq<&str> for PathRef<'_> {
    fn eq(&self, other: &&str) -> bool {
        match_path_node(&self.0, other, |_| false)
    }
}

/// The `PathRef` can be compared with other strings just like a `String`.
impl PartialEq<String> for PathRef<'_> {
    fn eq(&self, other: &String) -> bool {
        match_path_node(&self.0, other, |_| false)
    }
}

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

/// The path to a JSON `Element`.
pub(crate) type PathNodeRef<'buf> = Arc<PathNode<'buf>>;

/// A single node of a complete path.
///
/// Path's are structured as a linked list from the leaf of the path back to the root through the parents.
///
/// The Display impl of the `Path` outputs strings like:
///
/// - `$` The root is represented by a dollar.
/// - `$.object_key` Dots separate the path elements.
/// - `$.object_key.2` Arrays are represented as integers.
#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub(crate) enum PathNode<'buf> {
    /// The root of the JSON `Element` tree.
    #[default]
    Root,
    /// An `Array` element referenced by index.
    Array {
        parent: PathNodeRef<'buf>,
        index: usize,
    },
    /// An `Object` field referenced be key value.
    Object {
        parent: PathNodeRef<'buf>,
        key: RawStr<'buf>,
    },
}

/// A lightweight enum used to indicate the kind of component being visited when using the
/// [`PathComponents`] [`Iterator`].
pub enum PathNodeKind {
    /// The root of the JSON `Element` tree.
    Root,
    /// An `Array` element referenced by index.
    Array,
    /// An `Object` field referenced be key value.
    Object,
}

/// A path to a JSON `Element` where the path components are heap allocated and so do not require
/// a lifetime back to the source JSON `&str`.
///
/// The Display impl outputs strings like:
///
/// - `$` The root is represented by a dollar.
/// - `$.object_key` Dots separate the path elements.
/// - `$.object_key.2` Arrays are represented as integers.
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct Path(Vec<PathPiece>);

impl Path {
    /// Create a root `Path`.
    const fn root() -> Self {
        Self(vec![])
    }

    /// Create an `Path` by iterating over a [`PathNode`].
    fn from_node(path: PathNodeRef<'_>) -> Self {
        let paths: Vec<_> = PathIter::new(path).collect();

        let pieces = paths
            .into_iter()
            .rev()
            .filter_map(|path_node| match *path_node {
                PathNode::Root => None,
                PathNode::Array { index, .. } => Some(PathPiece::Array(index)),
                PathNode::Object { key, .. } => Some(PathPiece::Object(key.to_string())),
            })
            .collect();

        Self(pieces)
    }
}

/// The `Path` can be compared with other strings just like a `String`.
impl PartialEq<&str> for Path {
    fn eq(&self, other: &&str) -> bool {
        match_path(self, other)
    }
}

/// The `Path` can be compared with other strings just like a `String`.
impl PartialEq<String> for Path {
    fn eq(&self, other: &String) -> bool {
        match_path(self, other)
    }
}

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

        write!(f, "$")?;

        for path in iter {
            write!(f, ".{path}")?;
        }

        Ok(())
    }
}

/// A piece/component of a [`Path`].
///
/// The `PathComponent` name is already taken and this type is private.
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
enum PathPiece {
    /// An `Array` element referenced by index.
    Array(usize),
    /// An `Object` field referenced be key value.
    Object(String),
}

impl fmt::Display for PathPiece {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            PathPiece::Array(index) => write!(f, "{index}"),
            PathPiece::Object(key) => write!(f, "{key}"),
        }
    }
}

/// A single component of a [`PathRef`].
pub struct PathComponent<'buf>(PathNodeRef<'buf>);

impl PathComponent<'_> {
    /// Return the kind of component this is.
    pub fn kind(&self) -> PathNodeKind {
        match *self.0 {
            PathNode::Root => PathNodeKind::Root,
            PathNode::Array { .. } => PathNodeKind::Array,
            PathNode::Object { .. } => PathNodeKind::Object,
        }
    }
}

impl fmt::Display for PathComponent<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match *self.0 {
            PathNode::Root => f.write_str(PATH_ROOT),
            PathNode::Array { index, .. } => write!(f, "{index}"),
            PathNode::Object { key, .. } => write!(f, "{key}"),
        }
    }
}

impl fmt::Display for PathNode<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let paths: Vec<_> = PathIter::new(Arc::new(self.clone())).collect();
        let mut iter = paths.into_iter().rev();

        if f.alternate() {
            // Print out each path element as a debugging tab-stop.
            for path in iter {
                match *path {
                    PathNode::Root => f.write_str("")?,
                    PathNode::Array { .. } | PathNode::Object { .. } => f.write_str("...|")?,
                }
            }
        } else {
            if let Some(path) = iter.next() {
                write!(f, "{}", PathComponent(path))?;
            }

            for path in iter {
                write!(f, ".{}", PathComponent(path))?;
            }
        }
        Ok(())
    }
}

impl<'buf> PathNode<'buf> {
    /// Returns true if the `Path` refers to the root.
    pub(crate) fn is_root(&self) -> bool {
        matches!(self, PathNode::Root)
    }

    /// Returns true if the `Path` refers to an `Array`.
    pub(crate) fn is_array(&self) -> bool {
        matches!(self, PathNode::Array { .. })
    }

    /// Return a key as `Some(&str)` if the `Path` refers to an `Object`.
    pub(crate) fn as_object_key(&self) -> Option<&RawStr<'buf>> {
        match self {
            PathNode::Object { key, .. } => Some(key),
            PathNode::Root | PathNode::Array { .. } => None,
        }
    }
}

/// Return true if the given `PathNode` matches the given `&str`.
///
/// If the `skip` function returns true, that path component is skipped.
/// The `skip` function allows this single function to implement comparisons between `PathNode` and `&str`.
/// And comparisons between `PathNode` and `PathGlob`.
fn match_path_node<F>(path: &PathNode<'_>, s: &str, mut skip: F) -> bool
where
    F: FnMut(&str) -> bool,
{
    let mut parts = s.rsplit(PATH_SEPARATOR);
    let mut paths_iter = PathIter::new(Arc::new(path.clone()));

    loop {
        let node_segment = paths_iter.next();
        let str_segment = parts.next();

        let (node_segment, str_segment) = match (node_segment, str_segment) {
            // If we have exhausted both iterators then the `&str` is equal to the path.
            (None, None) => return true,
            // If either of the iterators are a different size, then they don't match.
            (None, Some(_)) | (Some(_), None) => return false,
            // If both iterators have another item, continue on to try match them.
            (Some(a), Some(b)) => (a, b),
        };

        // If the skip function says to skip a path segment, then continue to the next segment.
        if skip(str_segment) {
            continue;
        }

        let yip = match *node_segment {
            PathNode::Root => str_segment == PATH_ROOT,
            PathNode::Array { index, .. } => {
                let Ok(b) = str_segment.parse::<usize>() else {
                    return false;
                };

                index == b
            }
            PathNode::Object { key, .. } => key.as_raw() == str_segment,
        };

        // Return false on the first mismatch.
        if !yip {
            return false;
        }
    }
}

/// Return true if the given `Path` matches the given `&str`.
fn match_path(path: &Path, s: &str) -> bool {
    let mut parts = s.split(PATH_SEPARATOR);
    let mut paths_iter = path.0.iter();

    let Some(str_segment) = parts.next() else {
        return false;
    };

    // The root path segment is not explicitly stored in a `Path` so we just match the first
    // `str` segment to the expected `$` nomenclature.
    if str_segment != PATH_ROOT {
        return false;
    }

    loop {
        let node_segment = paths_iter.next();
        let str_segment = parts.next();

        let (node_segment, str_segment) = match (node_segment, str_segment) {
            // If we have exhausted both iterators then the `&str` is equal to the path.
            (None, None) => return true,
            // If either of the iterators are a different size, then they don't match.
            (None, Some(_)) | (Some(_), None) => return false,
            // If both iterators have another item, continue on to try match them.
            (Some(a), Some(b)) => (a, b),
        };

        let yip = match node_segment {
            PathPiece::Array(index) => {
                let Ok(b) = str_segment.parse::<usize>() else {
                    return false;
                };

                *index == b
            }
            PathPiece::Object(key) => key == str_segment,
        };

        // Return false on the first mismatch.
        if !yip {
            return false;
        }
    }
}

impl PartialEq<&str> for PathNode<'_> {
    fn eq(&self, other: &&str) -> bool {
        match_path_node(self, other, |_| false)
    }
}

impl PartialEq<String> for PathNode<'_> {
    fn eq(&self, other: &String) -> bool {
        match_path_node(self, other, |_| false)
    }
}

/// Traverse a `Path` from the leaf to the root.
struct PathIter<'buf> {
    /// The root has been reached.
    complete: bool,
    /// The current path node to introspect when `Iterator::next` is called.
    path: PathNodeRef<'buf>,
}

impl<'buf> PathIter<'buf> {
    /// Create a new `PathIter` from a leaf node.
    fn new(path: PathNodeRef<'buf>) -> Self {
        Self {
            complete: false,
            path,
        }
    }
}

impl<'buf> Iterator for PathIter<'buf> {
    type Item = PathNodeRef<'buf>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.complete {
            return None;
        }

        match &*self.path {
            PathNode::Root => {
                // The iteration is complete once we've arrived at the root node.
                self.complete = true;
                Some(Arc::clone(&self.path))
            }
            PathNode::Array { parent, .. } | PathNode::Object { parent, .. } => {
                let next = Arc::clone(&self.path);
                self.path = Arc::clone(parent);
                Some(next)
            }
        }
    }
}

/// Display the expectation stack for debugging.
struct DisplayExpectStack<'a>(&'a [schema::Expect]);

impl fmt::Display for DisplayExpectStack<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let mut iter = self.0.iter().rev();
        let last = iter.next();

        // Use the `~` to represent a schema stack.
        f.write_str("~")?;

        for _ in iter {
            f.write_str("...~")?;
        }

        if let Some(exp) = last {
            match exp {
                schema::Expect::Scalar => f.write_str("~")?,
                schema::Expect::Array(element) => match &**element {
                    schema::Element::Scalar => f.write_str("~")?,
                    schema::Element::Array(element) => write!(f, "[{element:?}]")?,
                    schema::Element::Object(fields) => {
                        write!(f, "[{{{:}}}])", DisplayExpectFields(&**fields))?;
                    }
                },
                schema::Expect::Object(fields) => {
                    write!(f, "{{{:}}}", DisplayExpectFields(&**fields))?;
                }
                schema::Expect::UnmatchedScalar => write!(f, "unmatched(scalar)")?,
                schema::Expect::UnmatchedArray => write!(f, "unmatched(array)")?,
                schema::Expect::UnmatchedObject => write!(f, "unmatched(object)")?,
                schema::Expect::OutOfSchema => write!(f, "no_schema")?,
            }
        }

        Ok(())
    }
}

/// Display the fields of a schema expect stack level.
struct DisplayExpectFields<'a, V>(&'a BTreeMap<&'a str, V>);

impl<V> fmt::Display for DisplayExpectFields<'_, V> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        const MAX_FIELDS: usize = 8;

        let mut count = 0;
        let mut iter = self.0.keys().peekable();

        loop {
            if count >= MAX_FIELDS {
                f.write_str("...")?;
                break;
            }

            let Some(field) = iter.next() else {
                break;
            };

            let Some(n) = count.checked_add(1) else {
                break;
            };
            count = n;

            write!(f, "{field}")?;

            let Some(_) = iter.peek() else {
                break;
            };

            f.write_str(", ")?;
        }

        Ok(())
    }
}

#[derive(Debug)]
struct UnbalancedExpectStack;

impl fmt::Display for UnbalancedExpectStack {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("unbalanced expectation stack")
    }
}

impl std::error::Error for UnbalancedExpectStack {}

/// Parse the JSON into a [`ParseReport`] checking the parsed [`Element`]s names against the given
/// [`schema`] and reporting fields that are unexpected in the [`ParseReport::unexpected_fields`].
#[expect(
    clippy::cognitive_complexity,
    reason = "Lifetimes make splitting this function more effort than it's worth"
)]
pub(crate) fn parse_with_schema<'buf>(
    json: ReasonableStr<'buf>,
    schema: &schema::Element,
) -> Result<ParseReport<'buf>, Error> {
    let parser = Parser::new(json.into_inner());
    let mut unexpected_fields = vec![];
    // The current node of the schema is the last element of this `Vec`.
    let mut expectation_stack = vec![schema.to_expectation()];

    for event in parser {
        match event? {
            parser::Event::Open { kind, parent_path } => {
                // Take the schema expectation off the stack.
                // This is simpler than taking a `&mut` to the last element.
                let Some(expectation) = expectation_stack.pop() else {
                    return Err(ErrorKind::Internal(Box::new(UnbalancedExpectStack))
                        .into_partial_error_without_token()
                        .with_root_path());
                };

                if tracing::enabled!(Level::DEBUG) {
                    match kind {
                        ObjectKind::Array => {
                            trace!("{parent_path} [ {}", DisplayExpectStack(&expectation_stack));
                        }
                        ObjectKind::Object => trace!(
                            "{parent_path} {{ {}",
                            DisplayExpectStack(&expectation_stack)
                        ),
                    }
                }

                match expectation {
                    schema::Expect::Array(elem) => {
                        // If the opening element is at the root we only care if the element
                        // is an array or not.
                        if parent_path.is_root() {
                            let next = match kind {
                                ObjectKind::Array => schema::Expect::Array(elem),
                                ObjectKind::Object => schema::Expect::UnmatchedArray,
                            };

                            expectation_stack.push(next);
                            trace!("{}", DisplayExpectStack(&expectation_stack));
                            continue;
                        }

                        if !parent_path.is_array() {
                            expectation_stack.push(schema::Expect::UnmatchedArray);
                            trace!("{}", DisplayExpectStack(&expectation_stack));
                            continue;
                        }

                        expectation_stack.push(schema::Expect::Array(Arc::clone(&elem)));
                        // Each array element should match this expectation
                        expectation_stack.push(elem.to_expectation());
                    }
                    schema::Expect::Object(fields) => {
                        // If the opening element is at the root there is no path to inspect.
                        // We only care if the element is an object or not.
                        if parent_path.is_root() {
                            let next = match kind {
                                ObjectKind::Array => schema::Expect::UnmatchedObject,
                                ObjectKind::Object => schema::Expect::Object(fields),
                            };

                            expectation_stack.push(next);
                            trace!("{}", DisplayExpectStack(&expectation_stack));
                            continue;
                        }
                        let Some(key) = parent_path.as_object_key() else {
                            expectation_stack.push(schema::Expect::UnmatchedObject);
                            trace!("{}", DisplayExpectStack(&expectation_stack));
                            continue;
                        };

                        let next = if let Some(elem) = fields.get(key.as_raw()) {
                            open_object(kind, elem.as_ref())
                        } else {
                            unexpected_fields.push(parent_path);
                            schema::Expect::OutOfSchema
                        };

                        expectation_stack.push(schema::Expect::Object(fields));
                        expectation_stack.push(next);
                    }
                    schema::Expect::OutOfSchema => {
                        // If we're already outside of the schema we put that back on the stack
                        // and add a new one for the object that just opened.
                        //
                        // We need to track the object level even though the schema expectations
                        // have been exhausted, as we'll pop these placeholder `OutOfSchema`s
                        // off the stack when the object has closed so we land on the correct
                        // schema again.
                        expectation_stack.push(expectation);
                        expectation_stack.push(schema::Expect::OutOfSchema);
                    }
                    schema::Expect::UnmatchedArray | schema::Expect::UnmatchedObject => {
                        expectation_stack.push(expectation);
                        expectation_stack.push(schema::Expect::OutOfSchema);
                    }
                    schema::Expect::Scalar | schema::Expect::UnmatchedScalar => {
                        expectation_stack.push(expectation);
                    }
                }

                trace!("{}", DisplayExpectStack(&expectation_stack));
            }
            parser::Event::Element { kind, parent_path } => {
                // Take the schema expectation off the stack.
                // This is simpler than taking a `&mut` to the last element.
                let Some(expectation) = expectation_stack.pop() else {
                    return Err(ErrorKind::Internal(Box::new(UnbalancedExpectStack))
                        .into_partial_error_without_token()
                        .with_root_path());
                };

                // An `Element` of kind `Array` or `Object` means the `Element` is closed
                // and has completed construction. The expectation can remain off the stack.
                if let ValueKind::Array | ValueKind::Object = kind {
                    if tracing::enabled!(Level::DEBUG) {
                        match kind {
                            ValueKind::Array => {
                                trace!(
                                    "{parent_path} ] {}",
                                    DisplayExpectStack(&expectation_stack)
                                );
                            }
                            ValueKind::Object => trace!(
                                "{parent_path} }} {}",
                                DisplayExpectStack(&expectation_stack)
                            ),
                            ValueKind::Null
                            | ValueKind::Bool
                            | ValueKind::Number
                            | ValueKind::String => (),
                        }
                    }
                    continue;
                }

                match expectation {
                    schema::Expect::Object(fields) => match &*parent_path {
                        PathNode::Root => unreachable!("The parser only emits an `Event::Complete` for a scalar object at the root"),
                        PathNode::Array { .. } => {
                            expectation_stack.push(schema::Expect::UnmatchedObject);
                        }
                        PathNode::Object { parent, key } => {
                            trace!("{parent:#}.{key}");

                            if !fields.contains_key(key.as_raw()) {
                                unexpected_fields.push(parent_path);
                            }

                            expectation_stack.push(schema::Expect::Object(fields));
                        }
                    },
                    schema::Expect::OutOfSchema => {
                        unexpected_fields.push(parent_path);
                        expectation_stack.push(expectation);
                    }
                    schema::Expect::UnmatchedArray | schema::Expect::Array(_) | schema::Expect::Scalar | schema::Expect::UnmatchedScalar | schema::Expect::UnmatchedObject => {
                        expectation_stack.push(expectation);
                    }
                }
            }
            parser::Event::Complete(element) => {
                if element.value().is_scalar() {
                    unexpected_fields.push(element.path_node());
                }

                // Parsing the JSON is complete.
                // Return the `Element` and the unexpected fields collected during parsing
                return Ok(ParseReport {
                    element,
                    unexpected_fields: UnexpectedFields::from_vec(unexpected_fields),
                });
            }
        }
    }

    Err(ErrorKind::UnexpectedEOF
        .into_partial_error_without_token()
        .with_root_path())
}

fn open_object(kind: ObjectKind, elem: Option<&Arc<schema::Element>>) -> schema::Expect {
    let Some(schema) = elem else {
        return schema::Expect::OutOfSchema;
    };

    match (kind, &**schema) {
        (ObjectKind::Object | ObjectKind::Array, schema::Element::Scalar) => {
            schema::Expect::UnmatchedScalar
        }
        (ObjectKind::Object, schema::Element::Array(_)) => schema::Expect::UnmatchedArray,
        (ObjectKind::Object, schema::Element::Object(fields)) => {
            schema::Expect::Object(Arc::clone(fields))
        }
        (ObjectKind::Array, schema::Element::Array(element)) => {
            schema::Expect::Array(Arc::clone(element))
        }
        (ObjectKind::Array, schema::Element::Object(_)) => schema::Expect::UnmatchedObject,
    }
}

/// The output of the `parse_with_schema` function where the parsed JSON `Element` is returned
/// along with a list of fields that the schema did not define.
#[derive(Debug)]
pub(crate) struct ParseReport<'buf> {
    /// The root JSON [`Element`].
    pub element: Element<'buf>,

    /// A list of fields that were not expected: The schema did not define them.
    pub unexpected_fields: UnexpectedFields<'buf>,
}
