///|
pub fn analyze_batch(
  cantus : Array[Note],
  variants : Array[Array[Note]],
  profile : RuleProfileConfig,
) -> Array[AnalysisSummary] {
  let result : Array[AnalysisSummary] = []
  for counter in variants {
    result.push(summarize(analyze_with_profile(cantus, counter, profile)))
  }
  result
}

///|
pub fn best_variant(
  cantus : Array[Note],
  variants : Array[Array[Note]],
  profile : RuleProfileConfig,
) -> Array[Note]? {
  if variants.length() == 0 {
    return None
  }
  let mut best = variants[0]
  let mut best_score = score_for(
    analyze_with_profile(cantus, best, profile),
    profile,
  )
  for variant in variants {
    let score = score_for(
      analyze_with_profile(cantus, variant, profile),
      profile,
    )
    if score > best_score {
      best = variant
      best_score = score
    }
  }
  Some(best)
}

///|
pub fn filter_by_range(
  notes : Array[Note],
  low : Int,
  high : Int,
) -> Array[Note] {
  let result : Array[Note] = []
  for note in notes {
    if low <= note.midi && note.midi <= high {
      result.push(note)
    }
  }
  result
}

///|
pub fn filter_by_duration(notes : Array[Note], duration : Int) -> Array[Note] {
  let result : Array[Note] = []
  for note in notes {
    if note.duration == duration {
      result.push(note)
    }
  }
  result
}

///|
pub fn filter_by_beat(
  meter : Meter,
  notes : Array[Note],
  strength : BeatStrength,
) -> Array[Note] {
  let result : Array[Note] = []
  for i, point in beat_points(meter, notes) {
    if point.strength == strength {
      result.push(notes[i])
    }
  }
  result
}

///|
pub fn merge_sequences(left : Array[Note], right : Array[Note]) -> Array[Note] {
  let result : Array[Note] = []
  for note in left {
    result.push(note)
  }
  for note in right {
    result.push(note)
  }
  result
}

///|
pub fn notes_with_labels(notes : Array[Note]) -> Array[String] {
  let result : Array[String] = []
  for note in notes {
    result.push(note_name(note))
  }
  result
}

///|
pub fn labels_text(notes : Array[Note]) -> String {
  notes_with_labels(notes).join(" ")
}

///|
pub fn event_count_between(notes : Array[Note], start : Int, end : Int) -> Int {
  let mut result = 0
  for note in notes {
    if start <= note.onset && note.onset < end {
      result += 1
    }
  }
  result
}

///|
pub fn duration_between(notes : Array[Note], start : Int, end : Int) -> Int {
  let mut result = 0
  for note in notes {
    let left = if note.onset < start { start } else { note.onset }
    let right = if note_end(note) > end { end } else { note_end(note) }
    if right > left {
      result += right - left
    }
  }
  result
}

///|
pub fn note_indices(notes : Array[Note]) -> Array[Int] {
  let result : Array[Int] = []
  for i in 0.. Note? {
  if index < 0 || index >= notes.length() {
    None
  } else {
    Some(notes[index])
  }
}

///|
pub fn replace_note(
  notes : Array[Note],
  index : Int,
  replacement : Note,
) -> Array[Note] {
  let result = notes.copy()
  if index >= 0 && index < result.length() {
    result[index] = replacement
  }
  result
}

///|
pub fn replace_pitch(
  notes : Array[Note],
  index : Int,
  midi : Int,
) -> Array[Note] {
  match note_at_index(notes, index) {
    None => notes
    Some(note) => replace_note(notes, index, note_with_pitch(note, midi))
  }
}

///|
pub fn edit_distance(left : Array[Note], right : Array[Note]) -> Int {
  let n = if left.length() < right.length() {
    left.length()
  } else {
    right.length()
  }
  let mut distance = if left.length() > right.length() {
    left.length() - right.length()
  } else {
    right.length() - left.length()
  }
  for i in 0.. Int {
  let n = if left.length() < right.length() {
    left.length()
  } else {
    right.length()
  }
  let mut distance = if left.length() > right.length() {
    left.length() - right.length()
  } else {
    right.length() - left.length()
  }
  for i in 0.. Array[Note] {
  let result : Array[Note] = []
  for note in notes {
    result.push(note_with_pitch(note, normalize_pitch(note.midi, low, high)))
  }
  result
}

///|
pub fn api_health(notes : Array[Note], meter : Meter) -> Bool {
  sequence_report(notes).is_valid() &&
  meter.is_valid() &&
  pitch_statistics(notes).count == notes.length()
}

///|
pub fn api_health_message(notes : Array[Note], meter : Meter) -> String {
  if api_health(notes, meter) {
    "input is ready for analysis"
  } else {
    "input requires normalization or timeline repair"
  }
}