///|
pub fn semver_recommendation(old_text : String, new_text : String) -> String {
  impact_label(diff_interfaces(old_text, new_text).recommendation)
}

///|
/// Parse a strict `major.minor.patch` SemVer core version.
pub fn parse_version(text : String) -> Version? {
  parse_version_view(text[:])
}

///|
/// Convert a parsed version back to its canonical `major.minor.patch` form.
pub fn version_to_string(version : Version) -> String {
  "\{version.major}.\{version.minor}.\{version.patch}"
}

///|
/// Compare two parsed versions.
///
/// Returns `-1` when `left < right`, `0` when equal, and `1` when
/// `left > right`.
pub fn compare_versions(left : Version, right : Version) -> Int {
  if left.major < right.major {
    -1
  } else if left.major > right.major {
    1
  } else if left.minor < right.minor {
    -1
  } else if left.minor > right.minor {
    1
  } else if left.patch < right.patch {
    -1
  } else if left.patch > right.patch {
    1
  } else {
    0
  }
}

///|
/// Check whether `next` satisfies the SemVer bump required by a report.
pub fn check_version_bump(
  report : ApiReport,
  current : String,
  next : String,
) -> VersionCheck {
  let required = report.recommendation
  match parse_version(current) {
    None =>
      make_version_check(
        false, required, current, next, "current version must be a strict major.minor.patch value",
      )
    Some(current_version) =>
      match parse_version(next) {
        None =>
          make_version_check(
            false, required, current, next, "next version must be a strict major.minor.patch value",
          )
        Some(next_version) =>
          check_parsed_version_bump(
            required, current, next, current_version, next_version,
          )
      }
  }
}

///|
/// Diff two interface texts and check the proposed SemVer bump.
pub fn check_interface_version_bump(
  old_text : String,
  new_text : String,
  current : String,
  next : String,
) -> VersionCheck {
  check_version_bump(diff_interfaces(old_text, new_text), current, next)
}

///|
/// Build a release-oriented decision from API, diagnostics, and SemVer data.
pub fn make_release_plan(
  report : ApiReport,
  diagnostics : Array[ApiDiagnostic],
  current : String,
  next : String,
) -> ReleasePlan {
  let version_check = check_version_bump(report, current, next)
  let diagnostic_summary = summarize_diagnostics(diagnostics)
  let status = release_plan_status(diagnostic_summary, version_check)
  {
    report,
    diagnostics,
    version_check,
    summary: summarize_report(report),
    diagnostic_summary,
    status,
    decision: release_plan_decision(status, version_check.required),
    next_action: release_plan_next_action(status, version_check),
  }
}

///|
fn parse_version_view(text : StringView) -> Version? {
  let text = text.trim()
  match text.find(".") {
    None => None
    Some(first_dot) => {
      let major_part = text.view(start_offset=0, end_offset=first_dot)
      let rest = text.view(start_offset=first_dot + 1)
      match rest.find(".") {
        None => None
        Some(second_dot) => {
          let minor_part = rest.view(start_offset=0, end_offset=second_dot)
          let patch_part = rest.view(start_offset=second_dot + 1)
          match patch_part.find(".") {
            Some(_) => None
            None =>
              match parse_version_number(major_part) {
                None => None
                Some(major) =>
                  match parse_version_number(minor_part) {
                    None => None
                    Some(minor) =>
                      match parse_version_number(patch_part) {
                        None => None
                        Some(patch) => Some({ major, minor, patch })
                      }
                  }
              }
          }
        }
      }
    }
  }
}

///|
fn parse_version_number(text : StringView) -> Int? {
  if text.length() == 0 {
    return None
  }
  if text.length() > 1 && text.unsafe_get(0).unsafe_to_char() == '0' {
    return None
  }
  let mut value = 0
  for ch in text {
    match decimal_digit_value(ch) {
      None => return None
      Some(digit) => value = value * 10 + digit
    }
  }
  Some(value)
}

///|
fn decimal_digit_value(ch : Char) -> Int? {
  let code = ch.to_int()
  if code >= '0'.to_int() && code <= '9'.to_int() {
    Some(code - '0'.to_int())
  } else {
    None
  }
}

///|
fn check_parsed_version_bump(
  required : Impact,
  current : String,
  next : String,
  current_version : Version,
  next_version : Version,
) -> VersionCheck {
  if compare_versions(next_version, current_version) <= 0 {
    return make_version_check(
      false, required, current, next, "next version must be greater than current version",
    )
  }
  if version_satisfies_required_bump(required, current_version, next_version) {
    make_version_check(
      true,
      required,
      current,
      next,
      "next version satisfies the \{impact_label(required)} recommendation",
    )
  } else {
    make_version_check(
      false,
      required,
      current,
      next,
      required_bump_failure_reason(required),
    )
  }
}

///|
fn version_satisfies_required_bump(
  required : Impact,
  current : Version,
  next : Version,
) -> Bool {
  match required {
    Patch => compare_versions(next, current) > 0
    Minor =>
      next.major > current.major ||
      (next.major == current.major && next.minor > current.minor)
    Major => next.major > current.major
  }
}

///|
fn required_bump_failure_reason(required : Impact) -> String {
  match required {
    Patch => "patch recommendation requires any greater next version"
    Minor => "minor recommendation requires increasing minor or major version"
    Major => "major recommendation requires increasing major version"
  }
}

///|
fn make_version_check(
  ok : Bool,
  required : Impact,
  current : String,
  next : String,
  reason : String,
) -> VersionCheck {
  { ok, required, current, next, reason }
}

///|
fn release_plan_status(
  diagnostic_summary : DiagnosticSummary,
  version_check : VersionCheck,
) -> String {
  if diagnostic_summary.errors > 0 {
    "blocked"
  } else if version_check.ok {
    "ready"
  } else {
    "needs-version-bump"
  }
}

///|
fn release_plan_decision(status : String, required : Impact) -> String {
  if status == "blocked" {
    "Fix interface snapshot diagnostics before publishing."
  } else if status == "ready" {
    "The proposed version satisfies the \{impact_label(required)} API impact."
  } else {
    "Increase the proposed version to satisfy the \{impact_label(required)} API impact."
  }
}

///|
fn release_plan_next_action(
  status : String,
  version_check : VersionCheck,
) -> String {
  if status == "blocked" {
    "Resolve diagnostic errors, regenerate package interfaces, then rerun MoonGuard."
  } else if status == "ready" {
    "Publish or merge after normal project checks pass."
  } else if version_check.required == Major {
    "Use a major version bump or accept the breaking API changes explicitly."
  } else if version_check.required == Minor {
    "Use a minor or major version bump for the added public API."
  } else {
    "Use any greater patch, minor, or major version."
  }
}