use std::fmt;

use super::{
    Component, Components, ElemId, Element, Field, FieldsAsExt as _, Location, Path, PathSet,
};

impl<'buf> Element<'buf> {
    pub(crate) fn find_field(&self, key: &str) -> Option<&Field<'buf>> {
        let fields = self.as_object_fields()?;
        fields.find_field(key)
    }
}

impl Field<'_> {
    pub(crate) fn id(&self) -> ElemId {
        self.element.id()
    }
}

/// The tests need to assert against literal `ElemId`s.
impl From<usize> for ElemId {
    fn from(value: usize) -> Self {
        Self(value)
    }
}

/// A `Display` object that writes out the JSON source and highlights the given line.
pub struct LineHighlighter {
    json: String,
    pos: Location,
}

impl LineHighlighter {
    pub fn from_value(json: &serde_json::Value, pos: Location) -> Self {
        let json = serde_json::to_string_pretty(json).unwrap();
        Self { json, pos }
    }
}

impl fmt::Display for LineHighlighter {
    #[expect(
        clippy::arithmetic_side_effects,
        reason = "line count cannot realistically overflow usize"
    )]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let Ok(target_line) = usize::try_from(self.pos.line) else {
            return Ok(());
        };

        for (index, line) in self.json.lines().enumerate() {
            let line_no = index + 1;
            if index == target_line {
                writeln!(f, "{line_no:3}| {line}")?;
            } else {
                writeln!(f, "   | {line}")?;
            }
        }

        Ok(())
    }
}

impl PathSet<'_> {
    /// Filter off the fields that match the glob.
    pub(crate) fn filter_matches(&mut self, glob: &PathGlob) {
        self.0.retain(|path| !glob.matches(path));
    }
}

/// A string based `Path` that can contain glob `*` patterns in place of a literal path element.
/// The glob means that the path section can be any valid section.
#[derive(Debug)]
pub(crate) struct PathGlob(String);

impl PathGlob {
    /// Return true if this `PathGlob` matches the given path string.
    ///
    /// The glob and path are compared component by component (see
    /// [`Path::components`]). A wildcard glob component, written `.*` and parsed
    /// as `Component::Member("*")`, matches any single path component, whether a
    /// member or an index; all other components must match exactly. The number
    /// of components must be equal.
    pub(crate) fn matches(&self, path: &Path) -> bool {
        let mut glob = Components::over(&self.0);
        let mut actual = path.components();

        loop {
            match (glob.next(), actual.next()) {
                (Some(glob_component), Some(component)) => {
                    let is_wildcard = matches!(glob_component, Component::Member("*"));
                    if !is_wildcard && glob_component != component {
                        return false;
                    }
                }
                // Both ran out at the same time: every component matched.
                (None, None) => return true,
                // The component counts differ.
                (None, Some(_)) | (Some(_), None) => return false,
            }
        }
    }
}

impl<'a> From<&'a str> for PathGlob {
    fn from(s: &'a str) -> Self {
        Self(s.into())
    }
}

impl From<String> for PathGlob {
    fn from(s: String) -> Self {
        Self(s)
    }
}

impl<'de> serde::Deserialize<'de> for PathGlob {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: ::serde::Deserializer<'de>,
    {
        let s = <String as ::serde::Deserialize>::deserialize(deserializer)?;
        Ok(Self(s))
    }
}
