///|
fn sample_error(
  line : Int,
  cursor : TextCursor,
  message : String,
) -> Result[Sample, ParseError] {
  Err(ParseError::new(line, cursor.position() + 1, message))
}

///|
fn parse_exemplar_from(
  cursor : TextCursor,
  line : Int,
) -> Result[Exemplar, ParseError] {
  if !cursor.consume('#') {
    return Err(
      ParseError::new(line, cursor.position() + 1, "expected exemplar marker"),
    )
  }
  if !cursor.take_horizontal_space() {
    return Err(
      ParseError::new(
        line,
        cursor.position() + 1,
        "expected space after exemplar marker",
      ),
    )
  }
  let labels = match parse_labels_from(cursor) {
    Ok(labels) => labels
    Err(error) =>
      return Err(ParseError::new(line, cursor.position() + 1, error))
  }
  if !cursor.take_horizontal_space() {
    return Err(
      ParseError::new(line, cursor.position() + 1, "expected exemplar value"),
    )
  }
  let value_text = cursor.read_token()
  let value = match parse_number(value_text) {
    Ok(value) => value
    Err(error) =>
      return Err(ParseError::new(line, cursor.position() + 1, error))
  }
  let mut timestamp : Double? = None
  if cursor.take_horizontal_space() {
    if !cursor.is_end() {
      let timestamp_text = cursor.read_token()
      timestamp = match parse_timestamp(timestamp_text) {
        Ok(value) => Some(value)
        Err(error) =>
          return Err(ParseError::new(line, cursor.position() + 1, error))
      }
      cursor.skip_horizontal_space()
    }
  }
  if !cursor.is_end() {
    return Err(
      ParseError::new(
        line,
        cursor.position() + 1,
        "unexpected text after exemplar",
      ),
    )
  }
  Ok(Exemplar::new(labels, value, timestamp?))
}

///|
/// Parse one complete OpenMetrics sample line.
///
/// The accepted shape is:
/// `name{labels} value [timestamp] [# {exemplar_labels} value [timestamp]]`.
pub fn parse_sample_line(
  input : StringView,
  line? : Int = 1,
) -> Result[Sample, ParseError] {
  let cursor = TextCursor::new(input)
  let name = cursor.read_metric_name()
  if !is_valid_metric_name(name) {
    return sample_error(line, cursor, "invalid or missing metric name")
  }
  let labels = if cursor.peek() == Some('{') {
    match parse_labels_from(cursor) {
      Ok(labels) => labels
      Err(error) => return sample_error(line, cursor, error)
    }
  } else {
    []
  }
  if !cursor.take_horizontal_space() {
    return sample_error(line, cursor, "expected space before sample value")
  }
  let value_text = cursor.read_token()
  if value_text == "" {
    return sample_error(line, cursor, "missing sample value")
  }
  let value = match parse_number(value_text) {
    Ok(value) => value
    Err(error) => return sample_error(line, cursor, error)
  }
  let mut timestamp : Double? = None
  let mut exemplar : Exemplar? = None
  if cursor.take_horizontal_space() && !cursor.is_end() {
    if cursor.peek() == Some('#') {
      exemplar = match parse_exemplar_from(cursor, line) {
        Ok(value) => Some(value)
        Err(error) => return Err(error)
      }
    } else {
      let timestamp_text = cursor.read_token()
      timestamp = match parse_timestamp(timestamp_text) {
        Ok(value) => Some(value)
        Err(error) => return sample_error(line, cursor, error)
      }
      if cursor.take_horizontal_space() && !cursor.is_end() {
        if cursor.peek() != Some('#') {
          return sample_error(line, cursor, "unexpected text after timestamp")
        }
        exemplar = match parse_exemplar_from(cursor, line) {
          Ok(value) => Some(value)
          Err(error) => return Err(error)
        }
      }
    }
  }
  cursor.skip_horizontal_space()
  if !cursor.is_end() {
    return sample_error(line, cursor, "unexpected text after sample")
  }
  Ok(Sample::new(name, value, labels~, timestamp?, exemplar?))
}