///|
/// Half-open UTF-16 offsets. Line numbers are 1-based.
pub(all) struct Span {
  start : Int
  end : Int
  line : Int
  column : Int
} derive(Eq, Debug)

///|
pub(all) enum Severity {
  Error
  Warning
} derive(Eq, Debug)

///|
pub(all) struct Diagnostic {
  code : String
  severity : Severity
  message : String
  span : Span
} derive(Eq, Debug)

///|
pub fn Diagnostic::format(self : Diagnostic) -> String {
  let severity = match self.severity {
    Error => "error"
    Warning => "warning"
  }
  "\{self.span.line}:\{self.span.column}: \{severity} \{self.code}: \{self.message}"
}

///|
pub(all) enum Layout {
  TD1
  TD2
  TD3
} derive(Eq, Debug)

///|
pub(all) struct Names {
  primary : String
  secondary : String
} derive(Eq, Debug)

///|
/// Immutable MRZ snapshot. Arrays returned by accessors are copies.
pub(all) struct Document {
  layout : Layout
  document_code : String
  issuer : String
  names : Names
  document_number : String
  nationality : String
  birth : String
  sex : String
  expiry : String
  optional : String
  optional_secondary : String
  raw_lines : Array[String]
} derive(Eq, Debug)

///|
pub fn Document::lines(self : Document) -> Array[String] {
  self.raw_lines.copy()
}

///|
pub fn format(doc : Document) -> String {
  if doc.raw_lines.length() == 0 {
    return ""
  }
  let mut out = doc.raw_lines[0]
  let mut i = 1
  while i < doc.raw_lines.length() {
    out = out + "\n" + doc.raw_lines[i]
    i += 1
  }
  out
}

///|
fn error_diag(
  code : String,
  message : String,
  line : Int,
  column : Int,
  start : Int,
  end : Int,
) -> Diagnostic {
  { code, severity: Error, message, span: { start, end, line, column } }
}

///|
fn warning_diag(
  code : String,
  message : String,
  line : Int,
  column : Int,
  start : Int,
  end : Int,
) -> Diagnostic {
  { code, severity: Warning, message, span: { start, end, line, column } }
}

///|
pub fn has_error(diags : Array[Diagnostic]) -> Bool {
  let mut i = 0
  while i < diags.length() {
    if diags[i].severity == Error {
      return true
    }
    i += 1
  }
  false
}

///|
pub fn Document::full_name(self : Document) -> String {
  if self.names.secondary.length() == 0 {
    self.names.primary
  } else {
    self.names.primary + " " + self.names.secondary
  }
}