use std::{fmt, sync::Arc};
use serde::{de, de::Error};
use crate::{
    metadata::{ConfigMetadata, ParamMetadata},
    value::{ValueOrigin, WithOrigin},
};
#[derive(Debug)]
pub struct DeserializeConfigError(());
impl DeserializeConfigError {
    pub(crate) fn new() -> Self {
        Self(())
    }
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum LocationInConfig {
    Param(usize),
}
#[doc(hidden)] #[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum ParseErrorCategory {
    Generic,
    MissingField,
}
#[derive(Debug)]
#[non_exhaustive]
pub enum LowLevelError {
    Json {
        err: serde_json::Error,
        category: ParseErrorCategory,
    },
    #[doc(hidden)] InvalidArray,
    #[doc(hidden)] InvalidObject,
    #[doc(hidden)] Validation,
}
impl From<serde_json::Error> for LowLevelError {
    fn from(err: serde_json::Error) -> Self {
        Self::Json {
            err,
            category: ParseErrorCategory::Generic,
        }
    }
}
impl fmt::Display for LowLevelError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Json { err, .. } => fmt::Display::fmt(err, formatter),
            Self::InvalidArray => formatter.write_str("error(s) deserializing array items"),
            Self::InvalidObject => formatter.write_str("error(s) deserializing object entries"),
            Self::Validation => formatter.write_str("validation failed"),
        }
    }
}
pub type ErrorWithOrigin = WithOrigin<LowLevelError>;
impl ErrorWithOrigin {
    pub(crate) fn json(err: serde_json::Error, origin: Arc<ValueOrigin>) -> Self {
        Self::new(err.into(), origin)
    }
    pub fn custom(message: impl fmt::Display) -> Self {
        Self::json(de::Error::custom(message), Arc::default())
    }
}
impl de::Error for ErrorWithOrigin {
    fn custom<T: fmt::Display>(msg: T) -> Self {
        Self::json(de::Error::custom(msg), Arc::default())
    }
    fn missing_field(field: &'static str) -> Self {
        let err = LowLevelError::Json {
            err: de::Error::missing_field(field),
            category: ParseErrorCategory::MissingField,
        };
        Self::new(err, Arc::default())
    }
}
impl fmt::Display for ErrorWithOrigin {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(formatter, "[{}]: {}", self.origin, self.inner)
    }
}
impl std::error::Error for ErrorWithOrigin {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match &self.inner {
            LowLevelError::Json { err, .. } => Some(err),
            LowLevelError::InvalidArray
            | LowLevelError::InvalidObject
            | LowLevelError::Validation => None,
        }
    }
}
pub struct ParseError {
    pub(crate) inner: serde_json::Error,
    pub(crate) category: ParseErrorCategory,
    pub(crate) path: String,
    pub(crate) origin: Arc<ValueOrigin>,
    pub(crate) config: &'static ConfigMetadata,
    pub(crate) location_in_config: Option<LocationInConfig>,
    pub(crate) validation: Option<String>,
}
impl fmt::Debug for ParseError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("ParseError")
            .field("inner", &self.inner)
            .field("origin", &self.origin)
            .field("path", &self.path)
            .field("config.ty", &self.config.ty)
            .field("location_in_config", &self.location_in_config)
            .field("validation", &self.validation)
            .finish_non_exhaustive()
    }
}
impl fmt::Display for ParseError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        let field = self.location_in_config.and_then(|location| {
            Some(match location {
                LocationInConfig::Param(idx) => {
                    let param = self.config.params.get(idx)?;
                    format!("param `{}` in ", param.name)
                }
            })
        });
        let field = field.as_deref().unwrap_or("");
        let origin = if matches!(self.origin(), ValueOrigin::Unknown) {
            String::new()
        } else {
            format!(" [origin: {}]", self.origin)
        };
        let failed_action = if let Some(validation) = &self.validation {
            format!("validating '{validation}' for")
        } else {
            "parsing".to_owned()
        };
        write!(
            formatter,
            "error {failed_action} {field}`{config}` at `{path}`{origin}: {err}",
            err = self.inner,
            config = self.config.ty.name_in_code(),
            path = self.path
        )
    }
}
impl std::error::Error for ParseError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&self.inner)
    }
}
impl ParseError {
    pub(crate) fn generic(path: String, config: &'static ConfigMetadata) -> Self {
        Self {
            inner: serde_json::Error::custom("unspecified error deserializing configuration"),
            category: ParseErrorCategory::Generic,
            path,
            origin: Arc::default(),
            config,
            location_in_config: None,
            validation: None,
        }
    }
    pub fn inner(&self) -> &serde_json::Error {
        &self.inner
    }
    #[doc(hidden)]
    pub fn category(&self) -> ParseErrorCategory {
        self.category
    }
    pub fn path(&self) -> &str {
        &self.path
    }
    pub fn origin(&self) -> &ValueOrigin {
        &self.origin
    }
    pub fn validation(&self) -> Option<&str> {
        self.validation.as_deref()
    }
    pub fn config(&self) -> &'static ConfigMetadata {
        self.config
    }
    pub fn param(&self) -> Option<&'static ParamMetadata> {
        let LocationInConfig::Param(idx) = self.location_in_config?;
        self.config.params.get(idx)
    }
}
#[derive(Debug, Default)]
pub struct ParseErrors {
    errors: Vec<ParseError>,
}
impl ParseErrors {
    pub(crate) fn push(&mut self, err: ParseError) {
        self.errors.push(err);
    }
    pub fn iter(&self) -> impl Iterator<Item = &ParseError> + '_ {
        self.errors.iter()
    }
    #[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize {
        self.errors.len()
    }
    #[allow(clippy::missing_panics_doc)] pub fn first(&self) -> &ParseError {
        self.errors.first().expect("no errors")
    }
    pub(crate) fn truncate(&mut self, len: usize) {
        self.errors.truncate(len);
    }
}
impl IntoIterator for ParseErrors {
    type Item = ParseError;
    type IntoIter = std::vec::IntoIter<ParseError>;
    fn into_iter(self) -> Self::IntoIter {
        self.errors.into_iter()
    }
}
impl fmt::Display for ParseErrors {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        for err in &self.errors {
            writeln!(formatter, "{err}")?;
        }
        Ok(())
    }
}
impl std::error::Error for ParseErrors {}
impl FromIterator<ParseError> for Result<(), ParseErrors> {
    fn from_iter<I: IntoIterator<Item = ParseError>>(iter: I) -> Self {
        let errors: Vec<_> = iter.into_iter().collect();
        if errors.is_empty() {
            Ok(())
        } else {
            Err(ParseErrors { errors })
        }
    }
}