///|
fn parse_strand(raw : String) -> Strand {
  match raw {
    "+" => Plus
    "-" => Minus
    "?" => Unknown
    _ => NotApplicable
  }
}

///|
fn optional_field(raw : String) -> String? {
  if raw == "." || raw.is_empty() {
    None
  } else {
    Some(raw)
  }
}

///|
fn parse_feature_line(
  line : String,
  line_number : Int,
  format : Format,
) -> Feature raise {
  let fields = line.split("\t").to_array()
  guard fields.length() == 9 else {
    fail("line \{line_number}: expected 9 tab-separated fields")
  }
  let start = @string.parse_int(fields[3].trim())
  let end = @string.parse_int(fields[4].trim())
  guard start <= end else { fail("line \{line_number}: start must be <= end") }
  let attributes = match format {
    GFF3 => parse_gff3_attributes(fields[8].to_owned())
    GTF => parse_gtf_attributes(fields[8].to_owned())
  }
  {
    seqid: fields[0].to_owned(),
    source: fields[1].to_owned(),
    feature_type: fields[2].to_owned(),
    start,
    end,
    score: optional_field(fields[5].to_owned()),
    strand: parse_strand(fields[6].to_owned()),
    phase: optional_field(fields[7].to_owned()),
    attributes,
  }
}

///|
fn parse_annotation(text : String, format : Format) -> Annotation raise {
  let features : Array[Feature] = []
  let diagnostics : Array[Diagnostic] = []
  for index, line_view in text.split("\n").to_array() {
    let line = line_view.trim(chars="\r\n").to_owned()
    if !line.is_empty() && !line.has_prefix("#") {
      features.push(parse_feature_line(line, index + 1, format))
    }
  }
  { format, features, diagnostics }
}

///|
pub fn parse_gff3(text : String) -> Annotation raise {
  parse_annotation(text, GFF3)
}

///|
pub fn parse_gtf(text : String) -> Annotation raise {
  parse_annotation(text, GTF)
}

///|
pub fn parse_auto(text : String) -> Annotation raise {
  for line_view in text.split("\n") {
    let line = line_view.trim(chars="\r\n").to_owned()
    if line.has_prefix("##gff-version") {
      return parse_gff3(text)
    }
    if !line.is_empty() && !line.has_prefix("#") {
      if line.contains("gene_id \"") || line.contains("transcript_id \"") {
        return parse_gtf(text)
      } else {
        return parse_gff3(text)
      }
    }
  }
  parse_gff3(text)
}