// `moui doctor` evidence scanning. Looks for `verify-manifest.json` under
// `artifacts///` and summarizes whether runtime evidence is
// ready per platform with a pure MoonBit scan.

///|
priv struct EvidenceStatus {
  platform : String
  app_id : String
  manifest_path : String
  passed : Bool
  observations_summary : String
}

///|
/// Scan `artifacts//*/verify-manifest.json` for the given platforms.
/// Returns a map keyed by `:`. Missing manifests produce no
/// entry; entries with malformed JSON are skipped (pure scan, no exceptions).
fn check_evidence_ready(
  project_root : String,
  platforms : Array[String],
  host : HostPlatform,
) -> Map[String, EvidenceStatus] {
  let _ = host
  let result : Map[String, EvidenceStatus] = Map([])
  for platform in platforms {
    let platform_dir = join_path(join_path(project_root, "artifacts"), platform)
    if !path_is_dir(platform_dir) {
      continue
    }
    let entries = @fs.read_dir(platform_dir) catch { _ => continue }
    for entry in entries {
      let app_id = entry
      let manifest_path = join_path(
        join_path(platform_dir, app_id),
        "verify-manifest.json",
      )
      if !path_is_file(manifest_path) {
        continue
      }
      match read_evidence_manifest(manifest_path, platform, app_id) {
        Some(status) => result[platform + ":" + app_id] = status
        None => ()
      }
    }
  }
  result
}

///|
/// Read a single `verify-manifest.json` and parse the canonical fields.
/// Returns `None` when the file is missing, malformed, or produced by an
/// unknown generator.
fn read_evidence_manifest(
  manifest_path : String,
  platform : String,
  app_id : String,
) -> EvidenceStatus? {
  let text = @fs.read_file_to_string(manifest_path) catch { _ => return None }
  let parsed = @json.parse(text) catch { _ => return None }
  guard parsed is Object(fields) else { return None }
  let generated_by = match fields.get("generatedBy") {
    Some(String(value)) => value
    _ => return None
  }
  if !is_accepted_generated_by(generated_by) {
    return None
  }
  let status = match fields.get("status") {
    Some(String(value)) => value
    _ => "unknown"
  }
  let passed = status == "passed"
  // Summarize observations: count of passed / total.
  let mut passed_count = 0
  let mut total = 0
  match fields.get("observations") {
    Some(Array(items)) =>
      for item in items {
        guard item is Object(obs_fields) else { continue }
        let obs_passed = match obs_fields.get("passed") {
          Some(True) => true
          Some(False) => false
          _ => continue
        }
        total += 1
        if obs_passed {
          passed_count += 1
        }
      }
    _ => ()
  }
  let summary = passed_count.to_string() + "/" + total.to_string() + " passed"
  Some({
    platform,
    app_id,
    manifest_path,
    passed,
    observations_summary: summary,
  })
}

///|
/// Render an `EvidenceStatus` map as a multi-line text block for `moui doctor`.
fn evidence_status_to_text(statuses : Map[String, EvidenceStatus]) -> String {
  if statuses.is_empty() {
    return "(no verify evidence found)\n"
  }
  let mut text = ""
  // Sorted for deterministic output.
  let keys : Array[String] = []
  for key, _value in statuses {
    keys.push(key)
  }
  keys.sort()
  for key in keys {
    match statuses.get(key) {
      Some(status) =>
        text += "[" +
          status.platform +
          "] " +
          status.app_id +
          " : " +
          (if status.passed { "passed" } else { "FAILED" }) +
          " (" +
          status.observations_summary +
          ")\n"
      None => ()
    }
  }
  text
}