///|
fn parse_quoted_label_value(cursor : TextCursor) -> Result[String, String] {
  if !cursor.consume('"') {
    return Err("expected quoted label value")
  }
  let encoded = StringBuilder()
  while !cursor.is_end() {
    match cursor.next() {
      Some('"') => return unescape_label_value(encoded.to_string())
      Some('\\') =>
        match cursor.next() {
          Some(escaped) => {
            encoded.write_char('\\')
            encoded.write_char(escaped)
          }
          None => return Err("trailing backslash in quoted label value")
        }
      Some(char) => encoded.write_char(char)
      None => ()
    }
  }
  Err("unterminated quoted label value")
}

///|
fn parse_labels_from(cursor : TextCursor) -> Result[Array[Label], String] {
  if !cursor.consume('{') {
    return Err("expected '{' before label set")
  }
  cursor.skip_horizontal_space()
  let labels : Array[Label] = []
  if cursor.consume('}') {
    return Ok(labels)
  }
  while true {
    let name = cursor.read_label_name()
    if !is_valid_label_name(name) {
      return Err("invalid or missing label name")
    }
    cursor.skip_horizontal_space()
    if !cursor.consume('=') {
      return Err("expected '=' after label name '\{name}'")
    }
    cursor.skip_horizontal_space()
    let value = match parse_quoted_label_value(cursor) {
      Ok(value) => value
      Err(error) => return Err(error)
    }
    labels.push(Label::new(name, value))
    cursor.skip_horizontal_space()
    if cursor.consume('}') {
      return Ok(labels)
    }
    if !cursor.consume(',') {
      return Err("expected ',' or '}' after label value")
    }
    cursor.skip_horizontal_space()
    if cursor.peek() == Some('}') {
      return Err("trailing comma in label set")
    }
  }
  Err("unexpected end of label set")
}

///|
/// Parse one complete OpenMetrics label set.
///
/// Whitespace around separators is accepted, while a trailing comma is
/// rejected.
pub fn parse_label_set(input : StringView) -> Result[Array[Label], String] {
  let cursor = TextCursor::new(input)
  cursor.skip_horizontal_space()
  let labels = match parse_labels_from(cursor) {
    Ok(labels) => labels
    Err(error) => return Err(error)
  }
  cursor.skip_horizontal_space()
  if !cursor.is_end() {
    return Err("unexpected text after label set")
  }
  Ok(labels)
}

///|
fn encode_label_set_inner(
  labels : Array[Label],
  canonical_number_label : StringView,
) -> String {
  if labels.is_empty() {
    return ""
  }
  let out = StringBuilder()
  out.write_char('{')
  for index in 0.. 0 {
      out.write_char(',')
    }
    let label = labels[index]
    let value = if !canonical_number_label.is_empty() &&
      canonical_number_label.equal_to_string(label.name) {
      match parse_number(label.value) {
        Ok(number) => format_number(number)
        Err(_) => label.value
      }
    } else {
      label.value
    }
    out.write_string(label.name)
    out.write_string("=\"")
    out.write_string(escape_label_value(value))
    out.write_char('"')
  }
  out.write_char('}')
  out.to_string()
}

///|
/// Encode labels in their existing order.
pub fn encode_label_set(labels : Array[Label]) -> String {
  encode_label_set_inner(labels, "")
}

///|
fn encode_metric_label_set(
  labels : Array[Label],
  canonical_number_label : StringView,
) -> String {
  encode_label_set_inner(labels, canonical_number_label)
}