///|
pub struct Catalog {
  entries : Array[IcdEntry]
} derive(Debug)

///|
pub fn Catalog::from_entries(
  entries : Array[IcdEntry],
) -> Catalog raise CatalogError {
  let normalized = entries.map(normalize_catalog_entry)
  for i in 0..
        if !catalog_has_code(normalized, parent) {
          raise CatalogError::MissingReference(entry.code, parent)
        }
      None => ()
    }
    for excluded in entry.excludes {
      if !catalog_has_code(normalized, excluded) {
        raise CatalogError::MissingReference(entry.code, excluded)
      }
    }
  }
  { entries: normalized }
}

///|
pub fn Catalog::len(self : Catalog) -> Int {
  self.entries.length()
}

///|
pub fn Catalog::all(self : Catalog) -> Array[IcdEntry] {
  self.entries.copy()
}

///|
fn catalog_has_code(entries : Array[IcdEntry], code : String) -> Bool {
  entries.any(fn(entry) { entry.code == code })
}

///|
fn normalize_catalog_entry(entry : IcdEntry) -> IcdEntry raise CatalogError {
  let code = parse_icd10(entry.code) catch {
    _ =>
      raise CatalogError::InvalidEntry(
        entry.code,
        "code is not valid ICD-10 syntax",
      )
  }
  let canonical = code.canonical()
  let chapter = find_chapter(code)
  if chapter is None || chapter.unwrap().id != entry.chapter_id {
    raise CatalogError::InvalidEntry(
      canonical, "chapter_id does not match the code range",
    )
  }
  let parent = match entry.parent {
    Some(raw) => {
      let parent_code = parse_icd10(raw) catch {
        _ =>
          raise CatalogError::InvalidEntry(
            canonical, "parent is not valid ICD-10 syntax",
          )
      }
      let normalized_parent = parent_code.canonical()
      if normalized_parent == canonical {
        raise CatalogError::InvalidEntry(
          canonical, "an entry cannot be its own parent",
        )
      }
      Some(normalized_parent)
    }
    None => None
  }
  let excludes = []
  for raw in entry.excludes {
    let excluded = parse_icd10(raw) catch {
      _ =>
        raise CatalogError::InvalidEntry(
          canonical, "exclusion is not valid ICD-10 syntax",
        )
    }
    excludes.push(excluded.canonical())
  }
  {
    code: canonical,
    title: entry.title.trim().to_owned(),
    chapter_id: entry.chapter_id.trim().to_owned().to_upper(),
    parent,
    excludes,
    note: entry.note.trim().to_owned(),
  }
}

///|
pub fn bundled_catalog() -> Catalog {
  let entries = sample_entries()
  for entry in representative_entries() {
    entries.push(entry)
  }
  Catalog::from_entries(entries) catch {
    _ => panic()
  }
}