///|
pub fn split_multi_allelic(
  record : VcfRecord,
  header~ : VcfHeader,
) -> Array[VcfRecord] {
  if record.alt_alleles.length() <= 1 {
    [record]
  } else {
    let records : Array[VcfRecord] = []
    for alt_index, alt in record.alt_alleles {
      records.push(copy_record_with_alt(record, header~, alt, alt_index~))
    }
    records
  }
}

///|
pub fn normalize_basic(record : VcfRecord) -> VcfRecord {
  if record.alt_alleles.length() != 1 {
    record
  } else {
    let (pos, ref_allele, alt_allele) = @normalize.trim_common(
      record.ref_allele,
      record.alt_alleles[0],
      pos=record.pos,
    )
    {
      chrom: record.chrom,
      pos,
      ids: record.ids,
      ref_allele,
      alt_alleles: [alt_allele],
      qual: record.qual,
      filters: record.filters,
      info: record.info,
      format_keys: record.format_keys,
      samples: record.samples,
    }
  }
}

///|
fn copy_record_with_alt(
  record : VcfRecord,
  header~ : VcfHeader,
  alt : String,
  alt_index~ : Int,
) -> VcfRecord {
  {
    chrom: record.chrom,
    pos: record.pos,
    ids: record.ids,
    ref_allele: record.ref_allele,
    alt_alleles: [alt],
    qual: record.qual,
    filters: record.filters,
    info: split_info_for_alt(
      record.info,
      header~,
      alt_index~,
      alt_count=record.alt_alleles.length(),
    ),
    format_keys: record.format_keys,
    samples: remap_samples(
      record.samples,
      header~,
      alt_index~,
      alt_count=record.alt_alleles.length(),
    ),
  }
}

///|
fn remap_samples(
  samples : Array[SampleCall],
  header~ : VcfHeader,
  alt_index~ : Int,
  alt_count~ : Int,
) -> Array[SampleCall] {
  samples.map(fn(sample) {
    let ploidy = sample_ploidy(sample)
    {
      name: sample.name,
      values: sample.values.map(fn(pair) {
        let (key, value) = pair
        (
          key,
          remap_format_value(
            key,
            value,
            header~,
            alt_index~,
            alt_count~,
            ploidy,
          ),
        )
      }),
    }
  })
}

///|
fn sample_ploidy(sample : SampleCall) -> Int? {
  for pair in sample.values {
    let (key, value) = pair
    if key == "GT" {
      match value {
        VcfValue::String(raw) =>
          if raw == "." || raw == "" {
            return None
          } else if raw.contains("/") {
            return Some(@text.split_char(raw, '/').length())
          } else if raw.contains("|") {
            return Some(@text.split_char(raw, '|').length())
          } else {
            return Some(1)
          }
        _ => return None
      }
    }
  }
  None
}

///|
fn remap_format_value(
  key : String,
  value : VcfValue,
  header~ : VcfHeader,
  alt_index~ : Int,
  alt_count~ : Int,
  ploidy : Int?,
) -> VcfValue {
  if key == "GT" {
    match value {
      VcfValue::String(raw) => VcfValue::String(remap_genotype(raw, alt_index~))
      _ => VcfValue::Missing
    }
  } else {
    match find_format_cardinality(header, key) {
      Some(cardinality) =>
        project_cardinality_value(
          value,
          cardinality,
          alt_index~,
          alt_count~,
          ploidy,
        )
      None => value
    }
  }
}

///|
fn find_format_cardinality(header : VcfHeader, key : String) -> Cardinality? {
  for item in header.meta {
    match item {
      MetaLine::Format(def) if def.id == key => return Some(def.number)
      _ => ()
    }
  }
  None
}

///|
fn project_cardinality_value(
  value : VcfValue,
  cardinality : Cardinality,
  alt_index~ : Int,
  alt_count~ : Int,
  ploidy : Int?,
) -> VcfValue {
  match cardinality {
    Cardinality::PerAlt => project_list_item(value, alt_index)
    Cardinality::PerAllele => project_reference_and_alt(value, alt_index~)
    Cardinality::PerGenotype =>
      project_genotype_likelihoods(value, alt_index~, alt_count~, ploidy)
    _ => value
  }
}

///|
fn project_list_item(value : VcfValue, index : Int) -> VcfValue {
  match value {
    VcfValue::List(values) if index < values.length() => values[index]
    VcfValue::List(_) => VcfValue::Missing
    _ => value
  }
}

///|
fn project_reference_and_alt(value : VcfValue, alt_index~ : Int) -> VcfValue {
  match value {
    VcfValue::List(values) if alt_index + 1 < values.length() =>
      VcfValue::List([values[0], values[alt_index + 1]])
    VcfValue::List(_) => VcfValue::Missing
    _ => value
  }
}

///|
fn project_genotype_likelihoods(
  value : VcfValue,
  alt_index~ : Int,
  alt_count~ : Int,
  ploidy : Int?,
) -> VcfValue {
  match value {
    VcfValue::List(values) => {
      let target = alt_index + 1
      let first = diploid_genotype_index(0, target)
      let second = diploid_genotype_index(target, target)
      if ploidy == Some(2) && second < values.length() && target <= alt_count {
        VcfValue::List([values[0], values[first], values[second]])
      } else {
        VcfValue::Missing
      }
    }
    _ => value
  }
}

///|
fn diploid_genotype_index(left : Int, right : Int) -> Int {
  let low = if left <= right { left } else { right }
  let high = if left <= right { right } else { left }
  high * (high + 1) / 2 + low
}

///|
fn remap_genotype(raw : String, alt_index~ : Int) -> String {
  let target = alt_index + 1
  if raw.contains("/") {
    remap_genotype_parts(raw, '/', target)
  } else if raw.contains("|") {
    remap_genotype_parts(raw, '|', target)
  } else {
    remap_genotype_allele(raw, target)
  }
}

///|
fn remap_genotype_parts(raw : String, separator : Char, target : Int) -> String {
  let parts = @text.split_char(raw, separator)
  if parts.length() == 0 {
    "."
  } else {
    let delimiter = if separator == '/' { "/" } else { "|" }
    parts.map(fn(part) { remap_genotype_allele(part, target) }).join(delimiter)
  }
}

///|
fn remap_genotype_allele(raw : String, target : Int) -> String {
  if raw == "." || raw == "" {
    "."
  } else {
    match parse_non_negative_int(raw) {
      Some(index) => if index == target { "1" } else { "0" }
      None => "."
    }
  }
}

///|
fn split_info_for_alt(
  info : Array[(String, VcfValue)],
  header~ : VcfHeader,
  alt_index~ : Int,
  alt_count~ : Int,
) -> Array[(String, VcfValue)] {
  let split : Array[(String, VcfValue)] = []
  for pair in info {
    let (key, value) = pair
    split.push(
      (
        key,
        match find_info_cardinality(header, key) {
          Some(cardinality) =>
            project_cardinality_value(
              value,
              cardinality,
              alt_index~,
              alt_count~,
              None,
            )
          None => value
        },
      ),
    )
  }
  split
}

///|
fn find_info_cardinality(header : VcfHeader, key : String) -> Cardinality? {
  for item in header.meta {
    match item {
      MetaLine::Info(def) if def.id == key => return Some(def.number)
      _ => ()
    }
  }
  None
}