///|
pub struct ReportBuilder {
  value : ChemReport
  history : Array[String]
} derive(Debug, Eq)

///|
pub fn report_builder(value : ChemReport) -> ReportBuilder {
  { value, history: ["created"] }
}

///|
fn ReportBuilder::record(
  self : ReportBuilder,
  event : String,
  value : ChemReport,
) -> ReportBuilder {
  let history = self.history.copy()
  history.push(event)
  { value, history }
}

///|
pub fn ReportBuilder::add_assumption(
  self : ReportBuilder,
  statement : String,
  impact : String,
) -> ReportBuilder {
  let assumptions = self.value.assumptions.copy()
  assumptions.push({ statement, impact })
  self.record("assumption-added", { ..self.value, assumptions, })
}

///|
pub fn ReportBuilder::add_input(
  self : ReportBuilder,
  name : String,
  value : String,
  unit : String?,
  note : String?,
) -> ReportBuilder {
  let inputs = self.value.inputs.copy()
  inputs.push({ name, value, unit, note })
  self.record("input-added", { ..self.value, inputs, })
}

///|
pub fn ReportBuilder::add_formula(
  self : ReportBuilder,
  name : String,
  expression : String,
  explanation : String,
) -> ReportBuilder {
  let formulas = self.value.formulas.copy()
  formulas.push({ name, expression, explanation })
  self.record("formula-added", { ..self.value, formulas, })
}

///|
pub fn ReportBuilder::add_result(
  self : ReportBuilder,
  name : String,
  value : String,
  unit : String?,
  status : ResultStatus,
  note : String?,
) -> ReportBuilder {
  let results = self.value.results.copy()
  results.push({ name, value, unit, status, note })
  self.record("result-added", { ..self.value, results, })
}

///|
pub fn ReportBuilder::add_warning(
  self : ReportBuilder,
  code : String,
  severity : WarningSeverity,
  summary : String,
  action : String,
) -> ReportBuilder {
  let warnings = self.value.warnings.copy()
  warnings.push({ code, severity, summary, action })
  self.record("warning-added", { ..self.value, warnings, })
}

///|
pub fn ReportBuilder::add_source(
  self : ReportBuilder,
  label : String,
  kind : SourceKind,
  url : String?,
  license : String?,
  note : String?,
) -> ReportBuilder {
  let sources = self.value.sources.copy()
  sources.push({ label, kind, url, license, note })
  self.record("source-added", { ..self.value, sources, })
}

///|
pub fn ReportBuilder::add_section(
  self : ReportBuilder,
  title : String,
  kind : SectionKind,
  body : String,
) -> ReportBuilder {
  let sections = self.value.sections.copy()
  sections.push({ title, kind, body })
  self.record("section-added", { ..self.value, sections, })
}

///|
pub fn ReportBuilder::tag(self : ReportBuilder, tag : String) -> ReportBuilder {
  self.record("tag-added", self.value.with_tag(tag))
}

///|
pub fn ReportBuilder::summary(
  self : ReportBuilder,
  text : String,
) -> ReportBuilder {
  self.record("summary-updated", { ..self.value, summary: text })
}

///|
pub fn ReportBuilder::build(self : ReportBuilder) -> ChemReport {
  self.value
}

///|
pub fn ReportBuilder::events(self : ReportBuilder) -> Array[String] {
  self.history
}

///|
pub fn ReportBuilder::is_ready(self : ReportBuilder) -> Bool {
  self.value.is_publish_ready()
}

///|
pub fn ReportBuilder::quality(self : ReportBuilder) -> QualityGate {
  quality_gate(self.value)
}

///|
pub fn ChemReport::with_summary(
  self : ChemReport,
  summary : String,
) -> ChemReport {
  { ..self, summary, }
}

///|
pub fn ChemReport::with_metadata(
  self : ChemReport,
  metadata : ReportMetadata,
) -> ChemReport {
  { ..self, metadata, }
}

///|
pub fn ChemReport::with_results(
  self : ChemReport,
  results : Array[ResultValue],
) -> ChemReport {
  { ..self, results, }
}

///|
pub fn ChemReport::with_warnings(
  self : ChemReport,
  warnings : Array[WarningNote],
) -> ChemReport {
  { ..self, warnings, }
}

///|
pub fn ChemReport::with_sources(
  self : ChemReport,
  sources : Array[SourceRef],
) -> ChemReport {
  { ..self, sources, }
}

///|
pub fn ChemReport::format(self : ChemReport, format : String) -> String {
  match format.trim() {
    "markdown" | "md" => self.to_markdown()
    "html" => self.to_html()
    "json" => self.to_json()
    _ => self.to_markdown()
  }
}

