///| Source location attached to parsed configuration items and diagnostics.
pub struct Span {
  file : String
  line : Int
  column : Int
} derive(@debug.Debug, Eq)

///| Creates a location object for synthetic values.
pub fn span(file : String, line : Int, column : Int) -> Span {
  { file, line, column }
}

///| Severity used by diagnostics and validation reports.
pub enum Severity {
  Error
  Warning
  Info
} derive(@debug.Debug, Eq)

///| A user-facing parser, merge, or validation message.
pub struct Diagnostic {
  severity : Severity
  code : String
  message : String
  span : Span?
  hint : String?
} derive(@debug.Debug, Eq)

///| Creates an error diagnostic.
pub fn error(code : String, message : String, span~ : Span? = None, hint~ : String? = None) -> Diagnostic {
  { severity: Error, code, message, span, hint }
}

///| Creates a warning diagnostic.
pub fn warning(code : String, message : String, span~ : Span? = None, hint~ : String? = None) -> Diagnostic {
  { severity: Warning, code, message, span, hint }
}

///| Creates an informational diagnostic.
pub fn info(code : String, message : String, span~ : Span? = None, hint~ : String? = None) -> Diagnostic {
  { severity: Info, code, message, span, hint }
}

///| One parsed key/value assignment.
pub struct ConfigEntry {
  section : String
  key : String
  value : String
  source : String
  span : Span
  comments : Array[String]
} derive(@debug.Debug, Eq)

///| A full configuration document plus non-fatal diagnostics.
pub struct ConfigDocument {
  name : String
  entries : Array[ConfigEntry]
  diagnostics : Array[Diagnostic]
} derive(@debug.Debug, Eq)

///| The result of a parser operation.
pub struct ParseResult {
  document : ConfigDocument
  ok : Bool
} derive(@debug.Debug, Eq)

///| Options shared by the INI and properties parsers.
pub struct ParseOptions {
  allow_colon_separator : Bool
  allow_empty_value : Bool
  preserve_comments : Bool
  strict_sections : Bool
} derive(@debug.Debug, Eq)

///| Default parser options for tolerant configuration files.
pub fn default_parse_options() -> ParseOptions {
  {
    allow_colon_separator: true,
    allow_empty_value: true,
    preserve_comments: true,
    strict_sections: false,
  }
}

///| Creates parser options for callers that need strict or custom behavior.
pub fn parse_options(
  allow_colon_separator? : Bool = true,
  allow_empty_value? : Bool = true,
  preserve_comments? : Bool = true,
  strict_sections? : Bool = false,
) -> ParseOptions {
  {
    allow_colon_separator,
    allow_empty_value,
    preserve_comments,
    strict_sections,
  }
}

///| Builds an empty document with a stable name.
pub fn empty_document(name : String) -> ConfigDocument {
  { name, entries: [], diagnostics: [] }
}

///| Returns the number of error diagnostics.
pub fn error_count(diagnostics : Array[Diagnostic]) -> Int {
  let mut count = 0
  for item in diagnostics {
    if item.severity == Error {
      count = count + 1
    }
  }
  count
}

///| Returns true when the diagnostic list has no errors.
pub fn diagnostics_ok(diagnostics : Array[Diagnostic]) -> Bool {
  error_count(diagnostics) == 0
}

///| Creates a normalized path-like key using section and key parts.
pub fn entry_path(section : String, key : String) -> String {
  if section == "" {
    key
  } else {
    section + "." + key
  }
}

///| Returns the canonical path for an entry.
pub fn ConfigEntry::path(self : ConfigEntry) -> String {
  entry_path(self.section, self.key)
}