///|
fn is_transcript_feature(feature : Feature) -> Bool {
  feature.feature_type == "mRNA" ||
  feature.feature_type == "transcript" ||
  feature.feature_type == "lnc_RNA" ||
  feature.feature_type == "ncRNA"
}

///|
fn child_kind(feature : Feature) -> String {
  match feature.feature_type {
    "exon" => "exon"
    "CDS" => "cds"
    "five_prime_UTR" => "utr"
    "three_prime_UTR" => "utr"
    "UTR" => "utr"
    _ => "other"
  }
}

///|
fn first_some(left : String?, right : String?) -> String? {
  match left {
    Some(_) => left
    None => right
  }
}

///|
fn build_transcript(
  annotation : Annotation,
  tx : Feature,
  tx_id : String,
) -> TranscriptModel {
  let exons : Array[Feature] = []
  let cds : Array[Feature] = []
  let utrs : Array[Feature] = []
  let others : Array[Feature] = []
  for feature in annotation.features {
    let belongs = match annotation.format {
      GFF3 => feature.attribute("Parent") == Some(tx_id)
      GTF =>
        feature.attribute("transcript_id") == Some(tx_id) &&
        feature.feature_type != "transcript"
    }
    if belongs {
      match child_kind(feature) {
        "exon" => exons.push(feature)
        "cds" => cds.push(feature)
        "utr" => utrs.push(feature)
        _ => others.push(feature)
      }
    }
  }
  {
    id: tx_id,
    name: first_some(tx.attribute("Name"), tx.attribute("transcript_name")),
    feature: tx,
    exons,
    cds,
    utrs,
    others,
  }
}

///|
fn build_gene(
  annotation : Annotation,
  gene : Feature,
  gene_id : String,
) -> GeneModel {
  let transcripts : Array[TranscriptModel] = []
  let others : Array[Feature] = []
  for feature in annotation.features {
    match annotation.format {
      GFF3 =>
        if is_transcript_feature(feature) &&
          feature.attribute("Parent") == Some(gene_id) {
          let tx_id = feature.attribute("ID").unwrap_or(feature.feature_type)
          transcripts.push(build_transcript(annotation, feature, tx_id))
        } else if feature.attribute("Parent") == Some(gene_id) {
          others.push(feature)
        }
      GTF =>
        if is_transcript_feature(feature) &&
          feature.attribute("gene_id") == Some(gene_id) {
          let tx_id = feature
            .attribute("transcript_id")
            .unwrap_or(feature.feature_type)
          transcripts.push(build_transcript(annotation, feature, tx_id))
        }
    }
  }
  {
    id: gene_id,
    name: first_some(gene.attribute("Name"), gene.attribute("gene_name")),
    feature: gene,
    transcripts,
    others,
  }
}

///|
pub fn Annotation::to_gene_models(self : Annotation) -> Array[GeneModel] {
  let genes : Array[GeneModel] = []
  for feature in self.features {
    match self.format {
      GFF3 =>
        if feature.feature_type == "gene" {
          let gene_id = feature.attribute("ID").unwrap_or(feature.seqid)
          genes.push(build_gene(self, feature, gene_id))
        }
      GTF =>
        if feature.feature_type == "gene" {
          let gene_id = feature.attribute("gene_id").unwrap_or(feature.seqid)
          genes.push(build_gene(self, feature, gene_id))
        }
    }
  }
  genes
}