use std::any;
use serde::de::Error as DeError;
use self::deserializer::ValueDeserializer;
pub use self::{
    deserializer::DeserializerOptions,
    macros::Serde,
    param::{
        CustomKnownOption, DeserializeParam, Optional, OrString, Qualified, Serde, WellKnown,
        WellKnownOption, WithDefault,
    },
    repeated::{Delimited, Entries, NamedEntries, Repeated, ToEntries},
    secret::{FromSecretString, Secret},
    units::WithUnit,
};
use crate::{
    error::{ErrorWithOrigin, LocationInConfig, LowLevelError},
    metadata::{BasicTypes, ConfigMetadata, ParamMetadata},
    value::{Pointer, StrValue, Value, ValueOrigin, WithOrigin},
    DescribeConfig, DeserializeConfigError, ParseError, ParseErrorCategory, ParseErrors,
};
#[doc(hidden)]
pub mod _private;
mod deserializer;
mod macros;
mod param;
#[cfg(feature = "primitive-types")]
mod primitive_types_impl;
mod repeated;
mod secret;
#[cfg(test)]
mod tests;
mod units;
#[derive(Debug)]
pub struct DeserializeContext<'a> {
    de_options: &'a DeserializerOptions,
    root_value: &'a WithOrigin,
    path: String,
    patched_current_value: Option<&'a WithOrigin>,
    current_config: &'static ConfigMetadata,
    location_in_config: Option<LocationInConfig>,
    errors: &'a mut ParseErrors,
}
impl<'a> DeserializeContext<'a> {
    pub(crate) fn new(
        de_options: &'a DeserializerOptions,
        root_value: &'a WithOrigin,
        path: String,
        current_config: &'static ConfigMetadata,
        errors: &'a mut ParseErrors,
    ) -> Self {
        Self {
            de_options,
            root_value,
            path,
            patched_current_value: None,
            current_config,
            location_in_config: None,
            errors,
        }
    }
    fn child(
        &mut self,
        path: &str,
        location_in_config: Option<LocationInConfig>,
    ) -> DeserializeContext<'_> {
        DeserializeContext {
            de_options: self.de_options,
            root_value: self.root_value,
            path: Pointer(&self.path).join(path),
            patched_current_value: self.patched_current_value.and_then(|val| {
                if path.is_empty() {
                    Some(val)
                } else if let Value::Object(object) = &val.inner {
                    object.get(path)
                } else {
                    None
                }
            }),
            current_config: self.current_config,
            location_in_config,
            errors: self.errors,
        }
    }
    pub fn borrow(&mut self) -> DeserializeContext<'_> {
        DeserializeContext {
            de_options: self.de_options,
            root_value: self.root_value,
            path: self.path.clone(),
            patched_current_value: self.patched_current_value,
            current_config: self.current_config,
            location_in_config: self.location_in_config,
            errors: self.errors,
        }
    }
    fn patched<'s>(&'s mut self, current_value: &'s WithOrigin) -> DeserializeContext<'s> {
        DeserializeContext {
            de_options: self.de_options,
            root_value: self.root_value,
            path: self.path.clone(),
            patched_current_value: Some(current_value),
            current_config: self.current_config,
            location_in_config: self.location_in_config,
            errors: self.errors,
        }
    }
    pub(crate) fn current_value(&self) -> Option<&'a WithOrigin> {
        self.patched_current_value
            .or_else(|| self.root_value.get(Pointer(&self.path)))
    }
    pub fn current_value_deserializer(
        &self,
        name: &'static str,
    ) -> Result<ValueDeserializer<'a>, ErrorWithOrigin> {
        if let Some(value) = self.current_value() {
            Ok(ValueDeserializer::new(value, self.de_options))
        } else {
            Err(DeError::missing_field(name))
        }
    }
    fn for_nested_config(&mut self, index: usize) -> DeserializeContext<'_> {
        let nested_meta = self.current_config.nested_configs.get(index).unwrap_or_else(|| {
            panic!("Internal error: called `for_nested_config()` with missing config index {index}")
        });
        let path = nested_meta.name;
        DeserializeContext {
            current_config: nested_meta.meta,
            ..self.child(path, None)
        }
    }
    fn for_param(&mut self, index: usize) -> (DeserializeContext<'_>, &'static ParamMetadata) {
        let param = self.current_config.params.get(index).unwrap_or_else(|| {
            panic!("Internal error: called `for_param()` with missing param index {index}")
        });
        (
            self.child(param.name, Some(LocationInConfig::Param(index))),
            param,
        )
    }
    pub fn push_error(&mut self, err: ErrorWithOrigin) {
        self.push_generic_error(err, None);
    }
    #[cold]
    fn push_generic_error(&mut self, err: ErrorWithOrigin, validation: Option<String>) {
        let (inner, category) = match err.inner {
            LowLevelError::Json { err, category } => (err, category),
            LowLevelError::InvalidArray
            | LowLevelError::InvalidObject
            | LowLevelError::Validation => return,
        };
        let mut origin = err.origin;
        if matches!(origin.as_ref(), ValueOrigin::Unknown) {
            if let Some(val) = self.current_value() {
                origin = val.origin.clone();
            }
        }
        self.errors.push(ParseError {
            inner,
            category,
            path: self.path.clone(),
            origin,
            config: self.current_config,
            location_in_config: self.location_in_config,
            validation,
        });
    }
    #[tracing::instrument(
        level = "trace",
        skip_all,
        fields(path = self.path, config = ?self.current_config.ty)
    )]
    pub(crate) fn deserialize_any_config(
        mut self,
    ) -> Result<Box<dyn any::Any>, DeserializeConfigError> {
        if let Some(val) = self.current_value() {
            if !matches!(&val.inner, Value::Object(_)) {
                self.push_error(val.invalid_type("config object"));
                return Err(DeserializeConfigError::new());
            }
        }
        let config = (self.current_config.deserializer)(self.borrow())?;
        let mut has_errors = false;
        for &validation in self.current_config.validations {
            let _span = tracing::trace_span!("validation", %validation).entered();
            if let Err(err) = validation.validate(config.as_ref()) {
                tracing::info!(%validation, origin = %err.origin, "config validation failed: {}", err.inner);
                self.push_generic_error(err, Some(validation.to_string()));
                has_errors = true;
            }
        }
        if has_errors {
            Err(DeserializeConfigError::new())
        } else {
            Ok(config)
        }
    }
    pub(crate) fn deserialize_config<C: 'static>(self) -> Result<C, DeserializeConfigError> {
        Ok(*self
            .deserialize_any_config()?
            .downcast::<C>()
            .expect("Internal error: config deserializer output has wrong type"))
    }
    pub(crate) fn deserialize_any_config_opt(
        mut self,
    ) -> Result<Option<Box<dyn any::Any>>, DeserializeConfigError> {
        if self.current_value().is_none() {
            return Ok(None);
        }
        let error_count = self.errors.len();
        self.borrow()
            .deserialize_any_config()
            .map(Some)
            .or_else(|err| {
                let only_missing_field_errors = self
                    .errors
                    .iter()
                    .skip(error_count)
                    .all(|err| matches!(err.category, ParseErrorCategory::MissingField));
                if only_missing_field_errors {
                    tracing::trace!(
                        "optional config misses required params and no other errors; coercing it to `None`"
                    );
                    self.errors.truncate(error_count);
                    Ok(None)
                } else {
                    Err(err)
                }
            })
    }
    pub(crate) fn deserialize_config_opt<C: 'static>(
        self,
    ) -> Result<Option<C>, DeserializeConfigError> {
        let config = self.deserialize_any_config_opt()?.map(|boxed| {
            *boxed
                .downcast::<C>()
                .expect("Internal error: config deserializer output has wrong type")
        });
        Ok(config)
    }
}
#[doc(hidden)]
impl DeserializeContext<'_> {
    pub fn deserialize_nested_config<C: DeserializeConfig>(
        &mut self,
        index: usize,
        default_fn: Option<fn() -> C>,
    ) -> Result<C, DeserializeConfigError> {
        let child_ctx = self.for_nested_config(index);
        if child_ctx.current_value().is_none() {
            if let Some(default) = default_fn {
                return Ok(default());
            }
        }
        child_ctx.deserialize_config()
    }
    pub fn deserialize_nested_config_opt<C: DeserializeConfig>(
        &mut self,
        index: usize,
    ) -> Result<Option<C>, DeserializeConfigError> {
        self.for_nested_config(index).deserialize_config_opt()
    }
    #[tracing::instrument(
        level = "trace",
        name = "deserialize_param",
        skip_all,
        fields(path = self.path, config = ?self.current_config.ty, param)
    )]
    pub(crate) fn deserialize_any_param(
        &mut self,
        index: usize,
    ) -> Result<Box<dyn any::Any>, DeserializeConfigError> {
        let (mut child_ctx, param) = self.for_param(index);
        tracing::Span::current().record("param", param.rust_field_name);
        let maybe_coerced = child_ctx
            .current_value()
            .and_then(|val| val.coerce_value_type(param.expecting));
        let mut child_ctx = if let Some(coerced) = &maybe_coerced {
            child_ctx.patched(coerced)
        } else {
            child_ctx
        };
        tracing::trace!(
            deserializer = ?param.deserializer,
            value = ?child_ctx.current_value(),
            "deserializing param"
        );
        match param
            .deserializer
            .deserialize_param(child_ctx.borrow(), param)
        {
            Ok(param) => Ok(param),
            Err(err) => {
                tracing::info!(origin = %err.origin, "deserialization failed: {}", err.inner);
                child_ctx.push_error(err);
                Err(DeserializeConfigError::new())
            }
        }
    }
    pub fn deserialize_param<T: 'static>(
        &mut self,
        index: usize,
    ) -> Result<T, DeserializeConfigError> {
        self.deserialize_any_param(index).map(|val| {
            *val.downcast()
                .expect("Internal error: deserializer output has wrong type")
        })
    }
}
impl WithOrigin {
    #[tracing::instrument(level = "trace", skip(self))]
    fn coerce_value_type(&self, expecting: BasicTypes) -> Option<Self> {
        let Value::String(StrValue::Plain(str)) = &self.inner else {
            return None; };
        if !expecting.contains(BasicTypes::STRING) && (str.is_empty() || str == "null") {
            return Some(Self::new(Value::Null, self.origin.clone()));
        }
        match expecting {
            BasicTypes::BOOL => match str.parse::<bool>() {
                Ok(bool_value) => {
                    return Some(Self::new(bool_value.into(), self.origin.clone()));
                }
                Err(err) => {
                    tracing::info!(%expecting, "failed coercing value: {err}");
                }
            },
            BasicTypes::INTEGER | BasicTypes::FLOAT => match str.parse::<serde_json::Number>() {
                Ok(number) => {
                    return Some(Self::new(number.into(), self.origin.clone()));
                }
                Err(err) => {
                    tracing::info!(%expecting, "failed coercing value: {err}");
                }
            },
            _ => { }
        }
        None
    }
}
pub trait DeserializeConfig: DescribeConfig + Sized {
    fn deserialize_config(ctx: DeserializeContext<'_>) -> Result<Self, DeserializeConfigError>;
}