///|
/// Half-open UTF-16 offsets; line and column are 1-based UTF-16 units, not bytes.
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
  related : Span?
} derive(Eq, Debug)

///|
pub(all) enum NodeKind {
  Blank
  Comment
  Section
  Entry
  Invalid
} derive(Eq, Debug)

///|
/// Snapshot value object. Changing returned arrays cannot mutate a Document.
pub(all) struct Node {
  kind : NodeKind
  span : Span
  section : String
  key : String
  value : String
  key_span : Span
  value_span : Span
} derive(Eq, Debug)

///|
pub(all) enum DuplicatePolicy {
  FirstWins
  LastWins
  Reject
} derive(Eq, Debug)

///|
pub(all) struct Dialect {
  allow_colon : Bool
  hash_comments : Bool
  inline_comments : Bool
  case_sensitive : Bool
  multiline : Bool
  duplicates : DuplicatePolicy
} derive(Eq, Debug)

///|
pub fn standard() -> Dialect {
  {
    allow_colon: false,
    hash_comments: true,
    inline_comments: true,
    case_sensitive: true,
    multiline: false,
    duplicates: Reject,
  }
}

///|
/// Owned immutable source snapshot; edits return a fresh snapshot.
pub struct Document {
  priv source : String
  priv limits : ParseLimits
  priv dialect : Dialect
  priv nodes : Array[Node]
  priv diagnostics : Array[Diagnostic]
}

///|
pub fn Document::render(self : Document) -> String {
  self.source
}

///|
pub fn Document::nodes(self : Document) -> Array[Node] {
  self.nodes.copy()
}

///|
pub fn Document::diagnostics(self : Document) -> Array[Diagnostic] {
  self.diagnostics.copy()
}

///|
pub fn Document::has_errors(self : Document) -> Bool {
  self.diagnostics.any(d => d.severity == Error)
}

///|
fn span(start : Int, end : Int, line : Int, column : Int) -> Span {
  { start, end, line, column }
}

///|
fn diagnostic(code : String, message : String, at : Span) -> Diagnostic {
  { code, severity: Error, message, span: at, related: None }
}