///|
pub fn Catalog::lookup(self : Catalog, code : String) -> IcdEntry? {
  let canonical = parse_icd10(code).canonical() catch { _ => return None }
  self.entries
  .search_by(fn(entry) { entry.code == canonical })
  .map(fn(i) { self.entries[i] })
}

///|
pub fn Catalog::prefix(self : Catalog, prefix : String) -> Array[IcdEntry] {
  let normalized = normalize_code(prefix)
  self.entries.filter(fn(entry) { entry.code.has_prefix(normalized) })
}

///|
pub fn Catalog::search_title(self : Catalog, query : String) -> Array[IcdEntry] {
  let normalized = query.trim().to_lower()
  if normalized.is_empty() {
    []
  } else {
    self.entries.filter(fn(entry) {
      entry.title.to_lower().contains(normalized)
    })
  }
}

///|
pub fn Catalog::in_chapter(
  self : Catalog,
  chapter_id : String,
) -> Array[IcdEntry] {
  let normalized = chapter_id.trim().to_owned().to_upper()
  self.entries.filter(fn(entry) { entry.chapter_id == normalized })
}

///|
pub fn Catalog::children(self : Catalog, parent : String) -> Array[IcdEntry] {
  let normalized = normalize_code(parent)
  self.entries.filter(fn(entry) { entry.parent == Some(normalized) })
}

///|
pub fn Catalog::ancestors(self : Catalog, code : String) -> Array[IcdEntry] {
  let result = []
  let seen = []
  let mut current = self.lookup(code)
  while current is Some(entry) {
    match entry.parent {
      Some(parent) => {
        if seen.contains(parent) {
          break
        }
        seen.push(parent)
        match self.lookup(parent) {
          Some(parent_entry) => {
            result.push(parent_entry)
            current = Some(parent_entry)
          }
          None => current = None
        }
      }
      None => current = None
    }
  }
  result
}

///|
pub fn Catalog::exclusions(self : Catalog, code : String) -> Array[IcdEntry] {
  match self.lookup(code) {
    Some(entry) =>
      entry.excludes.filter_map(fn(excluded) { self.lookup(excluded) })
    None => []
  }
}