///|
/// `x` as a percentage with one decimal, computed with integers so that
/// every backend prints the same digits.
pub fn percent(x : Double) -> String {
  let tenths = (x * 1000.0).round().to_int()
  "\{tenths / 10}.\{tenths % 10}%"
}

///|
fn percent_or_dash(x : Double?) -> String {
  match x {
    Some(v) => percent(v)
    None => "-"
  }
}

///|
fn lang_label(lang : @mbglot.Lang?) -> String {
  match lang {
    Some(l) => l.code()
    None => "none"
  }
}

///|
/// A Markdown summary of the report: overall accuracy, a table with
/// precision, recall and F1 per language (in order of first appearance),
/// and the `confusions` most frequent mistakes.
pub fn Report::to_markdown(self : Report, confusions~ : Int32 = 10) -> String {
  let buf = StringBuilder()
  buf.write_string(
    "Accuracy: \{percent(self.accuracy())} (\{self.correct} of \{self.total}), " +
    "unidentified: \{self.unidentified}, macro F1: \{percent(self.macro_f1())}\n\n",
  )
  buf.write_string("| Language | Samples | Precision | Recall | F1 |\n")
  buf.write_string("|---|---:|---:|---:|---:|\n")
  for s in self.per_language() {
    buf.write_string(
      "| \{s.lang.code()} \{s.lang.name()} | \{s.support} | " +
      "\{percent_or_dash(s.precision)} | \{percent_or_dash(s.recall)} | " +
      "\{percent_or_dash(s.f1)} |\n",
    )
  }
  let mistakes = self.top_confusions(confusions)
  if !mistakes.is_empty() {
    buf.write_string("\n| Expected | Predicted | Count |\n")
    buf.write_string("|---|---|---:|\n")
    for m in mistakes {
      let (expected, predicted, count) = m
      buf.write_string(
        "| \{expected.code()} | \{lang_label(predicted)} | \{count} |\n",
      )
    }
  }
  buf.to_string()
}

///|
/// The confusion matrix over `langs` as a Markdown table: one row per
/// expected language, one column per predicted language in `langs`, then
/// a column for other languages and one for no prediction.
pub fn Report::confusion_markdown(
  self : Report,
  langs : Array[@mbglot.Lang],
) -> String {
  let buf = StringBuilder()
  buf.write_string("| expected \\ predicted |")
  for lang in langs {
    buf.write_string(" \{lang.code()} |")
  }
  buf.write_string(" other | none |\n|---|")
  for _ in langs {
    buf.write_string("---:|")
  }
  buf.write_string("---:|---:|\n")
  for expected in langs {
    buf.write_string("| \{expected.code()} |")
    let mut listed = 0
    for predicted in langs {
      let n = self.count(expected, Some(predicted))
      listed += n
      buf.write_string(" \{n} |")
    }
    let none = self.count(expected, None)
    let mut row = 0
    for key, n in self.confusion {
      if key.0 == expected {
        row += n
      }
    }
    buf.write_string(" \{row - listed - none} | \{none} |\n")
  }
  buf.to_string()
}