///|
/// Release-oriented facts derived from an inventory, manifest and policy.
pub(all) struct ReleaseReadiness {
  profile : DoctorProfile
  module_name : String?
  version : String?
  repository : String?
  license : String?
  readme : String?
  description : String?
  keywords : Array[String]
  has_license_file : Bool
  has_ci : Bool
  has_examples : Bool
  has_tests : Bool
  has_changelog : Bool
  has_contributing : Bool
  has_acceptance : Bool
  required_commands : Array[String]
} derive(Debug, Eq)

///|
pub fn ReleaseReadiness::summary(self : ReleaseReadiness) -> String {
  let mut metadata = 0
  for
    value in [
      self.module_name,
      self.version,
      self.repository,
      self.license,
      self.description,
    ] {
    if value is Some(_) {
      metadata += 1
    }
  }
  "profile=\{self.profile.label()}, metadata=\{metadata}/5, tests=\{self.has_tests}, ci=\{self.has_ci}"
}

///|
pub fn ReleaseReadiness::missing_items(
  self : ReleaseReadiness,
) -> Array[String] {
  let missing : Array[String] = []
  if self.module_name is None {
    missing.push("moon.mod name")
  }
  if self.version is None {
    missing.push("moon.mod version")
  }
  if self.repository is None {
    missing.push("moon.mod repository")
  }
  if self.license is None || !self.has_license_file {
    missing.push("license metadata or LICENSE")
  }
  if self.description is None {
    missing.push("moon.mod description")
  }
  if self.keywords.length() == 0 {
    missing.push("moon.mod keywords")
  }
  if !self.has_tests {
    missing.push("tests")
  }
  if !self.has_ci {
    missing.push("CI workflow")
  }
  if !self.has_examples {
    missing.push("examples")
  }
  missing
}

///|
fn manifest_text(manifest : MoonManifest?, key : String) -> String? {
  match manifest {
    Some(value) => value.text(key)
    None => None
  }
}

///|
fn manifest_keywords(manifest : MoonManifest?) -> Array[String] {
  match manifest {
    Some(value) => value.text_array("keywords")
    None => []
  }
}

///|
fn inventory_has_tests(inventory : ProjectInventory) -> Bool {
  inventory.files.any(file => {
    file.path.has_suffix("_test.mbt") || file.path.has_suffix("_wbtest.mbt")
  })
}

///|
fn inventory_has_examples(inventory : ProjectInventory) -> Bool {
  inventory
  .files_under("examples")
  .any(file => {
    file.path.has_suffix(".mbt") ||
    file.path.has_suffix(".md") ||
    file.path.has_suffix("moon.pkg")
  })
}

///|
fn inventory_has_ci(inventory : ProjectInventory) -> Bool {
  analyze_workflows(inventory).any(workflow => {
    workflow.has_moon_check && workflow.has_moon_test
  })
}

///|
/// Derive a release status object without running package or network commands.
pub fn analyze_release_readiness(
  inventory : ProjectInventory,
  manifest : MoonManifest?,
  config : DoctorConfig,
) -> ReleaseReadiness {
  {
    profile: config.profile,
    module_name: manifest_text(manifest, "name"),
    version: manifest_text(manifest, "version"),
    repository: manifest_text(manifest, "repository"),
    license: manifest_text(manifest, "license"),
    readme: manifest_text(manifest, "readme"),
    description: manifest_text(manifest, "description"),
    keywords: manifest_keywords(manifest),
    has_license_file: inventory.has_file("LICENSE"),
    has_ci: inventory_has_ci(inventory),
    has_examples: inventory_has_examples(inventory),
    has_tests: inventory_has_tests(inventory),
    has_changelog: inventory.has_file("CHANGELOG.md"),
    has_contributing: inventory.has_file("CONTRIBUTING.md"),
    has_acceptance: inventory.has_file("ACCEPTANCE.md"),
    required_commands: config.required_commands,
  }
}

///|
fn optional_json(value : String?) -> Json {
  match value {
    Some(text) => Json::string(text)
    None => Json::null()
  }
}

///|
pub fn release_readiness_to_json(readiness : ReleaseReadiness) -> Json {
  Json::object({
    "profile": Json::string(readiness.profile.label()),
    "module_name": optional_json(readiness.module_name),
    "version": optional_json(readiness.version),
    "repository": optional_json(readiness.repository),
    "license": optional_json(readiness.license),
    "keywords": Json::array(readiness.keywords.map(Json::string)),
    "has_license_file": Json::boolean(readiness.has_license_file),
    "has_ci": Json::boolean(readiness.has_ci),
    "has_examples": Json::boolean(readiness.has_examples),
    "has_tests": Json::boolean(readiness.has_tests),
    "has_changelog": Json::boolean(readiness.has_changelog),
    "has_contributing": Json::boolean(readiness.has_contributing),
    "has_acceptance": Json::boolean(readiness.has_acceptance),
    "required_commands": Json::array(
      readiness.required_commands.map(Json::string),
    ),
    "missing_items": Json::array(readiness.missing_items().map(Json::string)),
  })
}

///|
pub fn render_release_readiness(readiness : ReleaseReadiness) -> String {
  let builder = StringBuilder()
  builder.write_string("Moon Doctor release readiness\n")
  builder.write_string(readiness.summary() + "\n\n")
  let missing = readiness.missing_items()
  if missing.length() == 0 {
    builder.write_string(
      "All release metadata and repository signals are present.\n",
    )
  } else {
    builder.write_string("Missing or incomplete signals:\n")
    for item in missing {
      builder.write_string("- " + item + "\n")
    }
  }
  builder.write_string("\nRequired commands:\n")
  for command in readiness.required_commands {
    builder.write_string("- " + command + "\n")
  }
  builder.to_string()
}