///|
/// Machine-readable inventory for a generated acceptance report.
pub(all) struct ReportManifest {
  schema_version : Int
  application_version : String
  part_id : String
  feature_count : Int
  observation_count : Int
  step_count : Int
  warning_count : Int
  evidence_coverage : Double
  reproducible : Bool
  status : String
}

///|
/// Build a manifest from the exact inputs used for a report.
pub fn build_report_manifest(
  plan : MeasurementPlan,
  features : Array[Feature],
  observations : Array[Observation],
  application_version : String,
) -> ReportManifest {
  let index = build_evidence_index(observations)
  let gate = evaluate_release_gate(plan, features, observations)
  let protocol = protocol_from_plan(plan, application_version)
  let evidence_coverage = if features.length() == 0 {
    1.0
  } else {
    matched_feature_count(index, catalog_feature_ids_from_features(features)).to_double() /
    features.length().to_double()
  }
  let reproducible = application_version.trim() != "" &&
    gate.ready &&
    protocol_is_operational(protocol) &&
    evidence_coverage >= 1.0
  {
    schema_version: 1,
    application_version,
    part_id: plan.part_id,
    feature_count: features.length(),
    observation_count: observations.length(),
    step_count: plan.steps.length(),
    warning_count: plan.summary.warnings.length(),
    evidence_coverage,
    reproducible,
    status: if reproducible {
      "ready"
    } else {
      "blocked"
    },
  }
}

///|
fn catalog_feature_ids_from_features(
  features : Array[Feature],
) -> Array[String] {
  let result = []
  for feature in features {
    result.push(feature.id)
  }
  result
}

///|
/// Return the manifest state used by CLI and report headers.
pub fn report_manifest_status(manifest : ReportManifest) -> String {
  if manifest.reproducible {
    "ready"
  } else if manifest.application_version.trim() == "" ||
    manifest.evidence_coverage <= 0.0 {
    "blocked"
  } else {
    "review"
  }
}

///|
/// Render a stable manifest summary.
pub fn report_manifest_text(manifest : ReportManifest) -> String {
  "schema=" +
  manifest.schema_version.to_string() +
  " version=" +
  manifest.application_version +
  " part=" +
  manifest.part_id +
  " features=" +
  manifest.feature_count.to_string() +
  " observations=" +
  manifest.observation_count.to_string() +
  " steps=" +
  manifest.step_count.to_string() +
  " coverage=" +
  manifest.evidence_coverage.to_string() +
  " status=" +
  report_manifest_status(manifest)
}

///|
/// Return whether a manifest has the current schema.
pub fn manifest_schema_is_current(manifest : ReportManifest) -> Bool {
  manifest.schema_version == 1
}

///|
/// Return whether all recorded counts are internally consistent.
pub fn manifest_counts_are_consistent(manifest : ReportManifest) -> Bool {
  manifest.feature_count >= 0 &&
  manifest.observation_count >= 0 &&
  manifest.step_count >= 0 &&
  manifest.warning_count >= 0 &&
  manifest.evidence_coverage >= 0.0 &&
  manifest.evidence_coverage <= 1.0
}

///|
/// Return a warning label for manifests with incomplete evidence.
pub fn manifest_evidence_label(manifest : ReportManifest) -> String {
  if manifest.evidence_coverage >= 1.0 {
    "complete"
  } else if manifest.evidence_coverage > 0.0 {
    "partial"
  } else {
    "missing"
  }
}

///|
/// Return whether a manifest can be stored as a release artifact.
pub fn manifest_is_publishable(manifest : ReportManifest) -> Bool {
  manifest_schema_is_current(manifest) &&
  manifest_counts_are_consistent(manifest) &&
  manifest.reproducible &&
  report_manifest_status(manifest) == "ready"
}

///|
/// Return the number of features not represented by observations.
pub fn manifest_missing_feature_count(manifest : ReportManifest) -> Int {
  if manifest.feature_count > manifest.observation_count {
    manifest.feature_count - manifest.observation_count
  } else {
    0
  }
}

///|
/// Return whether the manifest records at least one planned step per feature.
pub fn manifest_has_plan_for_features(manifest : ReportManifest) -> Bool {
  manifest.feature_count == 0 || manifest.step_count >= manifest.feature_count
}

///|
/// Return the ratio of planned steps to reported features.
pub fn manifest_step_density(manifest : ReportManifest) -> Double {
  if manifest.feature_count == 0 {
    0.0
  } else {
    manifest.step_count.to_double() / manifest.feature_count.to_double()
  }
}

///|
/// Return whether the manifest is safe to attach to a report.
pub fn manifest_is_attachable(manifest : ReportManifest) -> Bool {
  manifest_counts_are_consistent(manifest) &&
  manifest_has_plan_for_features(manifest) &&
  manifest.application_version.trim() != ""
}

///|
/// Return a compact readiness reason for operator-facing output.
pub fn manifest_readiness_reason(manifest : ReportManifest) -> String {
  if !manifest_schema_is_current(manifest) {
    "unsupported schema"
  } else if manifest.application_version.trim() == "" {
    "version is missing"
  } else if manifest.evidence_coverage < 1.0 {
    "evidence is incomplete"
  } else if manifest.warning_count > 0 {
    "plan warnings require review"
  } else {
    "all release evidence is present"
  }
}