///|
/// Checked failures raised while reading, writing, or verifying VCDIFF data.
/// Every format failure carries the byte offset at which it was detected.
pub(all) suberror VcdiffError {
  InvalidMagic(offset~ : Int)
  UnsupportedVersion(offset~ : Int, version~ : Int)
  UnsupportedFeature(offset~ : Int, feature~ : String)
  TruncatedInput(offset~ : Int, needed~ : Int, available~ : Int)
  InvalidVarint(offset~ : Int, reason~ : String)
  IntegerOverflow(offset~ : Int)
  InvalidHeader(offset~ : Int, reason~ : String)
  InvalidWindow(offset~ : Int, reason~ : String)
  InvalidInstruction(offset~ : Int, reason~ : String)
  InvalidAddress(offset~ : Int, address~ : Int, limit~ : Int)
  MissingSource(offset~ : Int)
  ResourceLimit(offset~ : Int, resource~ : String, limit~ : Int)
  LengthMismatch(offset~ : Int, expected~ : Int, actual~ : Int)
  VerificationMismatch(offset~ : Int, expected_size~ : Int, actual_size~ : Int)
  InvalidOption(option~ : String, reason~ : String)
  Io(path~ : String, reason~ : String)
} derive(Eq, Debug)

///|
/// Returns a stable machine-readable category for a VCDIFF error.
pub fn VcdiffError::code(self : VcdiffError) -> String {
  match self {
    InvalidMagic(..) => "invalid_magic"
    UnsupportedVersion(..) => "unsupported_version"
    UnsupportedFeature(..) => "unsupported_feature"
    TruncatedInput(..) => "truncated_input"
    InvalidVarint(..) => "invalid_varint"
    IntegerOverflow(..) => "integer_overflow"
    InvalidHeader(..) => "invalid_header"
    InvalidWindow(..) => "invalid_window"
    InvalidInstruction(..) => "invalid_instruction"
    InvalidAddress(..) => "invalid_address"
    MissingSource(..) => "missing_source"
    ResourceLimit(..) => "resource_limit"
    LengthMismatch(..) => "length_mismatch"
    VerificationMismatch(..) => "verification_mismatch"
    InvalidOption(..) => "invalid_option"
    Io(..) => "io_error"
  }
}

///|
/// Returns the most specific byte offset available for this error.
/// Configuration and I/O failures do not refer to delta bytes and return -1.
pub fn VcdiffError::offset(self : VcdiffError) -> Int {
  match self {
    InvalidMagic(offset~)
    | UnsupportedVersion(offset~, ..)
    | UnsupportedFeature(offset~, ..)
    | TruncatedInput(offset~, ..)
    | InvalidVarint(offset~, ..)
    | IntegerOverflow(offset~)
    | InvalidHeader(offset~, ..)
    | InvalidWindow(offset~, ..)
    | InvalidInstruction(offset~, ..)
    | InvalidAddress(offset~, ..)
    | MissingSource(offset~)
    | ResourceLimit(offset~, ..)
    | LengthMismatch(offset~, ..)
    | VerificationMismatch(offset~, ..) => offset
    InvalidOption(..) | Io(..) => -1
  }
}

///|
/// Returns a concise human-readable explanation without losing the category.
pub fn VcdiffError::message(self : VcdiffError) -> String {
  match self {
    InvalidMagic(offset~) => "invalid VCDIFF magic at byte \{offset}"
    UnsupportedVersion(offset~, version~) =>
      "unsupported VCDIFF version \{version} at byte \{offset}"
    UnsupportedFeature(offset~, feature~) =>
      "unsupported feature \{feature} at byte \{offset}"
    TruncatedInput(offset~, needed~, available~) =>
      "truncated input at byte \{offset}: need \{needed}, have \{available}"
    InvalidVarint(offset~, reason~) =>
      "invalid variable-length integer at byte \{offset}: \{reason}"
    IntegerOverflow(offset~) => "integer overflow at byte \{offset}"
    InvalidHeader(offset~, reason~) =>
      "invalid file header at byte \{offset}: \{reason}"
    InvalidWindow(offset~, reason~) =>
      "invalid window at byte \{offset}: \{reason}"
    InvalidInstruction(offset~, reason~) =>
      "invalid instruction at byte \{offset}: \{reason}"
    InvalidAddress(offset~, address~, limit~) =>
      "invalid copy address \{address} at byte \{offset}; limit is \{limit}"
    MissingSource(offset~) =>
      "delta requires a source segment at byte \{offset}"
    ResourceLimit(offset~, resource~, limit~) =>
      "resource limit \{resource}=\{limit} exceeded at byte \{offset}"
    LengthMismatch(offset~, expected~, actual~) =>
      "length mismatch at byte \{offset}: expected \{expected}, got \{actual}"
    VerificationMismatch(offset~, expected_size~, actual_size~) =>
      "verification mismatch at byte \{offset}: expected \{expected_size} bytes, got \{actual_size}"
    InvalidOption(option~, reason~) => "invalid option \{option}: \{reason}"
    Io(path~, reason~) => "I/O error for \{path}: \{reason}"
  }
}