///|
pub(all) struct BenchmarkResult {
  name : String
  documents : Int
  repetitions : Int
  input_characters : Int
  findings : Int
  output_characters : Int
} derive(Debug, Eq)

///|
pub fn exact_span_match(left : Span, right : Span) -> Bool {
  left.start == right.start && left.end == right.end
}

///|
pub fn overlap_span_match(left : Span, right : Span) -> Bool {
  left.overlaps(right)
}

///|
pub fn evaluate_one_case(
  case : EvaluationCase,
  predicted : Array[Finding],
) -> EvaluationResult {
  let expected = case.expected
  let predicted_spans = findings_to_spans(predicted)
  let true_positive = expected
    .filter(fn(item) {
      predicted_spans.any(fn(candidate) { exact_span_match(item, candidate) })
    })
    .length()
  let false_negative = expected.length() - true_positive
  let false_positive = predicted_spans
    .filter(fn(item) {
      !expected.any(fn(candidate) { exact_span_match(item, candidate) })
    })
    .length()
  {
    cases: 1,
    expected: expected.length(),
    predicted: predicted_spans.length(),
    true_positive,
    false_positive,
    false_negative,
  }
}

///|
pub fn evaluate_cases(
  cases : Array[EvaluationCase],
  rules? : Array[Rule] = comprehensive_rules(),
) -> EvaluationResult raise DeidError {
  let mut total = {
    cases: 0,
    expected: 0,
    predicted: 0,
    true_positive: 0,
    false_positive: 0,
    false_negative: 0,
  }
  for case in cases {
    let predicted = scan(case.text, rules~)
    let current = evaluate_one_case(case, predicted)
    total = {
      cases: total.cases + current.cases,
      expected: total.expected + current.expected,
      predicted: total.predicted + current.predicted,
      true_positive: total.true_positive + current.true_positive,
      false_positive: total.false_positive + current.false_positive,
      false_negative: total.false_negative + current.false_negative,
    }
  }
  total
}

///|
pub fn precision(result : EvaluationResult) -> Float {
  result.precision()
}

///|
pub fn recall(result : EvaluationResult) -> Float {
  result.recall()
}

///|
pub fn f1_score(result : EvaluationResult) -> Float {
  result.f1()
}

///|
pub fn evaluation_summary(result : EvaluationResult) -> String {
  [
    "cases=\{result.cases}",
    "expected=\{result.expected}",
    "predicted=\{result.predicted}",
    "tp=\{result.true_positive}",
    "fp=\{result.false_positive}",
    "fn=\{result.false_negative}",
    "precision=\{result.precision()}",
    "recall=\{result.recall()}",
    "f1=\{result.f1()}",
  ].join(" ")
}

///|
pub fn evaluation_to_json(result : EvaluationResult) -> String {
  "{" +
  "\"cases\":\{result.cases}," +
  "\"expected\":\{result.expected}," +
  "\"predicted\":\{result.predicted}," +
  "\"true_positive\":\{result.true_positive}," +
  "\"false_positive\":\{result.false_positive}," +
  "\"false_negative\":\{result.false_negative}," +
  "\"precision\":\{result.precision()}," +
  "\"recall\":\{result.recall()}," +
  "\"f1\":\{result.f1()}" +
  "}"
}

///|
pub fn benchmark_once(
  name : String,
  documents : Array[String],
  repetitions : Int,
  config : RedactionConfig,
) -> BenchmarkResult raise DeidError {
  let rounds = if repetitions <= 0 { 1 } else { repetitions }
  let mut input_characters = 0
  let mut findings = 0
  let mut output_characters = 0
  for _ in 0.. String {
  [
    "name=\{result.name}",
    "documents=\{result.documents}",
    "repetitions=\{result.repetitions}",
    "input_characters=\{result.input_characters}",
    "findings=\{result.findings}",
    "output_characters=\{result.output_characters}",
  ].join("\n")
}

///|
pub fn benchmark_to_json(result : BenchmarkResult) -> String {
  "{" +
  "\"name\":\{json_string(result.name)}," +
  "\"documents\":\{result.documents}," +
  "\"repetitions\":\{result.repetitions}," +
  "\"input_characters\":\{result.input_characters}," +
  "\"findings\":\{result.findings}," +
  "\"output_characters\":\{result.output_characters}" +
  "}"
}

///|
pub fn synthetic_evaluation_cases() -> Array[EvaluationCase] {
  [
    {
      id: "mixed-identifiers",
      text: "姓名:张三,电话13800138000,邮箱 zhang@example.com",
      expected: [
        { start: 3, end: 5 },
        { start: 8, end: 19 },
        { start: 22, end: 38 },
      ],
    },
    {
      id: "english-record",
      text: "Patient: John Smith MRN: A1234567",
      expected: [{ start: 9, end: 19 }, { start: 25, end: 33 }],
    },
    {
      id: "structured-identifiers",
      text: "DOB: 1990-01-02 IMEI: 490154203237518",
      expected: [{ start: 5, end: 15 }, { start: 22, end: 37 }],
    },
  ]
}

///|
pub fn evaluate_synthetic_cases() -> EvaluationResult raise DeidError {
  evaluate_cases(synthetic_evaluation_cases())
}

///|
pub fn benchmark_fixture() -> Array[String] {
  [
    "姓名:张三;身份证:110101199003071234;电话:13800138000;邮箱:zhang@example.com;住址:北京市海淀区中关村1号。",
    "Patient: John Smith; DOB: 1990-01-02; Phone: 415-555-1234; MRN: A1234567; Email: john@example.org.",
    "就诊号:ENC-20260819-0001;检验号:LAB-778899;医生:李医生;地址:上海市徐汇区漕溪北路。",
  ]
}