///|
fn add_issue(
  issues : Array[ValidationIssue],
  path : String,
  message : String,
) -> Unit {
  issues.push({ path, message })
}

///|
fn validate_contract(
  contract : ContractRef,
  path : String,
  issues : Array[ValidationIssue],
) -> Unit {
  if !safe_identifier(contract.id) {
    add_issue(issues, path + ".id", "must be a safe identifier")
  }
  if contract.version.trim().is_empty() {
    add_issue(issues, path + ".version", "must not be empty")
  }
  if !safe_artifact_path(contract.path) {
    add_issue(issues, path + ".path", "must remain inside the pack root")
  }
}

///|
fn validate_unique_contracts(
  contracts : Array[ContractRef],
  section : String,
  issues : Array[ValidationIssue],
) -> Unit {
  for i in 0.. Bool {
  manifest.schemas.any(fn(schema) { schema.id == id })
}

///|
fn workflow_declared(manifest : MoonPackManifest, id : String) -> Bool {
  manifest.workflows.any(fn(workflow) { workflow.id == id })
}

///|
pub fn validate_manifest(manifest : MoonPackManifest) -> ValidationReport {
  let issues : Array[ValidationIssue] = []
  if !safe_identifier(manifest.id) {
    add_issue(issues, "id", "must be a safe identifier")
  }
  if manifest.version.trim().is_empty() {
    add_issue(issues, "version", "must not be empty")
  }
  if manifest.display_name.trim().is_empty() {
    add_issue(issues, "display_name", "must not be empty")
  }
  if !safe_identifier(manifest.product_id) {
    add_issue(issues, "product_id", "must be a safe identifier")
  }
  for i in 0.. add_issue(issues, "uninstall_contract", "must be declared")
    Some(contract) => validate_contract(contract, "uninstall_contract", issues)
  }
  { ok: issues.is_empty(), issues }
}

///|
pub fn has_authority(
  manifest : MoonPackManifest,
  authority : AuthorityClass,
) -> Bool {
  manifest.tools.any(fn(tool) { tool.authority == authority })
}