///|
pub(all) struct WeightedEvent {
  key : String
  weight : Int
} derive(Eq, Debug)

///|
pub(all) struct WeightedStreamSummary {
  event_rows : Int
  total_weight : Int
  unique_keys : Int
  count_min : CountMinSketch
  exact : ExactCounter
  topk : Array[TopKItem]
} derive(Eq, Debug)

///|
pub fn weighted_event(key : String, weight : Int) -> WeightedEvent {
  { key, weight: sketch_max(0, weight) }
}

///|
pub fn parse_weighted_events(input : String) -> Array[WeightedEvent] {
  let events : Array[WeightedEvent] = Array::new()
  for token in parse_events(input) {
    match parse_weighted_token(token) {
      Some(event) => events.push(event)
      None => ()
    }
  }
  events
}

///|
pub fn parse_weighted_token(token : String) -> WeightedEvent? {
  let key = StringBuilder()
  let value = StringBuilder()
  let mut seen_sep = false
  for ch in token.iter() {
    if !seen_sep && (ch == ':' || ch == '=') {
      seen_sep = true
    } else if seen_sep {
      value.write_char(ch)
    } else {
      key.write_char(ch)
    }
  }
  let key_text = key.to_string()
  if key_text.is_empty() {
    return None
  }
  if !seen_sep {
    return Some({ key: key_text, weight: 1 })
  }
  let value_text = value.to_string()
  let weight = try @string.parse_int(value_text) catch {
    _ => 1
  } noraise {
    parsed => parsed
  }
  Some({ key: key_text, weight: sketch_max(0, weight) })
}

///|
pub fn weighted_summary(
  events : Array[WeightedEvent],
  width : Int,
  depth : Int,
  k : Int,
) -> WeightedStreamSummary {
  let mut cms = count_min_new(width, depth)
  let mut exact = exact_counter_new()
  let expanded : Array[String] = Array::new()
  let mut total_weight = 0
  for event in events {
    total_weight += event.weight
    cms = count_min_add_count(cms, event.key, event.weight)
    for _ in 0.. WeightedStreamSummary {
  weighted_summary(parse_weighted_events(input), 128, 5, 5)
}

///|
pub fn weighted_estimate(summary : WeightedStreamSummary, key : String) -> Int {
  count_min_estimate(summary.count_min, key)
}

///|
pub fn weighted_summary_markdown(summary : WeightedStreamSummary) -> String {
  let out = StringBuilder()
  out.write_string("# Weighted Stream Summary\n\n")
  out.write_string("| metric | value |\n| --- | ---: |\n")
  out.write_string("| event rows | " + summary.event_rows.to_string() + " |\n")
  out.write_string(
    "| total weight | " + summary.total_weight.to_string() + " |\n",
  )
  out.write_string(
    "| unique keys | " + summary.unique_keys.to_string() + " |\n",
  )
  out.write_string(
    "| sketch width | " + summary.count_min.width.to_string() + " |\n",
  )
  out.write_string(
    "| sketch depth | " + summary.count_min.depth.to_string() + " |\n\n",
  )
  out.write_string(
    "| key | exact count | estimated count |\n| --- | ---: | ---: |\n",
  )
  for item in summary.topk {
    out.write_string(
      "| " +
      item.key +
      " | " +
      item.count.to_string() +
      " | " +
      count_min_estimate(summary.count_min, item.key).to_string() +
      " |\n",
    )
  }
  out.to_string()
}

///|
pub fn weighted_summary_json(summary : WeightedStreamSummary) -> String {
  let out = StringBuilder()
  out.write_string("{")
  out.write_string("\"event_rows\":" + summary.event_rows.to_string())
  out.write_string(",\"total_weight\":" + summary.total_weight.to_string())
  out.write_string(",\"unique_keys\":" + summary.unique_keys.to_string())
  out.write_string(",\"topk\":[")
  for i in 0.. 0 {
      out.write_string(",")
    }
    let item = summary.topk[i]
    out.write_string(
      "{\"key\":\"" +
      sketch_escape_json(item.key) +
      "\",\"exact\":" +
      item.count.to_string() +
      ",\"estimated\":" +
      count_min_estimate(summary.count_min, item.key).to_string() +
      "}",
    )
  }
  out.write_string("]}")
  out.to_string()
}

///|
pub fn weighted_compare_markdown(
  baseline : WeightedStreamSummary,
  candidate : WeightedStreamSummary,
) -> String {
  let out = StringBuilder()
  out.write_string("# Weighted Stream Comparison\n\n")
  out.write_string(
    "| metric | baseline | candidate | delta |\n| --- | ---: | ---: | ---: |\n",
  )
  weighted_compare_row(
    out,
    "event rows",
    baseline.event_rows,
    candidate.event_rows,
  )
  weighted_compare_row(
    out,
    "total weight",
    baseline.total_weight,
    candidate.total_weight,
  )
  weighted_compare_row(
    out,
    "unique keys",
    baseline.unique_keys,
    candidate.unique_keys,
  )
  out.write_string("\n## Candidate Top-K\n\n")
  out.write_string(
    "| key | exact count | estimated count |\n| --- | ---: | ---: |\n",
  )
  for item in candidate.topk {
    out.write_string(
      "| " +
      item.key +
      " | " +
      item.count.to_string() +
      " | " +
      weighted_estimate(candidate, item.key).to_string() +
      " |\n",
    )
  }
  out.to_string()
}

///|
fn weighted_compare_row(
  out : StringBuilder,
  label : String,
  baseline : Int,
  candidate : Int,
) -> Unit {
  out.write_string(
    "| " +
    label +
    " | " +
    baseline.to_string() +
    " | " +
    candidate.to_string() +
    " | " +
    (candidate - baseline).to_string() +
    " |\n",
  )
}