///|
/// A compact audit report for one behavior tree run.
pub(all) struct AuditReport {
  name : String
  run : RunSummary
  coverage : TraceCoverage
  profile : ProfileReport
  lint : LintReport
  visual_assets : Int
} derive(Debug, Eq)

///|
pub fn AuditReport::ok(self : AuditReport) -> Bool {
  self.run.ok && self.lint.ok
}

///|
pub fn AuditReport::summary(self : AuditReport) -> String {
  self.name +
  ": ok=" +
  (if self.ok() { "true" } else { "false" }) +
  ", " +
  self.run.final_status.to_text() +
  ", " +
  self.coverage.summary() +
  ", profile_events=" +
  self.profile.total_events.to_string() +
  ", visual_assets=" +
  self.visual_assets.to_string()
}

///|
pub fn AuditReport::markdown(self : AuditReport) -> String {
  let lines = Array::new()
  lines.push("# MoonBTKit Audit: " + self.name)
  lines.push("")
  lines.push("## Summary")
  lines.push("")
  lines.push("- " + self.summary())
  lines.push("- run: " + self.run.to_line())
  lines.push("- lint: " + self.lint.summary())
  lines.push("")
  lines.push("## Coverage")
  lines.push("")
  let coverage_lines = self.coverage.lines()
  let mut c = 0
  while c < coverage_lines.length() {
    lines.push("- " + coverage_lines[c])
    c = c + 1
  }
  lines.push("")
  lines.push("## Hotspots")
  lines.push("")
  let hot = self.profile.hotspot_lines(5)
  let mut h = 0
  while h < hot.length() {
    lines.push("- " + hot[h])
    h = h + 1
  }
  if !self.lint.ok {
    lines.push("")
    lines.push("## Lint")
    lines.push("")
    let lint_lines = self.lint.lines()
    let mut l = 0
    while l < lint_lines.length() {
      lines.push("- " + lint_lines[l])
      l = l + 1
    }
  }
  join_strings(lines, "\n")
}

///|
pub(all) struct CatalogAudit {
  reports : Array[AuditReport]
} derive(Debug, Eq)

///|
pub fn CatalogAudit::passed(self : CatalogAudit) -> Int {
  let mut count = 0
  let mut i = 0
  while i < self.reports.length() {
    if self.reports[i].ok() {
      count = count + 1
    }
    i = i + 1
  }
  count
}

///|
pub fn CatalogAudit::failed(self : CatalogAudit) -> Int {
  self.reports.length() - self.passed()
}

///|
pub fn CatalogAudit::summary(self : CatalogAudit) -> String {
  "audits=" +
  self.reports.length().to_string() +
  ", passed=" +
  self.passed().to_string() +
  ", failed=" +
  self.failed().to_string()
}

///|
pub fn CatalogAudit::lines(self : CatalogAudit) -> Array[String] {
  let out = Array::new()
  out.push(self.summary())
  let mut i = 0
  while i < self.reports.length() {
    out.push(self.reports[i].summary())
    i = i + 1
  }
  out
}

///|
pub fn audit_fixture(name : String) -> Result[AuditReport, BtError] {
  match load_fixture(name) {
    Ok(doc) => {
      let run = smoke_run(name, doc.tree, doc.blackboard, Success, max_ticks=32)
      let engine = new_engine(doc.tree, blackboard=doc.blackboard)
      match engine.run_until_done(max_ticks=32) {
        Ok(_) => {
          let coverage = coverage_from_trace(doc.tree, engine.trace)
          let profile = profile_trace(name, engine.trace)
          let lint = lint_tree(doc.tree)
          let visuals = visualization_bundle(doc.tree, engine.trace)
          Ok({
            name,
            run,
            coverage,
            profile,
            lint,
            visual_assets: visuals.length(),
          })
        }
        Err(err) => Err(InvalidTree(err.message()))
      }
    }
    Err(err) => Err(err)
  }
}

///|
pub fn audit_fixture_catalog() -> CatalogAudit {
  let specs = fixture_catalog()
  let reports = Array::new()
  let mut i = 0
  while i < specs.length() {
    match audit_fixture(specs[i].name) {
      Ok(report) => reports.push(report)
      Err(_) => ()
    }
    i = i + 1
  }
  { reports, }
}

///|
pub fn audit_catalog_markdown() -> String {
  let audit = audit_fixture_catalog()
  let lines = Array::new()
  lines.push("# MoonBTKit Catalog Audit")
  lines.push("")
  lines.push("- " + audit.summary())
  lines.push("")
  let raw = audit.lines()
  let mut i = 0
  while i < raw.length() {
    lines.push("- " + raw[i])
    i = i + 1
  }
  join_strings(lines, "\n")
}