///|
fn profile_increment(map : Map[String, Int], key : String) -> Unit {
  map.update_or_default(key, 1, count => count + 1)
}

///|
fn profile_entity_family(entity : Entity) -> String {
  entity_family(entity)
}

///|
/// Build a reusable semantic profile without decoding every geometry mesh.
pub fn model_profile(model : Model) -> ModelProfile {
  let index = model.index()
  let schema = schema_version(model)
  let profile = schema_profile(schema)
  let family_counts : Map[String, Int] = Map([])
  let type_counts : Map[String, Int] = Map([])
  let mut known = 0
  let mut unknown = 0
  let mut relationships = 0
  let mut geometry_count = 0
  for entity in model.entities {
    let family = profile_entity_family(entity)
    profile_increment(family_counts, family)
    profile_increment(type_counts, entity.name.to_upper())
    if schema_entity_is_known(profile, entity.name) {
      known += 1
    } else {
      unknown += 1
    }
    if is_relationship(entity) {
      relationships += 1
    }
    if is_geometry_entity(entity) {
      geometry_count += 1
    }
  }
  {
    schema,
    entity_count: model.entities.length(),
    product_count: model.products().length(),
    family_counts,
    type_counts,
    known_entity_count: known,
    unknown_entity_count: unknown,
    relationship_count: relationships,
    geometry_count,
    property_set_count: index.ids_named("IFCPROPERTYSET").length(),
  }
}

///|
pub fn ModelProfile::family_count(self : ModelProfile, family : String) -> Int {
  self.family_counts.get(family).unwrap_or(0)
}

///|
pub fn ModelProfile::type_count(self : ModelProfile, name : String) -> Int {
  self.type_counts.get(name.to_upper()).unwrap_or(0)
}

///|
pub fn ModelProfile::known_ratio(self : ModelProfile) -> Double {
  if self.entity_count == 0 {
    1.0
  } else {
    self.known_entity_count.to_double() / self.entity_count.to_double()
  }
}

///|
pub fn ModelProfile::is_extension_heavy(self : ModelProfile) -> Bool {
  self.unknown_entity_count > self.known_entity_count / 2
}

///|
pub fn ModelProfile::family_names(self : ModelProfile) -> Array[String] {
  let result : Array[String] = []
  for family, _ in self.family_counts {
    result.push(family)
  }
  result
}

///|
pub fn ModelProfile::top_types(
  self : ModelProfile,
  limit : Int,
) -> Array[(String, Int)] {
  let rows : Array[(String, Int)] = []
  for name, count in self.type_counts {
    rows.push((name, count))
  }
  let result = rows.copy()
  let maximum = if limit <= 0 || limit > result.length() {
    result.length()
  } else {
    limit
  }
  for i in 0.. result[selected].1 ||
        (result[j].1 == result[selected].1 && result[j].0 < result[selected].0) {
        selected = j
      }
    }
    if selected != i {
      let item = result[i]
      result[i] = result[selected]
      result[selected] = item
    }
  }
  if maximum < result.length() {
    result[:maximum].to_owned()
  } else {
    result
  }
}

///|
pub fn ModelProfile::headline(self : ModelProfile) -> String {
  schema_profile_summary(schema_profile(self.schema)) +
  " entities=" +
  self.entity_count.to_string() +
  " products=" +
  self.product_count.to_string() +
  " known=" +
  self.known_entity_count.to_string() +
  " unknown=" +
  self.unknown_entity_count.to_string() +
  " geometry=" +
  self.geometry_count.to_string()
}

///|
pub fn ModelProfile::to_json(self : ModelProfile) -> String {
  let types = self
    .top_types(20)
    .map(row => {
      "{\"name\":\"" + row.0 + "\",\"count\":" + row.1.to_string() + "}"
    })
    .join(",")
  "{\"schema\":\"" +
  schema_profile_summary(schema_profile(self.schema)) +
  "\",\"entity_count\":" +
  self.entity_count.to_string() +
  ",\"product_count\":" +
  self.product_count.to_string() +
  ",\"known_entity_count\":" +
  self.known_entity_count.to_string() +
  ",\"unknown_entity_count\":" +
  self.unknown_entity_count.to_string() +
  ",\"relationship_count\":" +
  self.relationship_count.to_string() +
  ",\"geometry_count\":" +
  self.geometry_count.to_string() +
  ",\"property_set_count\":" +
  self.property_set_count.to_string() +
  ",\"top_types\":[" +
  types +
  "]}"
}

///|
pub fn ModelProfile::to_markdown(self : ModelProfile) -> String {
  let builder = StringBuilder()
  builder <+ "# Model profile\n\n"
  builder <+
    "- Schema: \{schema_profile_summary(schema_profile(self.schema))}\n"
  builder <+ "- Entities: \{self.entity_count}\n"
  builder <+ "- Products: \{self.product_count}\n"
  builder <+ "- Known entities: \{self.known_entity_count}\n"
  builder <+ "- Unknown entities: \{self.unknown_entity_count}\n"
  builder <+ "- Relationships: \{self.relationship_count}\n"
  builder <+ "- Geometry entities: \{self.geometry_count}\n"
  builder <+ "- Property sets: \{self.property_set_count}\n\n"
  builder <+ "| Type | Count |\n| --- | ---: |\n"
  for row in self.top_types(20) {
    builder <+ "| \{row.0} | \{row.1} |\n"
  }
  builder.to_string()
}

///|
pub fn model_profile_type_histogram(model : Model) -> Map[String, Int] {
  model_profile(model).type_counts
}

///|
pub fn model_profile_family_histogram(model : Model) -> Map[String, Int] {
  model_profile(model).family_counts
}

///|
pub fn model_profile_summary(model : Model) -> Array[String] {
  let profile = model_profile(model)
  [
    profile.headline(),
    "known_ratio=" + profile.known_ratio().to_string(),
    "extension_heavy=" + profile.is_extension_heavy().to_string(),
  ]
}