///|
/// One local report used to gate a model before it reaches a viewer or exporter.
pub(all) struct ModelDeliveryReport {
  source_length : Int
  profile : ModelProfile
  quality : ModelQualityReport
  manifest : AssetManifest
  canonical : CanonicalStepStats
} derive(Debug)

///|
/// Build the profile, quality score, cache manifest and canonical STEP statistics together.
pub fn model_delivery_report(
  source : String,
) -> ModelDeliveryReport raise ParseError {
  let model = parse(source)
  {
    source_length: source.length(),
    profile: model_profile(model),
    quality: model_quality_audit(model),
    manifest: asset_manifest(model),
    canonical: canonical_step_stats(source),
  }
}

///|
pub fn ModelDeliveryReport::is_ready(self : ModelDeliveryReport) -> Bool {
  !self.quality.has_errors() &&
  self.manifest.entry_count() > 0 &&
  !self.canonical.has_duplicates()
}

///|
pub fn ModelDeliveryReport::has_geometry(self : ModelDeliveryReport) -> Bool {
  self.profile.geometry_count > 0 && self.manifest.triangle_count > 0
}

///|
pub fn ModelDeliveryReport::score(self : ModelDeliveryReport) -> Int {
  let quality = self.quality.score
  let geometry = if self.has_geometry() { 10 } else { 0 }
  let canonical = if self.canonical.changed { 5 } else { 10 }
  let complete = if self.is_ready() { 10 } else { 0 }
  let result = quality + geometry + canonical + complete
  if result > 100 {
    100
  } else {
    result
  }
}

///|
pub fn ModelDeliveryReport::headline(self : ModelDeliveryReport) -> String {
  "ready=" +
  self.is_ready().to_string() +
  " score=" +
  self.score().to_string() +
  " schema=" +
  quality_schema_name(self.profile.schema) +
  " entities=" +
  self.profile.entity_count.to_string() +
  " products=" +
  self.profile.product_count.to_string() +
  " assets=" +
  self.manifest.entry_count().to_string()
}

///|
pub fn ModelDeliveryReport::to_json(self : ModelDeliveryReport) -> String {
  "{\"ready\":" +
  self.is_ready().to_string() +
  ",\"score\":" +
  self.score().to_string() +
  ",\"source_length\":" +
  self.source_length.to_string() +
  ",\"profile\":" +
  self.profile.to_json() +
  ",\"quality\":" +
  self.quality.to_json() +
  ",\"manifest\":" +
  self.manifest.to_json() +
  ",\"canonical\":" +
  self.canonical.to_json() +
  "}"
}

///|
pub fn ModelDeliveryReport::to_markdown(self : ModelDeliveryReport) -> String {
  let builder = StringBuilder()
  builder <+ "# Model delivery report\n\n"
  builder <+ "- \{self.headline()}\n"
  builder <+ "- Source length: \{self.source_length}\n"
  builder <+ "- Has geometry: \{self.has_geometry()}\n"
  builder <+ "- Quality: \{self.quality.headline()}\n"
  builder <+ "- Manifest: \{self.manifest.headline()}\n"
  builder <+ "- Canonical STEP: \{self.canonical.headline()}\n\n"
  builder <+ "## Delivery gates\n\n"
  builder <+ "| Gate | Result |\n| --- | --- |\n"
  builder <+ "| Quality errors | \{self.quality.has_errors()}\n"
  builder <+ "| Cache manifest | \{self.manifest.entry_count() > 0}\n"
  builder <+ "| Duplicate ids | \{self.canonical.has_duplicates()}\n"
  builder <+ "| Geometry | \{self.has_geometry()}\n"
  builder <+ "| Ready | \{self.is_ready()}\n"
  builder.to_string()
}

///|
pub fn model_delivery_summary(
  source : String,
) -> Array[String] raise ParseError {
  let report = model_delivery_report(source)
  [
    report.headline(),
    "quality_score=" + report.quality.score.to_string(),
    "manifest_bytes=" + report.manifest.total_bytes().to_string(),
    "canonical_ratio=" + report.canonical.compression_ratio().to_string(),
  ]
}

///|
pub fn model_delivery_gate(source : String) -> Bool raise ParseError {
  model_delivery_report(source).is_ready()
}

///|
pub fn ModelDeliveryReport::asset_bytes(
  self : ModelDeliveryReport,
  kind : AssetKind,
) -> Int {
  self.manifest
  .entries_of_kind(kind)
  .fold(init=0, (sum, entry) => sum + entry.byte_length)
}

///|
pub fn ModelDeliveryReport::quality_passed(self : ModelDeliveryReport) -> Bool {
  self.quality.count_level(Error) == 0 && self.quality.count_level(Warning) == 0
}

///|
pub fn ModelDeliveryReport::canonical_passed(
  self : ModelDeliveryReport,
) -> Bool {
  !self.canonical.has_duplicates() && self.canonical.entity_count > 0
}

///|
pub fn ModelDeliveryReport::gate_results(
  self : ModelDeliveryReport,
) -> Map[String, Bool] {
  let result : Map[String, Bool] = Map([])
  result["quality"] = self.quality_passed()
  result["manifest"] = self.manifest.entry_count() > 0
  result["canonical"] = self.canonical_passed()
  result["geometry"] = self.has_geometry()
  result["ready"] = self.is_ready()
  result
}

///|
pub fn model_delivery_report_with_policy(
  source : String,
  policy : QualityPolicy,
  options : AssetManifestOptions,
) -> ModelDeliveryReport raise ParseError {
  let model = parse(source)
  {
    source_length: source.length(),
    profile: model_profile(model),
    quality: model_quality_audit(model, policy~),
    manifest: asset_manifest(model, options~),
    canonical: canonical_step_stats(source),
  }
}

///|
pub fn model_delivery_gate_with_policy(
  source : String,
  policy : QualityPolicy,
  options : AssetManifestOptions,
) -> Bool raise ParseError {
  model_delivery_report_with_policy(source, policy, options).is_ready()
}

///|
pub fn model_delivery_asset_breakdown(
  report : ModelDeliveryReport,
) -> Array[String] {
  [
    "manifest_bytes=" + report.asset_bytes(Manifest).to_string(),
    "geometry_bytes=" + report.asset_bytes(Geometry).to_string(),
    "properties_bytes=" + report.asset_bytes(Properties).to_string(),
    "spatial_bytes=" + report.asset_bytes(Spatial).to_string(),
    "product_bytes=" + report.asset_bytes(Product).to_string(),
  ]
}