///|
/// A normalized qualifier lookup result.
pub struct QualifierMatch {
  feature_key : String
  qualifier_name : String
  value : String
  feature_start : Int
  feature_end : Int
} derive(Debug, Eq)

///|
pub fn find_qualifier_matches(
  record : Record,
  name : String,
) -> Array[QualifierMatch] {
  let result : Array[QualifierMatch] = []
  for feature in record.features {
    for qualifier in feature.qualifiers {
      match qualifier.value {
        Some(value) if qualifier.name == name =>
          result.push({
            feature_key: feature.key,
            qualifier_name: name,
            value,
            feature_start: feature_start(feature),
            feature_end: feature_end(feature),
          })
        _ => ()
      }
    }
  }
  result
}

///|
pub fn find_qualifier_value_matches(
  record : Record,
  name : String,
  value : String,
) -> Array[QualifierMatch] {
  find_qualifier_matches(record, name).filter(fn(item) { item.value == value })
}

///|
pub fn QualifierMatch::to_json_string(self : QualifierMatch) -> String {
  "{\"feature_key\":" +
  json_string(self.feature_key) +
  ",\"qualifier_name\":" +
  json_string(self.qualifier_name) +
  ",\"value\":" +
  json_string(self.value) +
  ",\"feature_start\":" +
  self.feature_start.to_string() +
  ",\"feature_end\":" +
  self.feature_end.to_string() +
  "}"
}

///|
pub fn qualifier_match_json(record : Record, name : String) -> String {
  array_json(
    find_qualifier_matches(record, name).map(fn(item) { item.to_json_string() }),
  )
}

///|
pub fn qualifier_value(
  record : Record,
  key : String,
  name : String,
) -> Array[String] {
  feature_qualifier_values(record.features_by_key(key), name)
}

///|
pub fn qualifier_values_for_feature(
  feature : Feature,
  name : String,
) -> Array[String] {
  feature.qualifiers.filter_map(fn(qualifier) {
    if qualifier.name == name {
      qualifier.value
    } else {
      None
    }
  })
}

///|
pub fn qualifier_names_for_record(record : Record) -> Array[String] {
  let result : Array[String] = []
  for feature in record.features {
    for name in feature_qualifier_names(feature) {
      if !result.contains(name) {
        result.push(name)
      }
    }
  }
  result
}

///|
pub fn qualifier_name_count(record : Record, name : String) -> Int {
  find_qualifier_matches(record, name).length()
}

///|
pub fn qualifier_value_count(
  record : Record,
  name : String,
  value : String,
) -> Int {
  find_qualifier_value_matches(record, name, value).length()
}

///|
pub fn qualifier_exists(record : Record, name : String) -> Bool {
  qualifier_name_count(record, name) > 0
}

///|
pub fn qualifier_value_exists(
  record : Record,
  name : String,
  value : String,
) -> Bool {
  qualifier_value_count(record, name, value) > 0
}

///|
pub fn qualifier_histogram(record : Record) -> Array[(String, Int)] {
  feature_qualifier_counts(record)
}

///|
pub fn sorted_qualifier_names(record : Record) -> Array[String] {
  let names = qualifier_names_for_record(record)
  names.sort()
  names
}

///|
pub fn sorted_feature_keys(record : Record) -> Array[String] {
  let keys = build_feature_index(record).keys()
  keys.sort()
  keys
}

///|
pub fn qualifier_search(
  record : Record,
  text : String,
) -> Array[QualifierMatch] {
  let needle = text.to_lower()
  find_qualifier_matches(record, "note").filter(fn(item) {
    item.value.to_lower().contains(needle)
  })
}

///|
pub fn feature_search(record : Record, text : String) -> Array[Feature] {
  let needle = text.to_lower()
  record.features.filter(fn(feature) {
    feature.key.to_lower().contains(needle) ||
    feature.qualifiers.any(fn(qualifier) {
      match qualifier.value {
        Some(value) => value.to_lower().contains(needle)
        None => false
      }
    })
  })
}

///|
pub fn record_gene_names(record : Record) -> Array[String] {
  feature_qualifier_values(record.features_by_key("gene"), "gene")
}

///|
pub fn record_products(record : Record) -> Array[String] {
  feature_qualifier_values(record.features_by_key("CDS"), "product")
}

///|
pub fn record_translations(record : Record) -> Array[String] {
  feature_qualifier_values(record.features_by_key("CDS"), "translation")
}

///|
pub fn record_organisms(record : Record) -> Array[String] {
  feature_qualifier_values(record.features_by_key("source"), "organism")
}

///|
pub fn record_locus_tags(record : Record) -> Array[String] {
  feature_qualifier_values(record.features, "locus_tag")
}

///|
pub fn record_notes(record : Record) -> Array[String] {
  feature_qualifier_values(record.features, "note")
}

///|
pub fn record_references(record : Record) -> Array[String] {
  feature_qualifier_values(record.features, "citation")
}

///|
pub fn qualifier_match_text(item : QualifierMatch) -> String {
  item.feature_key +
  ":" +
  item.qualifier_name +
  "=" +
  item.value +
  " [" +
  item.feature_start.to_string() +
  ".." +
  item.feature_end.to_string() +
  "]"
}

///|
pub fn record_qualifier_report(record : Record) -> String {
  let out = StringBuilder()
  for name in sorted_qualifier_names(record) {
    out.write_string(
      name + ": " + qualifier_name_count(record, name).to_string() + "\n",
    )
  }
  out.to_string()
}

///|
pub fn record_feature_report(record : Record) -> String {
  let out = StringBuilder()
  for key in sorted_feature_keys(record) {
    let features = record.features_by_key(key)
    out.write_string(key + ": " + features.length().to_string() + "\n")
    for feature in features {
      out.write_string("  " + feature_summary_text(feature) + "\n")
    }
  }
  out.to_string()
}

///|
pub fn record_annotation_report(record : Record) -> String {
  record_feature_report(record) +
  "qualifiers:\n" +
  record_qualifier_report(record)
}

///|
pub fn qualifier_values_json(record : Record, name : String) -> String {
  array_json(
    feature_qualifier_values(record.features, name).map(fn(value) {
      json_string(value)
    }),
  )
}

///|
pub fn gene_summary_json(record : Record) -> String {
  "{\"genes\":" +
  qualifier_values_json(record, "gene") +
  ",\"locus_tags\":" +
  qualifier_values_json(record, "locus_tag") +
  ",\"products\":" +
  qualifier_values_json(record, "product") +
  "}"
}

///|
pub fn translation_lengths(record : Record) -> Array[Int] {
  record_translations(record).map(fn(value) { value.length() })
}

///|
pub fn longest_translation(record : Record) -> String? {
  let values = record_translations(record)
  if values.length() == 0 {
    None
  } else {
    Some(
      values.fold(init=values[0], fn(best, value) {
        if value.length() > best.length() {
          value
        } else {
          best
        }
      }),
    )
  }
}

///|
pub fn shortest_translation(record : Record) -> String? {
  let values = record_translations(record)
  if values.length() == 0 {
    None
  } else {
    Some(
      values.fold(init=values[0], fn(best, value) {
        if value.length() < best.length() {
          value
        } else {
          best
        }
      }),
    )
  }
}