///|
pub fn pipeline_map(
  document : VcfDocument,
  transform : (VcfRecord) -> VcfRecord,
) -> VcfDocument {
  { header: document.header, records: document.records.map(transform) }
}

///|
pub fn pipeline_filter(
  document : VcfDocument,
  keep : (VcfRecord) -> Bool,
) -> VcfDocument {
  { header: document.header, records: document.records.filter(keep) }
}

///|
pub fn pipeline_take(document : VcfDocument, count : Int) -> VcfDocument {
  {
    header: document.header,
    records: if count <= 0 {
      []
    } else {
      document.records[:min_pipeline(count, document.records.length())].to_owned()
    },
  }
}

///|
pub fn pipeline_drop(document : VcfDocument, count : Int) -> VcfDocument {
  {
    header: document.header,
    records: if count <= 0 {
      document.records
    } else if count >= document.records.length() {
      []
    } else {
      document.records[count:].to_owned()
    },
  }
}

///|
pub fn pipeline_limit_by_contig(
  document : VcfDocument,
  chrom : String,
) -> VcfDocument {
  pipeline_filter(document, fn(record) { record.chrom == chrom })
}

///|
pub fn pipeline_limit_by_interval(
  document : VcfDocument,
  chrom : String,
  interval : GenomicInterval?,
) -> VcfDocument {
  pipeline_filter(document, fn(record) {
    record.chrom == chrom && interval_contains(interval, record.pos)
  })
}

///|
pub fn pipeline_sort(document : VcfDocument) -> VcfDocument {
  { header: document.header, records: sort_records(document.records) }
}

///|
pub fn pipeline_dedupe(document : VcfDocument) -> VcfDocument {
  let records : Array[VcfRecord] = []
  let keys : Array[String] = []
  for record in document.records {
    let key = record_key(record)
    if !pipeline_contains(keys, key) {
      keys.push(key)
      records.push(record)
    }
  }
  { header: document.header, records }
}

///|
pub fn pipeline_concat(documents : Array[VcfDocument]) -> VcfDocument? {
  if documents.length() == 0 {
    None
  } else {
    let first = documents[0]
    let records : Array[VcfRecord] = []
    for document in documents {
      if document.header == first.header {
        for record in document.records {
          records.push(record)
        }
      } else {
        return None
      }
    }
    Some({ header: first.header, records })
  }
}

///|
pub fn pipeline_split_by_filter(
  document : VcfDocument,
  filter : String,
) -> (VcfDocument, VcfDocument) {
  let yes : Array[VcfRecord] = []
  let no : Array[VcfRecord] = []
  for record in document.records {
    if record.filters.contains(filter) {
      yes.push(record)
    } else {
      no.push(record)
    }
  }
  (
    { header: document.header, records: yes },
    { header: document.header, records: no },
  )
}

///|
pub fn pipeline_split_by_qualification(
  document : VcfDocument,
  threshold : Double,
) -> (VcfDocument, VcfDocument) {
  let yes : Array[VcfRecord] = []
  let no : Array[VcfRecord] = []
  for record in document.records {
    match record.qual {
      Some(value) if value >= threshold => yes.push(record)
      _ => no.push(record)
    }
  }
  (
    { header: document.header, records: yes },
    { header: document.header, records: no },
  )
}

///|
pub fn pipeline_apply_normalization(document : VcfDocument) -> VcfDocument {
  let records : Array[VcfRecord] = []
  for record in document.records {
    match normalize_record(record) {
      Changed(value) | Unchanged(value) => records.push(value)
      Invalid(_) => records.push(record)
    }
  }
  { header: document.header, records }
}

///|
pub fn pipeline_split_multiallelic(document : VcfDocument) -> VcfDocument {
  let records : Array[VcfRecord] = []
  for record in document.records {
    for split in split_multi_allelic(record, header=document.header) {
      records.push(split)
    }
  }
  { header: document.header, records }
}

///|
pub fn pipeline_project_info(
  document : VcfDocument,
  keys : Array[String],
) -> VcfDocument {
  pipeline_map(document, fn(record) { select_info_fields(record, keys) })
}

///|
pub fn pipeline_project_samples(
  document : VcfDocument,
  names : Array[String],
) -> VcfDocument raise ParseError {
  select_document_samples(document, names)
}

///|
pub fn pipeline_validate(
  document : VcfDocument,
  strict : Bool,
) -> ValidationReport {
  validate_document(document, options=ValidationOptions::{ strict, })
}

///|
pub fn pipeline_stats(document : VcfDocument) -> VcfStats {
  summarize(document)
}

///|
pub fn pipeline_count(document : VcfDocument) -> Int {
  document.records.length()
}

///|
pub fn pipeline_is_empty(document : VcfDocument) -> Bool {
  document.records.length() == 0
}

///|
pub fn pipeline_first(document : VcfDocument) -> VcfRecord? {
  if document.records.length() == 0 {
    None
  } else {
    Some(document.records[0])
  }
}

///|
pub fn pipeline_last(document : VcfDocument) -> VcfRecord? {
  if document.records.length() == 0 {
    None
  } else {
    Some(document.records[document.records.length() - 1])
  }
}

///|
pub fn pipeline_contigs(document : VcfDocument) -> Array[String] {
  let names : Array[String] = []
  for record in document.records {
    if !pipeline_contains(names, record.chrom) {
      names.push(record.chrom)
    }
  }
  names
}

///|
pub fn pipeline_filters(document : VcfDocument) -> Array[String] {
  let names : Array[String] = []
  for record in document.records {
    for filter in record.filters {
      if !pipeline_contains(names, filter) {
        names.push(filter)
      }
    }
  }
  names
}

///|
pub fn pipeline_info_keys(document : VcfDocument) -> Array[String] {
  let names : Array[String] = []
  for record in document.records {
    for pair in record.info {
      if !pipeline_contains(names, pair.0) {
        names.push(pair.0)
      }
    }
  }
  names
}

///|
pub fn pipeline_record_counts_by_class(
  document : VcfDocument,
) -> Array[(VariantClass, Int)] {
  let counts : Array[(VariantClass, Int)] = []
  for record in document.records {
    let class = record_variant_class(record)
    let mut found = false
    for index, pair in counts {
      if pair.0 == class {
        counts[index] = (class, pair.1 + 1)
        found = true
        break
      }
    }
    if !found {
      counts.push((class, 1))
    }
  }
  counts
}

///|
pub fn pipeline_pairwise(
  left : VcfDocument,
  right : VcfDocument,
) -> Array[RecordDiff] {
  diff_documents(left, right)
}

///|
pub fn pipeline_merge(
  left : VcfDocument,
  right : VcfDocument,
) -> VcfDocument raise ParseError {
  merge_documents(left, right)
}

///|
pub fn pipeline_query(
  document : VcfDocument,
  chrom : String,
  start : Int,
  end : Int,
) -> Array[VcfRecord] {
  query_index(build_index(document), chrom, start, end)
}

///|
pub fn pipeline_format(document : VcfDocument) -> String {
  format_document(document)
}

///|
pub fn pipeline_round_trip(
  document : VcfDocument,
) -> VcfDocument raise ParseError {
  parse_document(format_document(document))
}

///|
fn pipeline_contains(values : Array[String], wanted : String) -> Bool {
  for value in values {
    if value == wanted {
      return true
    }
  }
  false
}

///|
fn min_pipeline(left : Int, right : Int) -> Int {
  if left < right {
    left
  } else {
    right
  }
}