///|
fn find_name_index(names : ArrayView[String], target : String) -> Int? {
  for index, name in names {
    if name == target {
      return Some(index)
    }
  }
  None
}

///|
fn append_vlq(output : StringBuilder, value : Int) -> Unit {
  output.write_string(encode_vlq(value))
}

///|
/// Encode decoded mappings into the canonical ECMA-426 delta representation.
///
/// Mappings must be sorted by generated line and column. Original coordinates
/// and source indices must be non-negative. Named mappings must refer to an
/// entry in `names`.
pub fn encode_mappings(
  mappings : ArrayView[Mapping],
  names~ : ArrayView[String],
) -> String raise SourceMapError {
  let output = StringBuilder()
  let mut generated_line = 0
  let mut generated_column = 0
  let mut source_index = 0
  let mut original_line = 0
  let mut original_column = 0
  let mut name_index = 0
  let mut segment_on_line = false
  let mut previous : Position? = None
  for mapping in mappings {
    if !mapping.generated.is_valid() {
      raise InvalidDocument(message="generated positions must be non-negative")
    }
    match previous {
      Some(position) =>
        if position.compare(mapping.generated) > 0 {
          raise InvalidDocument(
            message="mappings must be sorted by generated position",
          )
        }
      None => ()
    }
    while generated_line < mapping.generated.line {
      output.write_char(';')
      generated_line = generated_line + 1
      generated_column = 0
      segment_on_line = false
    }
    if segment_on_line {
      output.write_char(',')
    }
    append_vlq(output, mapping.generated.column - generated_column)
    generated_column = mapping.generated.column
    match mapping.original {
      None =>
        if mapping.name is Some(_) {
          raise InvalidDocument(
            message="generated-only mappings cannot carry a name",
          )
        }
      Some(original) => {
        if !original.is_valid() {
          raise InvalidDocument(
            message="original positions and source indices must be non-negative",
          )
        }
        append_vlq(output, original.source_index - source_index)
        append_vlq(output, original.line - original_line)
        append_vlq(output, original.column - original_column)
        source_index = original.source_index
        original_line = original.line
        original_column = original.column
        match mapping.name {
          None => ()
          Some(name) =>
            match find_name_index(names, name) {
              None =>
                raise InvalidDocument(
                  message="mapping name is not present in the names table",
                )
              Some(index) => {
                append_vlq(output, index - name_index)
                name_index = index
              }
            }
        }
      }
    }
    segment_on_line = true
    previous = Some(mapping.generated)
  }
  output.to_string()
}

///|
/// Return a copy sorted by generated position.
///
/// Sorting is stable for entries that share the same generated position.
pub fn sort_mappings(mappings : ArrayView[Mapping]) -> Array[Mapping] {
  let result = mappings.to_owned()
  result.sort_by((left, right) => left.generated.compare(right.generated))
  result
}

///|
/// Return true when mappings are in generated order.
pub fn mappings_are_sorted(mappings : ArrayView[Mapping]) -> Bool {
  if mappings.length() < 2 {
    return true
  }
  for index in 1.. 0 {
      return false
    }
  }
  true
}