///|
/// The kind of bump to apply.
///
/// Semantics follow node-semver's `inc()`, including the detail that is easy to miss:
/// the major bump of `1.0.0-pre` is `1.0.0`.
pub(all) enum Release {
  /// `1.2.3` → `2.0.0`;`1.0.0-pre` → `1.0.0`
  Major
  /// `1.2.3` → `1.3.0`;`1.2.0-pre` → `1.2.0`
  Minor
  /// `1.2.3` → `1.2.4`;`1.2.3-pre` → `1.2.3`
  Patch
  /// `1.2.3` → `2.0.0-0`
  PreMajor
  /// `1.2.3` → `1.3.0-0`
  PreMinor
  /// `1.2.3` → `1.2.4-0`
  PrePatch
  /// Bump the last numeric identifier, or raise the patch and append `-0` if there is none
  Prerelease
  /// Drop the prerelease, leaving the numbers alone
  Stable
}

///|
fn with_pre(v : Version, pre : Array[Identifier]) -> Version {
  { major: v.major, minor: v.minor, patch: v.patch, pre, build: [], }
}

///|
/// Bump the last numeric prerelease identifier, appending `0` if there is none.
fn bump_prerelease(v : Version) -> Version {
  let pre = v.pre.copy()
  let mut i = pre.length() - 1
  let mut bumped = false
  while i >= 0 {
    match pre[i] {
      Num(n) => {
        pre[i] = Num(n + one())
        bumped = true
        break
      }
      Alpha(_) => ()
    }
    i = i - 1
  }
  if !bumped {
    pre.push(Identifier::numeric(0))
  }
  with_pre(v, pre)
}

///|
/// Apply a bump.
pub fn Version::inc(self : Version, release : Release) -> Version {
  match release {
    Major => {
      let major = if !self.minor.is_zero() ||
        !self.patch.is_zero() ||
        self.pre.is_empty() {
        self.major + one()
      } else {
        self.major
      }
      mk(major, zero(), zero())
    }
    Minor => {
      let minor = if !self.patch.is_zero() || self.pre.is_empty() {
        self.minor + one()
      } else {
        self.minor
      }
      mk(self.major, minor, zero())
    }
    Patch => {
      let patch = if self.pre.is_empty() {
        self.patch + one()
      } else {
        self.patch
      }
      mk(self.major, self.minor, patch)
    }
    PreMajor =>
      with_pre(mk(self.major + one(), zero(), zero()), [Identifier::numeric(0)])
    PreMinor =>
      with_pre(mk(self.major, self.minor + one(), zero()), [
        Identifier::numeric(0),
      ])
    PrePatch =>
      with_pre(mk(self.major, self.minor, self.patch + one()), [
        Identifier::numeric(0),
      ])
    Prerelease =>
      if self.pre.is_empty() {
        with_pre(mk(self.major, self.minor, self.patch + one()), [
          Identifier::numeric(0),
        ])
      } else {
        bump_prerelease(self)
      }
    Stable => mk(self.major, self.minor, self.patch)
  }
}

///|
/// Parse a bump name, for the CLI.
pub fn Release::parse(name : String) -> Release? {
  match name {
    "major" => Some(Major)
    "minor" => Some(Minor)
    "patch" => Some(Patch)
    "premajor" => Some(PreMajor)
    "preminor" => Some(PreMinor)
    "prepatch" => Some(PrePatch)
    "prerelease" | "pre" => Some(Prerelease)
    "stable" | "release" => Some(Stable)
    _ => None
  }
}

///|
pub extend Release with @debug.Debug::{to_repr}

///|
pub impl @debug.Debug for Release with fn to_repr(self : Release) -> @debug.Repr {
  @debug.Repr(self.to_string())
}

///|
pub extend Release with Show::{to_string, output}

///|
pub impl Show for Release with fn to_string(self : Release) -> String {
  match self {
    Major => "major"
    Minor => "minor"
    Patch => "patch"
    PreMajor => "premajor"
    PreMinor => "preminor"
    PrePatch => "prepatch"
    Prerelease => "prerelease"
    Stable => "stable"
  }
}