// Structured errors for every security.txt processing stage. Parser reports
// syntax problems, validator reports RFC 9116 field violations, audit reports
// advisories. Errors carry stage, kind, 1-based line/column, 0-based byte
// offset so callers can locate problems precisely.

///|
/// The processing stage an error was produced in.
pub(all) enum SecurityTxtErrorStage {
  Input
  Line
  FieldName
  FieldValue
  Uri
  DateTime
  Language
  SignatureEnvelope
  Validation
  Limit
} derive(Eq, Debug)

///|
/// The concrete error kind, one distinct kind per violation.
pub(all) enum SecurityTxtErrorKind {
  InvalidLine
  MissingColon
  EmptyFieldName
  EmptyFieldValue
  InvalidUtf8
  InvalidUri
  InvalidScheme
  InvalidDateTime
  InvalidLanguage
  DuplicateExpires
  DuplicatePreferredLanguages
  DuplicateField
  MissingContact
  MissingExpires
  ContextMismatch
  InvalidContext
  LimitExceeded
  InvalidSignatureEnvelope
} derive(Eq, Debug)

///|
/// A structured security.txt error: stage, kind and exact position.
pub(all) suberror SecurityTxtError {
  SecurityTxtError(
    SecurityTxtErrorStage,
    SecurityTxtErrorKind,
    Int,
    Int,
    Int,
    String
  )
}

///|
fn max_context_bytes() -> Int {
  200
}

///|
fn bound_context(context : String) -> String {
  if context.length() <= max_context_bytes() {
    context
  } else {
    "\{slice(context, 0, max_context_bytes())}..."
  }
}

///|
/// Extract the package suberror from a caught `Error`.
fn unwrap_security_txt_error(e : Error) -> SecurityTxtError {
  match e {
    SecurityTxtError(stage, kind, line, column, offset, message) =>
      SecurityTxtError(stage, kind, line, column, offset, message)
    _ => abort("internal error: unexpected non-SecurityTxtError")
  }
}

///|
/// Construct a security.txt error; line/column 1-based, byte_offset 0-based.
pub fn security_txt_error(
  stage : SecurityTxtErrorStage,
  kind : SecurityTxtErrorKind,
  line : Int,
  column : Int,
  byte_offset : Int,
  message : String,
) -> SecurityTxtError {
  SecurityTxtError(
    stage,
    kind,
    line,
    column,
    byte_offset,
    bound_context(message),
  )
}

///|
/// The processing stage of this error.
pub fn SecurityTxtError::stage(
  self : SecurityTxtError,
) -> SecurityTxtErrorStage {
  match self {
    SecurityTxtError(stage, _, _, _, _, _) => stage
  }
}

///|
/// The kind of this error.
pub fn SecurityTxtError::kind(self : SecurityTxtError) -> SecurityTxtErrorKind {
  match self {
    SecurityTxtError(_, kind, _, _, _, _) => kind
  }
}

///|
/// 1-based line number, 0 when unknown.
pub fn SecurityTxtError::line(self : SecurityTxtError) -> Int {
  match self {
    SecurityTxtError(_, _, line, _, _, _) => line
  }
}

///|
/// 1-based column number, 0 when unknown.
pub fn SecurityTxtError::column(self : SecurityTxtError) -> Int {
  match self {
    SecurityTxtError(_, _, _, column, _, _) => column
  }
}

///|
/// 0-based byte offset into the raw input, -1 when unknown.
pub fn SecurityTxtError::byte_offset(self : SecurityTxtError) -> Int {
  match self {
    SecurityTxtError(_, _, _, _, offset, _) => offset
  }
}

///|
/// Human-readable description of the violation.
pub fn SecurityTxtError::message(self : SecurityTxtError) -> String {
  match self {
    SecurityTxtError(_, _, _, _, _, message) => message
  }
}

///|
/// Render the error as a single readable line.
pub fn SecurityTxtError::to_string(self : SecurityTxtError) -> String {
  if self.line() > 0 {
    "\{self.stage().to_string()}::\{self.kind().to_string()} at byte \{self.byte_offset()}, line \{self.line()}, column \{self.column()}: \{self.message()}"
  } else {
    "\{self.stage().to_string()}::\{self.kind().to_string()}: \{self.message()}"
  }
}

///|
/// Render the error stage name.
pub fn SecurityTxtErrorStage::to_string(self : SecurityTxtErrorStage) -> String {
  match self {
    Input => "Input"
    Line => "Line"
    FieldName => "FieldName"
    FieldValue => "FieldValue"
    Uri => "Uri"
    DateTime => "DateTime"
    Language => "Language"
    SignatureEnvelope => "SignatureEnvelope"
    Validation => "Validation"
    Limit => "Limit"
  }
}

///|
/// Render the error kind name.
pub fn SecurityTxtErrorKind::to_string(self : SecurityTxtErrorKind) -> String {
  match self {
    InvalidLine => "InvalidLine"
    MissingColon => "MissingColon"
    EmptyFieldName => "EmptyFieldName"
    EmptyFieldValue => "EmptyFieldValue"
    InvalidUtf8 => "InvalidUtf8"
    InvalidUri => "InvalidUri"
    InvalidScheme => "InvalidScheme"
    InvalidDateTime => "InvalidDateTime"
    InvalidLanguage => "InvalidLanguage"
    DuplicateExpires => "DuplicateExpires"
    DuplicatePreferredLanguages => "DuplicatePreferredLanguages"
    DuplicateField => "DuplicateField"
    MissingContact => "MissingContact"
    MissingExpires => "MissingExpires"
    ContextMismatch => "ContextMismatch"
    InvalidContext => "InvalidContext"
    LimitExceeded => "LimitExceeded"
    InvalidSignatureEnvelope => "InvalidSignatureEnvelope"
  }
}