///|
/// A KeyValues value. Objects are ordered lists, so duplicate keys are retained.
pub(all) enum Value {
  Text(String)
  Object(Array[Entry])
} derive(Eq, Debug)

///|
/// One key-value pair, in source order.
pub(all) struct Entry {
  key : String
  value : Value
} derive(Eq, Debug)

///|
/// A document can contain zero, one, or several top-level entries.
pub(all) struct Document {
  entries : Array[Entry]
} derive(Eq, Debug)

///|
/// Parsing limits apply to the whole document; depth counts nested objects.
pub(all) struct ParseOptions {
  escape_sequences : Bool
  max_depth : Int
  max_entries : Int
  max_input_chars : Int
  max_token_chars : Int
  conditions : Array[String]?
} derive(Eq, Debug)

///|
/// Escapes are enabled for canonical output and Steam's escaped Windows paths.
pub fn ParseOptions::default() -> ParseOptions {
  {
    escape_sequences: true,
    max_depth: 128,
    max_entries: 100000,
    max_input_chars: 8388608,
    max_token_chars: 1048576,
    conditions: None,
  }
}

///|
/// Offset is a zero-based Unicode scalar index; line and column are one-based.
pub(all) struct Diagnostic {
  code : String
  message : String
  offset : Int
  line : Int
  column : Int
} derive(Eq, Debug)

///|
pub suberror ParseError {
  ParseError(Diagnostic)
} derive(Debug)

///|
/// Look up the first exact, case-sensitive key match.
pub fn Document::get(self : Document, key : String) -> Value? {
  find_entry(self.entries, key)
}

///|
/// Look up every exact key match in source order.
pub fn Document::get_all(self : Document, key : String) -> Array[Value] {
  self.entries.filter_map(e => if e.key == key { Some(e.value) } else { None })
}

///|
/// Walk object keys. At each level, the first duplicate wins.
/// An empty path addresses the document's top-level object.
pub fn Document::lookup(self : Document, path : Array[String]) -> Value? {
  let mut current = Value::Object(self.entries)
  for key in path {
    match current {
      Object(entries) =>
        match find_entry(entries, key) {
          Some(value) => current = value
          None => return None
        }
      Text(_) => return None
    }
  }
  Some(current)
}

///|
pub fn Value::as_text(self : Value) -> String? {
  match self {
    Text(s) => Some(s)
    Object(_) => None
  }
}

///|
pub fn Value::as_object(self : Value) -> Array[Entry]? {
  match self {
    Object(entries) => Some(entries)
    Text(_) => None
  }
}

///|
/// Lossless JSON representation: ordered arrays of {key,value} entries.
/// An ordinary JSON object would silently lose duplicate keys.
pub fn Document::to_json(self : Document) -> Json {
  entries_to_json(self.entries)
}

///|
fn entries_to_json(entries : Array[Entry]) -> Json {
  Json::array(
    entries.map(e => {
      let fields : Map[String, Json] = Map([])
      fields["key"] = Json::string(e.key)
      fields["value"] = match e.value {
        Text(s) => Json::string(s)
        Object(children) => entries_to_json(children)
      }
      Json::object(fields)
    }),
  )
}

///|
fn find_entry(entries : Array[Entry], key : String) -> Value? {
  for entry in entries {
    if entry.key == key {
      return Some(entry.value)
    }
  }
  None
}