///|
/// Export a parsed record as compact JSON.
pub fn Record::to_json_string(self : Record) -> String {
  let out = StringBuilder()
  out.write_string("{\"accession\":")
  out.write_string(json_string(self.accession))
  out.write_string(",\"version\":")
  out.write_string(json_string(self.version))
  out.write_string(",\"locus\":")
  out.write_string(self.locus.to_json_string())
  out.write_string(",\"definition\":")
  out.write_string(json_string(self.definition))
  out.write_string(",\"keywords\":")
  out.write_string(json_string(self.keywords))
  out.write_string(",\"source\":")
  out.write_string(json_string(self.source))
  out.write_string(",\"features\":")
  out.write_string(features_json(self.features))
  out.write_string(",\"origin\":")
  out.write_string(json_string(self.origin))
  out.write_string(",\"stats\":")
  out.write_string(self.sequence_stats().to_json_string())
  out.write_string(",\"validation\":")
  out.write_string(
    array_json(self.validate().map(fn(issue) { issue.to_json_string() })),
  )
  out.write_string("}")
  out.to_string()
}

///|
pub fn SequenceStats::to_json_string(self : SequenceStats) -> String {
  let out = StringBuilder()
  out.write_string("{\"length\":")
  out.write_string(self.length.to_string())
  out.write_string(",\"a\":")
  out.write_string(self.a.to_string())
  out.write_string(",\"c\":")
  out.write_string(self.c.to_string())
  out.write_string(",\"g\":")
  out.write_string(self.g.to_string())
  out.write_string(",\"t\":")
  out.write_string(self.t.to_string())
  out.write_string(",\"n\":")
  out.write_string(self.n.to_string())
  out.write_string(",\"gc_percent\":")
  out.write_string(self.gc_percent.to_string())
  out.write_string("}")
  out.to_string()
}

///|
pub fn Locus::to_json_string(self : Locus) -> String {
  let out = StringBuilder()
  out.write_string("{\"name\":")
  out.write_string(json_string(self.name))
  out.write_string(",\"length\":")
  out.write_string(self.length.to_string())
  out.write_string(",\"molecule_type\":")
  out.write_string(json_string(self.molecule_type))
  out.write_string(",\"topology\":")
  out.write_string(json_string(self.topology))
  out.write_string(",\"division\":")
  out.write_string(json_string(self.division))
  out.write_string(",\"date\":")
  out.write_string(json_string(self.date))
  out.write_string("}")
  out.to_string()
}

///|
pub fn Feature::to_json_string(self : Feature) -> String {
  let out = StringBuilder()
  out.write_string("{\"key\":")
  out.write_string(json_string(self.key))
  out.write_string(",\"location\":")
  out.write_string(self.location.to_json_string())
  out.write_string(",\"qualifiers\":")
  out.write_string(qualifiers_json(self.qualifiers))
  out.write_string("}")
  out.to_string()
}

///|
pub fn Location::to_json_string(self : Location) -> String {
  let out = StringBuilder()
  out.write_string("{\"raw\":")
  out.write_string(json_string(self.raw))
  out.write_string(",\"complement\":")
  out.write_string(self.complement.to_string())
  out.write_string(",\"joined\":")
  out.write_string(self.joined.to_string())
  out.write_string(",\"ordered\":")
  out.write_string(self.ordered.to_string())
  out.write_string(",\"ranges\":")
  out.write_string(ranges_json(self.ranges))
  out.write_string(",\"segments\":")
  out.write_string(segments_json(self.segments))
  out.write_string("}")
  out.to_string()
}

///|
fn features_json(features : Array[Feature]) -> String {
  array_json(features.map(fn(feature) { feature.to_json_string() }))
}

///|
fn qualifiers_json(qualifiers : Array[Qualifier]) -> String {
  array_json(
    qualifiers.map(fn(qualifier) {
      let value_json = match qualifier.value {
        Some(value) => json_string(value)
        None => "null"
      }
      "{\"name\":" +
      json_string(qualifier.name) +
      ",\"value\":" +
      value_json +
      "}"
    }),
  )
}

///|
fn ranges_json(ranges : Array[Range]) -> String {
  array_json(
    ranges.map(fn(range) {
      let out = StringBuilder()
      out.write_string("{\"start\":")
      out.write_string(range.start.to_string())
      out.write_string(",\"end\":")
      out.write_string(range.end.to_string())
      out.write_string(",\"partial_start\":")
      out.write_string(range.partial_start.to_string())
      out.write_string(",\"partial_end\":")
      out.write_string(range.partial_end.to_string())
      out.write_string(",\"between\":")
      out.write_string(range.between.to_string())
      out.write_string("}")
      out.to_string()
    }),
  )
}

///|
fn segments_json(segments : Array[LocationSegment]) -> String {
  array_json(
    segments.map(fn(segment) {
      let remote = match segment.remote {
        Some(accession) => json_string(accession)
        None => "null"
      }
      "{\"range\":" +
      range_json(segment.range) +
      ",\"reverse\":" +
      segment.reverse.to_string() +
      ",\"remote\":" +
      remote +
      "}"
    }),
  )
}

///|
fn range_json(range : Range) -> String {
  let out = StringBuilder()
  out.write_string("{\"start\":")
  out.write_string(range.start.to_string())
  out.write_string(",\"end\":")
  out.write_string(range.end.to_string())
  out.write_string(",\"partial_start\":")
  out.write_string(range.partial_start.to_string())
  out.write_string(",\"partial_end\":")
  out.write_string(range.partial_end.to_string())
  out.write_string(",\"between\":")
  out.write_string(range.between.to_string())
  out.write_string("}")
  out.to_string()
}

///|
fn array_json(items : Array[String]) -> String {
  "[" + items.join(",") + "]"
}

///|
fn json_string(text : String) -> String {
  let out = StringBuilder()
  out.write_char('"')
  for ch in text {
    match ch {
      '"' => out.write_string("\\\"")
      '\\' => out.write_string("\\\\")
      '\n' => out.write_string("\\n")
      '\r' => out.write_string("\\r")
      '\t' => out.write_string("\\t")
      _ => out.write_char(ch)
    }
  }
  out.write_char('"')
  out.to_string()
}