use std::fmt;

/// Errors that can occur while parsing a Kv Format text.
///
/// Variants are listed in priority order (Appendix B of the spec). When
/// multiple errors could apply to the same line, the highest-priority variant
/// (lowest number) MUST be reported.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError {
    /// `BOM_ERROR` (B.1) — the input starts with a UTF-8 BOM (`EF BB BF`).
    /// Detected before any other parsing; reported without a line number.
    BomError,

    /// `INVALID_UTF8_ERROR` (B.2) — the line contains an invalid UTF-8
    /// byte sequence.
    InvalidUtf8Error {
        /// 1-based line number (0 for the shebang line).
        line: u32,
    },

    /// `INVALID_CHARACTER_ERROR` (B.3) — the line contains a standalone CR
    /// (`U+000D`, i.e. a CR not followed by LF) or a NUL character
    /// (`U+0000`).
    InvalidCharacterError {
        /// 1-based line number.
        line: u32,
    },

    /// `EMPTY_KEY_ERROR` (B.4) — a data line starts with the `=` operator
    /// after stripping leading whitespace, indicating an empty key.
    EmptyKeyError {
        /// 1-based line number.
        line: u32,
    },

    /// `MISSING_OPERATOR_ERROR` (B.5) — a non-blank, non-comment line does
    /// not contain the `=` assignment operator.
    MissingOperatorError {
        /// 1-based line number.
        line: u32,
    },

    /// `INVALID_KEY_ERROR` (B.6) — the key does not match the pattern
    /// `[A-Za-z_][A-Za-z0-9_]*`.
    InvalidKeyError {
        /// 1-based line number.
        line: u32,
    },

    /// `MISSING_FINAL_EOL_ERROR` (B.7) — the final line of the input is not
    /// terminated by LF or CRLF. Reported without a line number.
    MissingFinalEolError,
}

impl fmt::Display for ParseError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ParseError::BomError => {
                write!(f, "BOM_ERROR: input must not begin with a UTF-8 BOM (EF BB BF)")
            }
            ParseError::InvalidUtf8Error { line } => {
                write!(f, "INVALID_UTF8_ERROR on line {line}: invalid UTF-8 byte sequence")
            }
            ParseError::InvalidCharacterError { line } => {
                write!(
                    f,
                    "INVALID_CHARACTER_ERROR on line {line}: standalone CR or NUL character"
                )
            }
            ParseError::EmptyKeyError { line } => {
                write!(f, "EMPTY_KEY_ERROR on line {line}: key must not be empty")
            }
            ParseError::MissingOperatorError { line } => {
                write!(
                    f,
                    "MISSING_OPERATOR_ERROR on line {line}: assignment operator '=' not found"
                )
            }
            ParseError::InvalidKeyError { line } => {
                write!(
                    f,
                    "INVALID_KEY_ERROR on line {line}: key must match [A-Za-z_][A-Za-z0-9_]*"
                )
            }
            ParseError::MissingFinalEolError => {
                write!(f, "MISSING_FINAL_EOL_ERROR: final line is not terminated by LF or CRLF")
            }
        }
    }
}

impl std::error::Error for ParseError {}
