///|
pub struct CatalogAudit {
  entries : Int
  chapter_coverage : Int
  missing_parents : Int
  missing_exclusions : Int
  duplicate_codes : Int
  invalid_codes : Int
} derive(Debug, Eq, ToJson)

///|
pub fn CatalogAudit::is_healthy(self : CatalogAudit) -> Bool {
  self.chapter_coverage == icd10_chapters().length() &&
  self.missing_parents == 0 &&
  self.missing_exclusions == 0 &&
  self.duplicate_codes == 0 &&
  self.invalid_codes == 0
}

///|
pub fn audit_catalog(catalog : Catalog) -> CatalogAudit {
  audit_entries(catalog.all())
}

///|
pub fn audit_entries(entries : Array[IcdEntry]) -> CatalogAudit {
  let valid_codes = []
  let mut invalid_codes = 0
  let mut duplicate_codes = 0
  let chapter_ids = []
  for entry in entries {
    try parse_icd10(entry.code) catch {
      _ => invalid_codes += 1
    } noraise {
      code => {
        let canonical = code.canonical()
        if valid_codes.contains(canonical) {
          duplicate_codes += 1
        } else {
          valid_codes.push(canonical)
        }
        match find_chapter(code) {
          Some(chapter) =>
            if !chapter_ids.contains(chapter.id) {
              chapter_ids.push(chapter.id)
            } else {
              ()
            }
          None => ()
        }
      }
    }
  }
  let mut missing_parents = 0
  let mut missing_exclusions = 0
  for entry in entries {
    match entry.parent {
      Some(parent) =>
        if !valid_codes.contains(normalize_code(parent)) {
          missing_parents += 1
        }
      None => ()
    }
    for excluded in entry.excludes {
      if !valid_codes.contains(normalize_code(excluded)) {
        missing_exclusions += 1
      }
    }
  }
  {
    entries: entries.length(),
    chapter_coverage: chapter_ids.length(),
    missing_parents,
    missing_exclusions,
    duplicate_codes,
    invalid_codes,
  }
}