//! Error types for ASN.1 DER/BER parsing.

extern crate alloc;

use alloc::string::String;
use core::fmt;

/// ASN.1 parsing error.
#[derive(Debug, Clone)]
pub struct Asn1Error {
    /// Error kind
    pub kind: Asn1ErrorKind,
    /// Position in input where error occurred
    pub pos: usize,
}

/// The kind of ASN.1 error.
#[derive(Debug, Clone)]
pub enum Asn1ErrorKind {
    /// Unexpected end of input
    UnexpectedEof,
    /// Unknown or unsupported tag
    UnknownTag { tag: u8 },
    /// Length mismatch
    LengthMismatch { expected: usize, got: usize },
    /// Invalid boolean value
    InvalidBool,
    /// Invalid real (float) value
    InvalidReal,
    /// Invalid UTF-8 string
    InvalidString { message: String },
    /// Sequence/content size mismatch
    SequenceSizeMismatch {
        sequence_end: usize,
        content_end: usize,
    },
    /// Unsupported ASN.1 type or shape
    Unsupported { message: String },
    /// Invalid type tag attribute
    InvalidTypeTag { message: String },
    /// Invalid discriminant for enum variant
    InvalidDiscriminant { discriminant: Option<i64> },
}

impl fmt::Display for Asn1Error {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match &self.kind {
            Asn1ErrorKind::UnexpectedEof => {
                write!(f, "unexpected end of input at position {}", self.pos)
            }
            Asn1ErrorKind::UnknownTag { tag } => {
                write!(f, "unknown tag 0x{:02x} at position {}", tag, self.pos)
            }
            Asn1ErrorKind::LengthMismatch { expected, got } => {
                write!(
                    f,
                    "length mismatch at position {}: expected {}, got {}",
                    self.pos, expected, got
                )
            }
            Asn1ErrorKind::InvalidBool => {
                write!(f, "invalid boolean value at position {}", self.pos)
            }
            Asn1ErrorKind::InvalidReal => write!(f, "invalid real value at position {}", self.pos),
            Asn1ErrorKind::InvalidString { message } => {
                write!(f, "invalid string at position {}: {}", self.pos, message)
            }
            Asn1ErrorKind::SequenceSizeMismatch {
                sequence_end,
                content_end,
            } => {
                write!(
                    f,
                    "sequence size mismatch: sequence ends at {}, content ends at {}",
                    sequence_end, content_end
                )
            }
            Asn1ErrorKind::Unsupported { message } => {
                write!(f, "unsupported: {}", message)
            }
            Asn1ErrorKind::InvalidTypeTag { message } => {
                write!(f, "invalid type tag: {}", message)
            }
            Asn1ErrorKind::InvalidDiscriminant { discriminant } => {
                if let Some(d) = discriminant {
                    write!(f, "invalid discriminant: {}", d)
                } else {
                    write!(f, "missing discriminant")
                }
            }
        }
    }
}

#[cfg(feature = "std")]
impl std::error::Error for Asn1Error {}

impl Asn1Error {
    /// Create a new error with the given kind at the given position.
    pub const fn new(kind: Asn1ErrorKind, pos: usize) -> Self {
        Self { kind, pos }
    }

    /// Create an unexpected EOF error.
    pub const fn unexpected_eof(pos: usize) -> Self {
        Self::new(Asn1ErrorKind::UnexpectedEof, pos)
    }

    /// Create an unknown tag error.
    pub const fn unknown_tag(tag: u8, pos: usize) -> Self {
        Self::new(Asn1ErrorKind::UnknownTag { tag }, pos)
    }

    /// Create an unsupported error.
    pub fn unsupported(message: impl Into<String>, pos: usize) -> Self {
        Self::new(
            Asn1ErrorKind::Unsupported {
                message: message.into(),
            },
            pos,
        )
    }
}
