///|
/// A compact, deterministic quality view for CI logs and acceptance reports.
pub fn model_complexity_score(model : Model) -> Int {
  let metrics = analyze_model(model.to_step(), model)
  let mut score = 0
  if metrics.entity_count > 0 {
    score += 20
  }
  if metrics.product_count > 0 {
    score += 20
  }
  if metrics.geometry_count > 0 {
    score += 20
  }
  if metrics.property_count > 0 {
    score += 20
  }
  if metrics.spatial_depth > 0 {
    score += 20
  }
  score
}

///|
pub fn model_geometry_coverage(model : Model) -> Double {
  let products = model.products()
  if products.length() == 0 {
    0.0
  } else {
    products
    .fold(init=0, (count, product) => {
      if product.representation is Some(_) {
        count + 1
      } else {
        count
      }
    })
    .to_double() /
    products.length().to_double()
  }
}

///|
pub fn model_property_coverage(model : Model) -> Double {
  let products = model.products()
  if products.length() == 0 {
    0.0
  } else {
    products
    .fold(init=0, (count, product) => {
      if product.property_sets.length() > 0 {
        count + 1
      } else {
        count
      }
    })
    .to_double() /
    products.length().to_double()
  }
}

///|
pub fn model_spatial_coverage(model : Model) -> Bool {
  spatial_roots(model.index()).length() > 0
}

///|
pub fn model_quality_grade(model : Model) -> String {
  let report = validate(model)
  let score = model_complexity_score(model)
  if !report.is_valid() {
    "invalid"
  } else if score >= 80 {
    "complete"
  } else if score >= 60 {
    "usable"
  } else {
    "partial"
  }
}

///|
pub fn model_quality_lines(model : Model) -> Array[String] {
  let metrics = analyze_model(model.to_step(), model)
  let validation = validate(model)
  let health = source_health(model.to_step())
  let schema = validate_schema(model)
  [
    "grade=" + model_quality_grade(model),
    "score=" + model_complexity_score(model).to_string() + "/100",
    "schema=" + model.schema(),
    "entities=" + metrics.entity_count.to_string(),
    "products=" + metrics.product_count.to_string(),
    "geometry=" + metrics.geometry_count.to_string(),
    "properties=" + metrics.property_count.to_string(),
    "relationships=" + metrics.relationship_count.to_string(),
    "references=" + metrics.reference_count.to_string(),
    "spatial_depth=" + metrics.spatial_depth.to_string(),
    "geometry_coverage=" + model_geometry_coverage(model).to_string(),
    "property_coverage=" + model_property_coverage(model).to_string(),
    "spatial_root=" + model_spatial_coverage(model).to_string(),
    "parse_health=" + health.summary(),
    "semantic_errors=" + validation.error_count.to_string(),
    "semantic_warnings=" + validation.warning_count.to_string(),
    "schema_errors=" + schema.error_count.to_string(),
    "schema_warnings=" + schema.warning_count.to_string(),
  ]
}

///|
pub fn model_quality_report(model : Model) -> String {
  model_quality_lines(model).join("\n")
}

///|
pub fn model_quality_json(model : Model) -> String {
  let metrics = analyze_model(model.to_step(), model)
  let validation = validate(model)
  "{\"grade\":" +
  json_string(model_quality_grade(model)) +
  ",\"score\":" +
  model_complexity_score(model).to_string() +
  ",\"schema\":" +
  json_string(model.schema()) +
  ",\"entities\":" +
  metrics.entity_count.to_string() +
  ",\"products\":" +
  metrics.product_count.to_string() +
  ",\"geometry\":" +
  metrics.geometry_count.to_string() +
  ",\"properties\":" +
  metrics.property_count.to_string() +
  ",\"errors\":" +
  validation.error_count.to_string() +
  ",\"warnings\":" +
  validation.warning_count.to_string() +
  "}"
}

///|
pub fn acceptance_checklist(model : Model) -> Array[String] {
  let checklist : Array[String] = []
  let validation = validate(model)
  let parser_status = if model.entities.length() > 0 { "pass" } else { "empty" }
  let semantic_status = if validation.is_valid() { "pass" } else { "fail" }
  checklist.push("parser: " + parser_status)
  checklist.push("semantic validation: " + semantic_status)
  checklist.push("schema profile: " + validate_schema(model).headline())
  let spatial_status = if model_spatial_coverage(model) {
    "pass"
  } else {
    "not present"
  }
  let geometry_status = if geometry_entities(model.index()).length() > 0 {
    "pass"
  } else {
    "not present"
  }
  let property_status = if model.index().ids_named("IFCPROPERTYSET").length() >
    0 {
    "pass"
  } else {
    "not present"
  }
  checklist.push("spatial extraction: " + spatial_status)
  checklist.push("geometry extraction: " + geometry_status)
  checklist.push("property extraction: " + property_status)
  checklist
}

///|
pub fn acceptance_checklist_markdown(model : Model) -> String {
  acceptance_checklist(model)
  .map(item => {
    let marker = if item.contains("fail") { " " } else { "x" }
    "- [" + marker + "] " + item
  })
  .join("\n")
}

///|
pub fn model_summary_json(model : Model) -> String raise ParseError {
  let catalog = ModelCatalog::new(model)
  let query = CatalogQuery::new().page(0, 100000)
  let page = catalog.query(query)
  "{\"quality\":" +
  model_quality_json(model) +
  ",\"catalog_products\":" +
  page.total_matches.to_string() +
  ",\"types\":[" +
  catalog
  .type_histogram()
  .to_array()
  .map(item => {
    let (name, count) = item
    "{\"type\":" + json_string(name) + ",\"count\":" + count.to_string() + "}"
  })
  .join(",") +
  "]}"
}