///|
pub fn build_release_plan(input : AuditInput) -> ReleasePlan {
  let report = audit_project(input)
  let manifest = parse_manifest(input.moon_mod)
  let namespace_data = namespace_from_manifest(manifest)
  let version = parse_semver(report.version)
  let workflow = analyze_workflow(input.ci)
  let license = analyze_license(input.license_text)
  let blockers : Array[String] = []
  release_collect_blockers(
    blockers, input, report, namespace_data, version, workflow, license,
  )
  release_plan(
    report.package_name,
    report.version,
    release_default_steps(report.version),
    blockers,
  )
}

///|
pub fn release_plan_from_report(
  input : AuditInput,
  report : AuditReport,
) -> ReleasePlan {
  let manifest = parse_manifest(input.moon_mod)
  let namespace_data = namespace_from_manifest(manifest)
  let version = parse_semver(report.version)
  let workflow = analyze_workflow(input.ci)
  let license = analyze_license(input.license_text)
  let blockers : Array[String] = []
  release_collect_blockers(
    blockers, input, report, namespace_data, version, workflow, license,
  )
  release_plan(
    report.package_name,
    report.version,
    release_default_steps(report.version),
    blockers,
  )
}

///|
pub fn ReleaseStep::label(self : ReleaseStep) -> String {
  if self.required {
    "required"
  } else {
    "optional"
  }
}

///|
pub fn ReleaseStep::markdown_row(self : ReleaseStep) -> String {
  "| " +
  self.index.to_string() +
  " | `" +
  self.command +
  "`" +
  " | " +
  self.label() +
  " | " +
  self.description +
  " |\n"
}

///|
pub fn ReleaseStep::is_validation(self : ReleaseStep) -> Bool {
  contains(self.command, "moon check") ||
  contains(self.command, "moon build") ||
  contains(self.command, "moon test")
}

///|
pub fn ReleaseStep::is_publish(self : ReleaseStep) -> Bool {
  contains(self.command, "moon publish")
}

///|
pub fn ReleasePlan::is_blocked(self : ReleasePlan) -> Bool {
  self.blockers.length() > 0
}

///|
pub fn ReleasePlan::required_steps(self : ReleasePlan) -> Array[ReleaseStep] {
  let items : Array[ReleaseStep] = []
  let mut index = 0
  while index < self.steps.length() {
    if self.steps[index].required {
      items.push(self.steps[index])
    }
    index += 1
  }
  items
}

///|
pub fn ReleasePlan::optional_steps(self : ReleasePlan) -> Array[ReleaseStep] {
  let items : Array[ReleaseStep] = []
  let mut index = 0
  while index < self.steps.length() {
    if !self.steps[index].required {
      items.push(self.steps[index])
    }
    index += 1
  }
  items
}

///|
pub fn ReleasePlan::validation_steps(self : ReleasePlan) -> Array[ReleaseStep] {
  let items : Array[ReleaseStep] = []
  let mut index = 0
  while index < self.steps.length() {
    if self.steps[index].is_validation() {
      items.push(self.steps[index])
    }
    index += 1
  }
  items
}

///|
pub fn ReleasePlan::publish_steps(self : ReleasePlan) -> Array[ReleaseStep] {
  let items : Array[ReleaseStep] = []
  let mut index = 0
  while index < self.steps.length() {
    if self.steps[index].is_publish() {
      items.push(self.steps[index])
    }
    index += 1
  }
  items
}

///|
pub fn ReleasePlan::first_blocker(self : ReleasePlan) -> String {
  if self.blockers.length() == 0 {
    ""
  } else {
    self.blockers[0]
  }
}

///|
pub fn ReleasePlan::summary(self : ReleasePlan) -> String {
  let status = if self.is_blocked() { "blocked" } else { "ready" }
  "release \{status}: package=\{self.package_name}, version=\{self.version}, blockers=\{self.blockers.length()}"
}

///|
pub fn ReleasePlan::commands(self : ReleasePlan) -> Array[String] {
  let commands : Array[String] = []
  let mut index = 0
  while index < self.steps.length() {
    commands.push(self.steps[index].command)
    index += 1
  }
  commands
}

///|
pub fn ReleasePlan::command_script(self : ReleasePlan) -> String {
  let mut out = "# Release validation for " + self.package_name + "\n"
  let mut index = 0
  while index < self.steps.length() {
    let step = self.steps[index]
    if step.required {
      out = out + step.command + "\n"
    } else {
      out = out + "# optional: " + step.command + "\n"
    }
    index += 1
  }
  out
}