///|
pub fn ChemReport::format_names(_self : ChemReport) -> Array[String] {
  ["markdown", "html", "json"]
}

///|
pub fn ChemReport::export_table(self : ChemReport) -> ReportTable {
  let rows : Array[Array[String]] = []
  for item in self.results {
    rows.push([
      item.name,
      item.value,
      item.unit.unwrap_or(""),
      result_status_name(item.status),
    ])
  }
  table(["result", "value", "unit", "status"], rows)
}

///|
pub fn ChemReport::warning_table(self : ChemReport) -> ReportTable {
  let rows : Array[Array[String]] = []
  for item in self.warnings {
    rows.push([
      item.code,
      severity_name(item.severity),
      item.summary,
      item.action,
    ])
  }
  table(["code", "severity", "summary", "action"], rows)
}

///|
pub fn ChemReport::source_table(self : ChemReport) -> ReportTable {
  let rows : Array[Array[String]] = []
  for item in self.sources {
    rows.push([
      item.label,
      source_kind_name(item.kind),
      item.url.unwrap_or(""),
      item.license.unwrap_or(""),
    ])
  }
  table(["label", "kind", "url", "license"], rows)
}

///|
pub struct BatchReport {
  reports : Array[ChemReport]
  labels : Array[String]
} derive(Debug, Eq)

///|
pub fn batch_report(
  reports : Array[ChemReport],
  labels : Array[String],
) -> BatchReport {
  { reports, labels }
}

///|
pub fn BatchReport::count(self : BatchReport) -> Int {
  self.reports.length()
}

///|
pub fn BatchReport::valid_count(self : BatchReport) -> Int {
  let mut count = 0
  for report in self.reports {
    if report.is_publish_ready() {
      count = count + 1
    }
  }
  count
}

///|
pub fn BatchReport::invalid_count(self : BatchReport) -> Int {
  self.count() - self.valid_count()
}

///|
pub fn BatchReport::all_formats(self : BatchReport) -> Array[ExportBundle] {
  self.reports.map(fn(report) { report.render_all_formats() })
}

///|
pub fn BatchReport::benchmark(
  self : BatchReport,
  iterations : Int,
) -> BenchmarkResult {
  benchmark_exports(self.reports, iterations)
}

///|
pub fn BatchReport::result_count(self : BatchReport) -> Int {
  let mut count = 0
  for report in self.reports {
    count = count + report.results.length()
  }
  count
}

///|
pub fn BatchReport::warning_count(self : BatchReport) -> Int {
  let mut count = 0
  for report in self.reports {
    count = count + report.warnings.length()
  }
  count
}

///|
pub fn BatchReport::critical_count(self : BatchReport) -> Int {
  let mut count = 0
  for report in self.reports {
    count = count + report.warning_count(Critical)
  }
  count
}

///|
pub fn BatchReport::summary(self : BatchReport) -> String {
  "reports=\{self.count()}, valid=\{self.valid_count()}, invalid=\{self.invalid_count()}, results=\{self.result_count()}, warnings=\{self.warning_count()}"
}

///|
pub fn BatchReport::to_markdown(self : BatchReport) -> String {
  let rows : Array[Array[String]] = []
  for index, report in self.reports {
    rows.push([
      self.labels.get(index).unwrap_or(""),
      report.metadata.title,
      report.metadata.process_unit,
      if report.is_publish_ready() {
        "ready"
      } else {
        "invalid"
      },
      "{report.results.length()}",
      "{report.warnings.length()}",
    ])
  }
  table(
    ["label", "title", "process unit", "status", "results", "warnings"],
    rows,
  ).to_markdown()
}

///|
pub fn BatchReport::to_json(self : BatchReport) -> String {
  "[" + self.reports.map(fn(report) { report.to_json() }).join(",") + "]"
}

///|
pub fn merge_reports(left : ChemReport, right : ChemReport) -> ChemReport {
  let assumptions = left.assumptions.copy()
  assumptions.append(right.assumptions)
  let inputs = left.inputs.copy()
  inputs.append(right.inputs)
  let formulas = left.formulas.copy()
  formulas.append(right.formulas)
  let results = left.results.copy()
  results.append(right.results)
  let warnings = left.warnings.copy()
  warnings.append(right.warnings)
  let sources = left.sources.copy()
  sources.append(right.sources)
  let sections = left.sections.copy()
  sections.append(right.sections)
  let tags = left.tags.copy()
  for tag in right.tags {
    if !tags.contains(tag) {
      tags.push(tag)
    }
  }
  {
    ..left,
    assumptions,
    inputs,
    formulas,
    results,
    warnings,
    sources,
    sections,
    tags,
    summary: left.summary + "\n\n" + right.summary,
  }
}