///|
/// A flat row suitable for TSV export, spreadsheets, and lightweight UIs.
pub struct FeatureTableRow {
  index : Int
  key : String
  start : Int
  end : Int
  length : Int
  strand : String
  remote : Bool
  fuzzy : Bool
  qualifier_count : Int
  label : String
} derive(Debug, Eq)

///|
/// A key-level annotation summary used by dashboards and batch reports.
pub struct FeatureKeySummary {
  key : String
  count : Int
  total_bases : Int
  remote_count : Int
  fuzzy_count : Int
  qualifier_count : Int
} derive(Debug, Eq)

///|
/// Convert a parsed feature to a stable tabular record.
pub fn feature_table_row(index : Int, feature : Feature) -> FeatureTableRow {
  let location = summarize_location(feature.location)
  let strand = if feature.location.complement { "reverse" } else { "forward" }
  let label = match feature.qualifier("label") {
    Some(value) => value
    None =>
      match feature.qualifier("gene") {
        Some(value) => value
        None =>
          match feature.qualifier("locus_tag") {
            Some(value) => value
            None => feature.key
          }
      }
  }
  {
    index,
    key: feature.key,
    start: location.minimum,
    end: location.maximum,
    length: location.length,
    strand,
    remote: location.remote_segments > 0,
    fuzzy: location.fuzzy,
    qualifier_count: feature.qualifiers.length(),
    label,
  }
}

///|
/// Build rows in source order; source order is preserved for reproducible CSV.
pub fn feature_table(record : Record) -> Array[FeatureTableRow] {
  let rows : Array[FeatureTableRow] = []
  for index, feature in record.features {
    rows.push(feature_table_row(index, feature))
  }
  rows
}

///|
/// Select rows by key without rebuilding the complete feature index.
pub fn feature_table_by_key(
  record : Record,
  key : String,
) -> Array[FeatureTableRow] {
  feature_table(record).filter(fn(row) { row.key == key })
}

///|
/// Summarize all features by key in first-seen order.
pub fn feature_key_summary(record : Record) -> Array[FeatureKeySummary] {
  let result : Array[FeatureKeySummary] = []
  for feature in record.features {
    let location = summarize_location(feature.location)
    let qualifier_count = feature.qualifiers.length()
    match result.search_by(fn(item) { item.key == feature.key }) {
      Some(index) => {
        let item = result[index]
        result[index] = {
          key: item.key,
          count: item.count + 1,
          total_bases: item.total_bases + location.length,
          remote_count: item.remote_count +
          (if location.remote_segments > 0 { 1 } else { 0 }),
          fuzzy_count: item.fuzzy_count + (if location.fuzzy { 1 } else { 0 }),
          qualifier_count: item.qualifier_count + qualifier_count,
        }
      }
      None =>
        result.push({
          key: feature.key,
          count: 1,
          total_bases: location.length,
          remote_count: if location.remote_segments > 0 {
            1
          } else {
            0
          },
          fuzzy_count: if location.fuzzy {
            1
          } else {
            0
          },
          qualifier_count,
        })
    }
  }
  result
}

///|
/// Return the percentage of a record covered by features of one key.
pub fn feature_key_coverage(record : Record, key : String) -> Double {
  let coordinates : Array[Int] = []
  for feature in record.features {
    if feature.key == key {
      for coordinate in location_coordinates(feature.location) {
        if !coordinates.contains(coordinate) {
          coordinates.push(coordinate)
        }
      }
    }
  }
  if record.origin.length() == 0 {
    0.0
  } else {
    coordinates.length().to_double() *
    100.0 /
    record.origin.length().to_double()
  }
}

///|
/// Locate the first feature covering a one-based sequence coordinate.
pub fn feature_at_coordinate(
  record : Record,
  coordinate : Int,
) -> Array[FeatureTableRow] {
  if coordinate < 1 {
    return []
  }
  feature_table(record).filter(fn(row) {
    row.start <= coordinate && coordinate <= row.end
  })
}

///|
/// Export feature rows as RFC-style tab-separated text with a header.
pub fn feature_table_tsv(record : Record) -> String {
  let out = StringBuilder()
  out.write_string(
    "index\tkey\tstart\tend\tlength\tstrand\tremote\tfuzzy\tqualifiers\tlabel\n",
  )
  for row in feature_table(record) {
    out.write_string(row.index.to_string())
    out.write_char('\t')
    out.write_string(row.key)
    out.write_char('\t')
    out.write_string(row.start.to_string())
    out.write_char('\t')
    out.write_string(row.end.to_string())
    out.write_char('\t')
    out.write_string(row.length.to_string())
    out.write_char('\t')
    out.write_string(row.strand)
    out.write_char('\t')
    out.write_string(row.remote.to_string())
    out.write_char('\t')
    out.write_string(row.fuzzy.to_string())
    out.write_char('\t')
    out.write_string(row.qualifier_count.to_string())
    out.write_char('\t')
    out.write_string(
      row.label.replace_all(old="\t", new=" ").replace_all(old="\n", new=" "),
    )
    out.write_char('\n')
  }
  out.to_string()
}

///|
/// Export key summaries as stable TSV for command-line data processing.
pub fn feature_key_summary_tsv(record : Record) -> String {
  let out = StringBuilder()
  out.write_string("key\tcount\ttotal_bases\tremote\tfuzzy\tqualifiers\n")
  for item in feature_key_summary(record) {
    out.write_string(item.key)
    out.write_char('\t')
    out.write_string(item.count.to_string())
    out.write_char('\t')
    out.write_string(item.total_bases.to_string())
    out.write_char('\t')
    out.write_string(item.remote_count.to_string())
    out.write_char('\t')
    out.write_string(item.fuzzy_count.to_string())
    out.write_char('\t')
    out.write_string(item.qualifier_count.to_string())
    out.write_char('\n')
  }
  out.to_string()
}

///|
pub fn FeatureTableRow::to_json_string(self : FeatureTableRow) -> String {
  "{\"index\":" +
  self.index.to_string() +
  ",\"key\":" +
  json_string(self.key) +
  ",\"start\":" +
  self.start.to_string() +
  ",\"end\":" +
  self.end.to_string() +
  ",\"length\":" +
  self.length.to_string() +
  ",\"strand\":" +
  json_string(self.strand) +
  ",\"remote\":" +
  self.remote.to_string() +
  ",\"fuzzy\":" +
  self.fuzzy.to_string() +
  ",\"qualifier_count\":" +
  self.qualifier_count.to_string() +
  ",\"label\":" +
  json_string(self.label) +
  "}"
}

///|
pub fn FeatureKeySummary::to_json_string(self : FeatureKeySummary) -> String {
  "{\"key\":" +
  json_string(self.key) +
  ",\"count\":" +
  self.count.to_string() +
  ",\"total_bases\":" +
  self.total_bases.to_string() +
  ",\"remote_count\":" +
  self.remote_count.to_string() +
  ",\"fuzzy_count\":" +
  self.fuzzy_count.to_string() +
  ",\"qualifier_count\":" +
  self.qualifier_count.to_string() +
  "}"
}