///|
pub fn ReleasePlan::to_markdown(self : ReleasePlan) -> String {
  let mut out = "## Release Plan\n\n"
  let status = if self.is_blocked() { "blocked" } else { "ready" }
  out = out + "- Package: `" + self.package_name + "`\n"
  out = out + "- Version: `" + self.version + "`\n"
  out = out + "- Status: " + status + "\n"
  out = out + "- Blockers: " + self.blockers.length().to_string() + "\n\n"
  if self.blockers.length() > 0 {
    out = out + "### Blockers\n\n"
    let mut blocker_index = 0
    while blocker_index < self.blockers.length() {
      out = out + "- " + self.blockers[blocker_index] + "\n"
      blocker_index += 1
    }
    out = out + "\n"
  }
  out = out + "### Steps\n\n"
  out = out +
    "| # | Command | Required | Purpose |\n| --- | --- | --- | --- |\n"
  let mut index = 0
  while index < self.steps.length() {
    out = out + self.steps[index].markdown_row()
    index += 1
  }
  out
}

///|
pub fn release_plan_table(plans : Array[ReleasePlan]) -> String {
  let mut out = "| Package | Version | Status | Blockers | Required steps |\n| --- | --- | --- | --- | --- |\n"
  let mut index = 0
  while index < plans.length() {
    let plan = plans[index]
    let status = if plan.is_blocked() { "blocked" } else { "ready" }
    out = out + "| `" + plan.package_name + "`"
    out = out + " | `" + plan.version + "`"
    out = out + " | " + status
    out = out + " | " + plan.blockers.length().to_string()
    out = out + " | " + plan.required_steps().length().to_string() + " |\n"
    index += 1
  }
  out
}

///|
pub fn release_plan_has_command(plan : ReleasePlan, command : String) -> Bool {
  let mut index = 0
  while index < plan.steps.length() {
    if contains(plan.steps[index].command, command) {
      return true
    }
    index += 1
  }
  false
}

///|
fn release_default_steps(version : String) -> Array[ReleaseStep] {
  let steps : Array[ReleaseStep] = []
  steps.push(
    release_step(
      1, "moon check --deny-warn", true, "strict type and warning validation",
    ),
  )
  steps.push(release_step(2, "moon build", true, "build all package targets"))
  steps.push(
    release_step(
      3, "moon test --deny-warn", true, "run tests with warnings treated as failures",
    ),
  )
  steps.push(
    release_step(
      4, "moon run examples/basic", true, "execute the runnable example used by reviewers",
    ),
  )
  steps.push(
    release_step(
      5, "moon publish --dry-run", true, "validate Mooncakes package metadata without publishing",
    ),
  )
  steps.push(
    release_step(
      6, "git status --short --branch", false, "confirm the worktree before tagging",
    ),
  )
  steps.push(
    release_step(
      7,
      "git tag v" + version,
      false,
      "optional release tag for the published version",
    ),
  )
  steps.push(
    release_step(
      8, "moon publish", true, "publish the package with the logged-in Mooncakes account",
    ),
  )
  steps
}

///|
fn release_collect_blockers(
  blockers : Array[String],
  input : AuditInput,
  report : AuditReport,
  namespace_data : NamespaceInfo,
  version : SemVerInfo,
  workflow : WorkflowInfo,
  license : LicenseFacts,
) -> Unit {
  release_add_blocker_if(
    blockers,
    report.score.error_count > 0,
    "audit report still has errors",
  )
  release_add_blocker_if(
    blockers,
    !namespace_data.valid,
    "moon.mod package namespace is invalid",
  )
  release_add_blocker_if(
    blockers,
    !version.valid,
    "moon.mod version is not valid semver",
  )
  release_add_blocker_if(
    blockers,
    !workflow.is_acceptance_ready(),
    "CI workflow does not cover check/build/test/run",
  )
  release_add_blocker_if(
    blockers,
    !license.is_publishable(),
    "LICENSE is missing or not recognized as publishable",
  )
  release_add_blocker_if(
    blockers,
    !input.repository_public,
    "GitHub repository must be public before acceptance",
  )
  release_add_blocker_if(
    blockers,
    input.commit_count < 5,
    "development history should include meaningful commits",
  )
  release_add_blocker_if(
    blockers,
    trim_ascii(input.readme).length() == 0,
    "README must be present and useful",
  )
  release_add_blocker_if(
    blockers,
    !input.package_published,
    "package is not yet published to mooncakes.io",
  )
}

///|
fn release_add_blocker_if(
  blockers : Array[String],
  condition : Bool,
  message : String,
) -> Unit {
  if condition && !release_blocker_seen(blockers, message) {
    blockers.push(message)
  }
}

///|
fn release_blocker_seen(blockers : Array[String], message : String) -> Bool {
  let mut index = 0
  while index < blockers.length() {
    if blockers[index] == message {
      return true
    }
    index += 1
  }
  false
}