///|
/// Whether a diagnostic blocks importing the affected transaction.
///
/// `Error` marks a value the importer refuses to hand downstream, so the
/// affected transaction is dropped from [`Statement::transactions`].
/// `Warning` marks a value that was kept but is suspicious, such as an OFX
/// header line the parser could not interpret.
pub enum Severity {
  Error
  Warning
}

///|
/// One diagnostic produced while scanning an OFX document.
///
/// `code` is a stable, machine-readable identifier such as `invalid-posted`;
/// `location` names where the problem is, either a tag and its 1-based line
/// (`DTPOSTED@12`) or the offending value (a FITID). It is empty when the
/// diagnostic applies to the document as a whole.
pub struct Diagnostic {
  severity : Severity
  code : String
  location : String
}

///|
/// Whether the diagnostic blocks the import.
pub fn Diagnostic::severity(self : Diagnostic) -> Severity {
  self.severity
}

///|
/// The stable machine-readable code, such as `invalid-posted`.
pub fn Diagnostic::code(self : Diagnostic) -> String {
  self.code
}

///|
/// The tag and line, or value, the diagnostic belongs to; empty for
/// document-wide diagnostics.
pub fn Diagnostic::location(self : Diagnostic) -> String {
  self.location
}

///|
/// `error` or `warning`.
pub fn Severity::to_string(self : Severity) -> String {
  match self {
    Error => "error"
    Warning => "warning"
  }
}

///|
/// The stable `severity:code[:location]` rendering of a diagnostic.
pub fn Diagnostic::to_string(self : Diagnostic) -> String {
  if self.location == "" {
    self.severity.to_string() + ":" + self.code
  } else {
    self.severity.to_string() + ":" + self.code + ":" + self.location
  }
}

///|
fn error_at(code : String, location : String) -> Diagnostic {
  { severity: Error, code, location, }
}

///|
fn warning_at(code : String, location : String) -> Diagnostic {
  { severity: Warning, code, location, }
}

///|
/// The 1-based line of a tag as a location, e.g. `DTPOSTED@12`.
fn at_line(tag : String, line : Int) -> String {
  tag + "@" + line.to_string()
}

///|
/// The offending text and its 1-based line as a location, e.g. ` @7`.
fn at_text(text : String, line : Int) -> String {
  text + "@" + line.to_string()
}

///|
/// Every diagnostic code this library can emit, in the order the parser is able
/// to produce them.
///
/// Consumers can diff this list between library versions to reject codes they
/// do not understand yet. A code is never renamed or reused for a different
/// meaning.
pub fn diagnostic_codes() -> Array[String] {
  [
    "unclosed-tag", "empty-tag", "unclosed-comment", "unclosed-cdata", "malformed-entity",
    "unknown-entity", "unknown-header-line", "empty-header-value", "invalid-header-version",
    "unsupported-compression", "missing-header", "invalid-status-code", "invalid-server-date",
    "signon-status-error", "invalid-balance-amount", "invalid-balance-date", "unexpected-close",
    "mismatched-aggregate", "nested-aggregate", "unclosed-aggregate", "missing-type",
    "missing-fitid", "invalid-posted", "invalid-amount", "invalid-user-date", "invalid-sic",
    "duplicate-fitid", "conflicting-value", "missing-acctid", "missing-curdef", "no-transactions",
  ]
}