///|
/// The stage that produced a diagnostic.
pub(all) enum DiagnosticStage {
  Lex
  Parse
  Evaluate
  Configure
} derive(Eq, Debug, ToJson, FromJson)

///|
/// A stable, structured error intended for editors, CLIs, and CI reports.
pub(all) struct Diagnostic {
  stage : DiagnosticStage
  code : String
  message : String
  span : Span
  hint : String?
} derive(Eq, Debug, ToJson, FromJson)

///|
pub fn Diagnostic::new(
  stage : DiagnosticStage,
  code : String,
  message : String,
  span : Span,
  hint? : String,
) -> Diagnostic {
  { stage, code, message, span, hint }
}

///|
pub fn Diagnostic::render(self : Diagnostic, source : String) -> String {
  let prefix = match self.stage {
    Lex => "lex"
    Parse => "parse"
    Evaluate => "evaluate"
    Configure => "configure"
  }
  let excerpt = if self.span.start >= 0 &&
    self.span.end >= self.span.start &&
    self.span.end <= source.length() {
    source[self.span.start:self.span.end].to_owned()
  } else {
    ""
  }
  let hint = match self.hint {
    Some(value) => "\nhint: \{value}"
    None => ""
  }
  "\{prefix}[\{self.code}] at \{self.span.start}..\{self.span.end}: \{self.message}\nsource: \{excerpt}\{hint}"
}

///|
priv suberror RuleFailure {
  RuleFailure(Diagnostic)
}