///|
fn escape_lexicon_field(value : String) -> String {
  let mut output = ""
  for code in text_code_points(value) {
    let escaped = match code {
      92 => "\\\\"
      9 => "\\t"
      10 => "\\n"
      13 => "\\r"
      _ => code_points_text([code])
    }
    output = output + escaped
  }
  output
}

///|
fn unescape_lexicon_field(value : String) -> Result[String, PinyinError] {
  let output_codes : Array[Int] = []
  let mut index = 0
  while index < value.length() {
    let code = value[index].to_int()
    if code != 92 {
      output_codes.push(code)
      index = index + 1
      continue
    }
    if index + 1 >= value.length() {
      return Err(InvalidLexiconText("trailing_escape"))
    }
    let escaped = value[index + 1].to_int()
    match escaped {
      92 => output_codes.push(92)
      116 => output_codes.push(9)
      110 => output_codes.push(10)
      114 => output_codes.push(13)
      _ => return Err(InvalidLexiconText("unknown_escape"))
    }
    index = index + 2
  }
  Ok(code_points_text(output_codes))
}

///|
fn separator_positions(line : String) -> Array[Int] {
  let positions : Array[Int] = []
  let mut escaped = false
  for index = 0; index < line.length(); index = index + 1 {
    let code = line[index].to_int()
    if escaped {
      escaped = false
    } else if code == 92 {
      escaped = true
    } else if code == 9 {
      positions.push(index)
    }
  }
  positions
}

///|
fn parse_lexicon_readings(field : String) -> Result[Array[String], PinyinError] {
  let readings : Array[String] = []
  for value in field.split(",") {
    let reading = value.to_owned()
    if reading.length() == 0 {
      return Err(InvalidLexiconText("empty_reading"))
    }
    readings.push(reading)
  }
  Ok(readings)
}

///|
/// Exports entries in stable insertion order for reviewable configuration.
pub fn lexicon_to_text(lexicon : Lexicon) -> String {
  let mut output = ""
  for entry in lexicon.values {
    output = output + escape_lexicon_field(entry.phrase_value) + "\t"
    for index = 0; index < entry.reading_values.length(); index = index + 1 {
      if index > 0 {
        output = output + ","
      }
      output = output +
        entry.reading_values[index].format(ToneNumbers, Lowercase)
    }
    output = output + "\n"
  }
  output
}

///|
/// Imports the stable text format through the same LexiconBuilder validation.
pub fn lexicon_from_text(text : String) -> Result[Lexicon, PinyinError] {
  let builder = LexiconBuilder::new()
  let mut line_number = 0
  for line_view in text.split("\n") {
    let line = line_view.to_owned()
    line_number = line_number + 1
    if line.length() == 0 {
      continue
    }
    let positions = separator_positions(line)
    if positions.length() == 0 {
      return Err(
        InvalidLexiconText("missing_separator:" + line_number.to_string()),
      )
    }
    if positions.length() > 1 {
      return Err(
        InvalidLexiconText("multiple_separators:" + line_number.to_string()),
      )
    }
    let separator = positions[0]
    let phrase_field = syllable_ascii_slice(line, 0, separator)
    let readings_field = syllable_ascii_slice(
      line,
      separator + 1,
      line.length(),
    )
    let phrase = match unescape_lexicon_field(phrase_field) {
      Err(error) => return Err(error)
      Ok(value) => value
    }
    let readings = match parse_lexicon_readings(readings_field) {
      Err(error) => return Err(error)
      Ok(value) => value
    }
    match builder.add(phrase, readings) {
      Err(error) => return Err(error)
      Ok(_) => ()
    }
  }
  Ok(builder.build())
}