///|
/// Half-open byte span in a source report. Lines are one-based and columns are
/// one-based for user-facing diagnostics.
pub(all) struct Span {
  start : Int
  end : Int
  line : Int
  column : Int
} derive(Eq, Debug)

///|
pub fn Span::new(
  start : Int,
  end : Int,
  line? : Int = 1,
  column? : Int = 1,
) -> Span {
  { start, end, line, column }
}

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

///|
pub(all) enum DiagnosticCode {
  EmptyInput
  MissingStation
  MissingObservationTime
  InvalidStation
  InvalidTime
  InvalidWind
  InvalidVisibility
  InvalidWeather
  InvalidCloud
  InvalidTemperature
  InvalidPressure
  InvalidTafPeriod
  InvalidTafChange
  InvalidRunwayVisualRange
  UnknownToken
} derive(Eq, Debug)

///|
/// Structured diagnostic emitted by parsers and validators.
pub(all) struct Diagnostic {
  severity : Severity
  code : DiagnosticCode
  message : String
  span : Span
  token : String
} derive(Eq, Debug)

///|
pub fn Diagnostic::new(
  severity : Severity,
  code : DiagnosticCode,
  message : String,
  span : Span,
  token? : String = "",
) -> Diagnostic {
  { severity, code, message, span, token }
}

///|
pub fn Diagnostic::is_error(self : Diagnostic) -> Bool {
  self.severity == Error
}

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