#[cfg(feature = "no_std")]
use crate::prelude::*;
use core::fmt;
#[cfg(not(feature = "no_std"))]
use std::io;

/// The category of a serialization failure.
///
/// `SerializeErrorKind` is intentionally structured (instead of a free-form `String`) so callers
/// can reliably distinguish conditions such as *invalid values* vs *depth limits*,
/// and implement robust error handling.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SerializeErrorKind {
    /// An I/O error occurred while writing output.
    ///
    /// This wraps the `io::ErrorKind` since `io::Error` is not Clone.
    /// The original error message is preserved in the `SerializeError.context` field.
    ///
    // TODO(0.3.0): the BinWriter-based serializer pipeline no longer surfaces
    // `io::Error` internally, so this variant is unreachable from the
    // first-party serializers. Kept for backwards compatibility with
    // downstream `match` arms compiled against 0.2.7. Plan to remove in 0.3.0.
    #[cfg(not(feature = "no_std"))]
    IoError(io::ErrorKind),

    /// The Value contains data that cannot be serialized in the target format.
    ///
    /// Examples:
    /// - NaN or Infinity in JSON (not allowed by spec)
    /// - Keys that are not strings in JSON objects
    /// - Unsupported Value variants for specific formats
    InvalidValue(&'static str),

    /// The Value structure exceeds the configured maximum nesting depth.
    ///
    /// This is a security measure to prevent stack overflow from malicious
    /// or accidentally deeply-nested data structures.
    DepthLimit,

    /// The output buffer or writer is full and cannot accept more data.
    ///
    /// This is rare but can occur with fixed-size buffers or quota-limited writers.
    BufferFull,

    /// A generic static message for implementation-specific errors that do not fit other kinds.
    ///
    /// Prefer more specific variants when possible.
    Message(&'static str),
}

/// A structured serialization error with optional context.
///
/// Unlike `ParseError` which tracks position in input, `SerializeError` focuses on:
/// - What kind of error occurred
/// - Optional context (like the problematic value or field name)
#[derive(Debug, Clone)]
pub struct SerializeError {
    /// The structured error category.
    pub kind: SerializeErrorKind,

    /// Optional short, human-readable context.
    ///
    /// Examples:
    /// - Field name where error occurred: `"field 'user.age'"`
    /// - Value that caused error: `"NaN"`
    /// - Original I/O error message
    pub context: Option<String>,
}

impl SerializeError {
    /// Create a new `SerializeError` with the given kind.
    pub fn new(kind: SerializeErrorKind) -> Self {
        Self {
            kind,
            context: None,
        }
    }

    /// Attach short human-readable context to the error.
    pub fn with_context(mut self, ctx: impl Into<String>) -> Self {
        self.context = Some(ctx.into());
        self
    }

    /// Create an I/O error from an `io::Error`.
    // TODO(0.3.0): remove together with the `IoError` variant.
    #[cfg(not(feature = "no_std"))]
    pub fn from_io_error(err: io::Error) -> Self {
        Self {
            kind: SerializeErrorKind::IoError(err.kind()),
            context: Some(err.to_string()),
        }
    }

    /// Create an InvalidValue error with a message.
    pub fn invalid_value(msg: &'static str) -> Self {
        Self::new(SerializeErrorKind::InvalidValue(msg))
    }

    /// Create a depth limit error.
    pub fn depth_limit() -> Self {
        Self::new(SerializeErrorKind::DepthLimit)
    }

    /// Create a message error.
    pub fn message(msg: &'static str) -> Self {
        Self::new(SerializeErrorKind::Message(msg))
    }
}

impl fmt::Display for SerializeError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.kind {
            #[cfg(not(feature = "no_std"))]
            SerializeErrorKind::IoError(kind) => {
                write!(f, "I/O error: {:?}", kind)?;
            }
            SerializeErrorKind::InvalidValue(msg) => {
                write!(f, "invalid value: {}", msg)?;
            }
            SerializeErrorKind::DepthLimit => {
                write!(f, "depth limit exceeded")?;
            }
            SerializeErrorKind::BufferFull => {
                write!(f, "buffer full")?;
            }
            SerializeErrorKind::Message(msg) => {
                write!(f, "{}", msg)?;
            }
        }

        if let Some(ctx) = &self.context {
            write!(f, " - {}", ctx)?;
        }

        Ok(())
    }
}

impl core::error::Error for SerializeError {}

// TODO(0.3.0): remove together with the `IoError` variant.
#[cfg(not(feature = "no_std"))]
impl From<io::Error> for SerializeError {
    fn from(err: io::Error) -> Self {
        Self::from_io_error(err)
    }
}
