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

///|
pub extend Severity with Eq::{equal, not_equal}

///|
pub extend Severity with @debug.Debug::{to_repr}

///|
/// Half-open UTF-16 offsets into the owned source.
pub struct Span { start : Int; end : Int } derive(Eq, Debug)

///|
pub extend Span with Eq::{equal, not_equal}

///|
pub extend Span with @debug.Debug::{to_repr}

///|
pub struct Position { offset : Int; line : Int; column : Int } derive(Eq, Debug)

///|
pub extend Position with Eq::{equal, not_equal}

///|
pub extend Position with @debug.Debug::{to_repr}

///|
pub struct Location { source_id : String?; start : Position; end : Position } derive(Eq, Debug)

///|
pub extend Location with Eq::{equal, not_equal}

///|
pub extend Location with @debug.Debug::{to_repr}

///|
pub struct SourceMap { source_id : String?; length : Int; line_starts : ReadOnlyArray[Int] } derive(Eq, Debug)

///|
pub extend SourceMap with Eq::{equal, not_equal}

///|
pub extend SourceMap with @debug.Debug::{to_repr}

///|
fn source_map(source : String, source_id : String?) -> SourceMap {
  let starts = [0]
  let mut i = 0
  while i < source.length() {
    let c = unit(source, i)
    i += 1
    if c == 13 { if unit(source, i) == 10 { i += 1 }; starts.push(i) }
    else if c == 10 { starts.push(i) }
  }
  { source_id, length: source.length(), line_starts: ReadOnlyArray::from_array(starts) }
}

///|
/// Offsets outside the source are rejected, not clamped into invented locations.
pub fn SourceMap::position(self : SourceMap, offset : Int) -> Position? {
  if offset < 0 || offset > self.length { return None }
  let mut lo = 0
  let mut hi = self.line_starts.length()
  while lo + 1 < hi {
    let mid = lo + (hi - lo) / 2
    if self.line_starts[mid] <= offset { lo = mid } else { hi = mid }
  }
  Some({ offset, line: lo + 1, column: offset - self.line_starts[lo] + 1 })
}

///|
pub fn SourceMap::location(self : SourceMap, span : Span) -> Location? {
  guard span.start <= span.end && self.position(span.start) is Some(start) && self.position(span.end) is Some(end) else { return None }
  Some({ source_id: self.source_id, start, end })
}

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

///|
pub extend Diagnostic with Eq::{equal, not_equal}

///|
pub extend Diagnostic with @debug.Debug::{to_repr}

///|
fn finding(code : String, message : String, span? : Span, map? : SourceMap) -> Diagnostic {
  let location = match (map, span) { (Some(m), Some(s)) => m.location(s); _ => None }
  { code, severity: Error, message, span, location }
}

///|
/// Lexical source records. Raw text is never normalized or entity-expanded.
pub(all) enum TokenKind {
  Open; EndOpen; Close; EmptyClose; Name; Whitespace; Equals; Quote
  AttributeValue; Text; CharacterReference; Comment; ProcessingInstruction
  CData; Declaration; Error
} derive(Eq, Debug)

///|
pub extend TokenKind with Eq::{equal, not_equal}

///|
pub extend TokenKind with @debug.Debug::{to_repr}

///|
pub struct Token { kind : TokenKind; text : String; span : Span } derive(Eq, Debug)

///|
pub extend Token with Eq::{equal, not_equal}

///|
pub extend Token with @debug.Debug::{to_repr}

///|
pub struct ExpandedName { namespace_uri : String; local_name : String } derive(Eq, Debug)

///|
pub extend ExpandedName with Eq::{equal, not_equal}

///|
pub extend ExpandedName with @debug.Debug::{to_repr}

///|
pub struct XmlAttribute {
  raw_name : String
  name : ExpandedName
  value : String
  span : Span
  value_span : Span
  namespace_declaration : Bool
} derive(Eq, Debug)

///|
pub extend XmlAttribute with Eq::{equal, not_equal}

///|
pub extend XmlAttribute with @debug.Debug::{to_repr}

///|
pub(all) enum XmlNodeKind { Document; Element; Text; Comment; ProcessingInstruction; CData; Declaration; Error } derive(Eq, Debug)

///|
pub extend XmlNodeKind with Eq::{equal, not_equal}

///|
pub extend XmlNodeKind with @debug.Debug::{to_repr}

///|
/// Nodes use local arena indexes, so deeply nested XML does not require recursive
/// values or recursive CST operations. Bindings are declarations on this node.
pub struct XmlNode {
  kind : XmlNodeKind
  raw_name : String
  name : ExpandedName?
  attributes : ReadOnlyArray[XmlAttribute]
  bindings : ReadOnlyArray[(String, String)]
  children : ReadOnlyArray[Int]
  parent : Int?
  span : Span
  value : String
  complete : Bool
} derive(Eq, Debug)

///|
pub extend XmlNode with Eq::{equal, not_equal}

///|
pub extend XmlNode with @debug.Debug::{to_repr}

///|
pub struct Cst { tokens : ReadOnlyArray[Token]; nodes : ReadOnlyArray[XmlNode]; complete : Bool } derive(Eq, Debug)

///|
pub extend Cst with Eq::{equal, not_equal}

///|
pub extend Cst with @debug.Debug::{to_repr}

///|
pub fn Cst::text(self : Cst) -> String {
  if self.tokens.length() == 1 { return self.tokens[0].text }
  let b = StringBuilder()
  for token in self.tokens { b.write_string(token.text) }
  b.to_string()
}

///|
/// A processing bound is inclusive. All fields must be positive. Node count
/// includes the document node, elements, content nodes and attributes.
pub struct Limits {
  source_units : Int
  output_units : Int
  nodes : Int
  depth : Int
  token_units : Int
  diagnostics : Int
} derive(Eq, Debug)

///|
pub extend Limits with Eq::{equal, not_equal}

///|
pub extend Limits with @debug.Debug::{to_repr}

///|
pub fn Limits::new(source_units? : Int = 16777216, output_units? : Int = 16777216, nodes? : Int = 1000000, depth? : Int = 4096, token_units? : Int = 1048576, diagnostics? : Int = 256) -> Limits {
  { source_units, output_units, nodes, depth, token_units, diagnostics }
}

///|
pub struct ReadResult { tree : Cst; document : Document?; source_map : SourceMap?; diagnostics : ReadOnlyArray[Diagnostic] } derive(Eq, Debug)

///|
pub extend ReadResult with Eq::{equal, not_equal}

///|
pub extend ReadResult with @debug.Debug::{to_repr}

///|
/// Stable XML syntax/namespace/limit diagnostic inventory. BPMN lowering codes
/// are separate; later structural/reference stages do not use this inventory.
pub let xml_diagnostic_codes : ReadOnlyArray[String] = [
  "xml/invalid-limits", "xml/source-limit", "xml/node-limit", "xml/depth-limit",
  "xml/token-limit", "xml/diagnostic-limit", "xml/invalid-character",
  "xml/invalid-name", "xml/invalid-tag", "xml/unclosed-tag", "xml/mismatched-tag",
  "xml/duplicate-attribute", "xml/undeclared-prefix", "xml/invalid-namespace",
  "xml/character-reference", "xml/outside-root", "xml/root-required",
  "xml/invalid-comment", "xml/unclosed-comment", "xml/unclosed-cdata",
  "xml/invalid-pi", "xml/unclosed-pi", "xml/invalid-declaration", "xml/encoding",
  "xml/prohibited-declaration", "xml/cdata-close",
]