///|
pub(all) struct DocumentAnalytics {
  id : String
  input_length : Int
  output_length : Int
  finding_count : Int
  risk_score : Int
  redaction_ratio : Float
  average_confidence : Float
  kinds : Map[String, Int]
} derive(Debug)

///|
pub(all) struct AnalyticsComparison {
  left_id : String
  right_id : String
  finding_delta : Int
  risk_delta : Int
  length_delta : Int
  ratio_delta : Float
} derive(Debug)

///|
pub fn analyze_document(
  id : String,
  input : String,
  result : DeidResult,
) -> DocumentAnalytics {
  {
    id,
    input_length: input.length(),
    output_length: result.text.length(),
    finding_count: result.findings.length(),
    risk_score: risk_score(result.findings),
    redaction_ratio: redaction_ratio(result),
    average_confidence: average_confidence(result.findings),
    kinds: finding_kind_counts(result.findings),
  }
}

///|
pub fn analyze_documents(
  items : Array[BatchItem],
  config : RedactionConfig,
) -> Array[DocumentAnalytics] raise DeidError {
  items.map(item => {
    analyze_document(item.id, item.text, redact_with_config(item.text, config))
  })
}

///|
pub fn compare_analytics(
  left : DocumentAnalytics,
  right : DocumentAnalytics,
) -> AnalyticsComparison {
  {
    left_id: left.id,
    right_id: right.id,
    finding_delta: right.finding_count - left.finding_count,
    risk_delta: right.risk_score - left.risk_score,
    length_delta: right.output_length - left.output_length,
    ratio_delta: right.redaction_ratio - left.redaction_ratio,
  }
}

///|
pub fn analytics_kind_totals(
  items : Array[DocumentAnalytics],
) -> Map[String, Int] {
  let result : Map[String, Int] = Map([])
  for item in items {
    merge_counts(result, item.kinds)
  }
  result
}

///|
pub fn analytics_risk_total(items : Array[DocumentAnalytics]) -> Int {
  items.fold(init=0, (sum, item) => sum + item.risk_score)
}

///|
pub fn analytics_average_ratio(items : Array[DocumentAnalytics]) -> Float {
  if items.is_empty() {
    0.0
  } else {
    items.fold(init=0.0, (sum, item) => sum + item.redaction_ratio) /
    Float::from_int(items.length())
  }
}

///|
pub fn analytics_average_confidence(items : Array[DocumentAnalytics]) -> Float {
  if items.is_empty() {
    0.0
  } else {
    items.fold(init=0.0, (sum, item) => sum + item.average_confidence) /
    Float::from_int(items.length())
  }
}

///|
pub fn analytics_max_risk(
  items : Array[DocumentAnalytics],
) -> DocumentAnalytics? {
  let mut result = None
  for item in items {
    match result {
      None => result = Some(item)
      Some(previous) =>
        if item.risk_score > previous.risk_score {
          result = Some(item)
        }
    }
  }
  result
}

///|
pub fn analytics_min_risk(
  items : Array[DocumentAnalytics],
) -> DocumentAnalytics? {
  let mut result = None
  for item in items {
    match result {
      None => result = Some(item)
      Some(previous) =>
        if item.risk_score < previous.risk_score {
          result = Some(item)
        }
    }
  }
  result
}

///|
pub fn analytics_to_csv(items : Array[DocumentAnalytics]) -> String {
  let lines = [
    "id,input_length,output_length,findings,risk_score,redaction_ratio,average_confidence",
  ]
  for item in items {
    lines.push(
      [
        csv_cell(item.id),
        "\{item.input_length}",
        "\{item.output_length}",
        "\{item.finding_count}",
        "\{item.risk_score}",
        "\{item.redaction_ratio}",
        "\{item.average_confidence}",
      ].join(","),
    )
  }
  lines.join("\n")
}

///|
pub fn analytics_to_json(items : Array[DocumentAnalytics]) -> String {
  "[" +
  items
  .map(fn(item) {
    "{" +
    "\"id\":\{json_string(item.id)}," +
    "\"input_length\":\{item.input_length}," +
    "\"output_length\":\{item.output_length}," +
    "\"finding_count\":\{item.finding_count}," +
    "\"risk_score\":\{item.risk_score}," +
    "\"redaction_ratio\":\{item.redaction_ratio}" +
    "}"
  })
  .join(",") +
  "]"
}

///|
pub fn analytics_markdown(items : Array[DocumentAnalytics]) -> String {
  let lines = [
    "| ID | Findings | Risk | Ratio | Confidence |", "| --- | ---: | ---: | ---: | ---: |",
  ]
  for item in items {
    lines.push(
      "| \{item.id} | \{item.finding_count} | \{item.risk_score} | \{item.redaction_ratio} | \{item.average_confidence} |",
    )
  }
  lines.join("\n")
}

///|
pub fn analytics_threshold(
  items : Array[DocumentAnalytics],
  risk : Int,
) -> Array[DocumentAnalytics] {
  items.filter(fn(item) { item.risk_score >= risk })
}

///|
pub fn analytics_with_findings(
  items : Array[DocumentAnalytics],
) -> Array[DocumentAnalytics] {
  items.filter(fn(item) { item.finding_count > 0 })
}

///|
pub fn analytics_without_findings(
  items : Array[DocumentAnalytics],
) -> Array[DocumentAnalytics] {
  items.filter(fn(item) { item.finding_count == 0 })
}

///|
pub fn analytics_summary(items : Array[DocumentAnalytics]) -> String {
  [
    "documents=\{items.length()}",
    "with_findings=\{analytics_with_findings(items).length()}",
    "risk_total=\{analytics_risk_total(items)}",
    "average_ratio=\{analytics_average_ratio(items)}",
    "average_confidence=\{analytics_average_confidence(items)}",
  ].join("\n")
}