///|
/// A version identifier.
///
/// SemVer 2.0.0 allows two kinds of identifier: all digits, or a string drawn from
/// `[0-9A-Za-z-]`. Numeric ones are held as arbitrary-precision integers, so a value
/// such as `18446744073709551616` does not overflow.
pub(all) enum Identifier {
  /// A numeric identifier, normalised to carry no leading zeroes.
  Num(@bigint.BigInt)
  /// An alphanumeric identifier, kept verbatim.
  Alpha(String)
}

///|
pub fn Identifier::is_numeric(self : Identifier) -> Bool {
  match self {
    Num(_) => true
    Alpha(_) => false
  }
}

///|
/// The integer value, or `None` for an alphanumeric identifier.
pub fn Identifier::as_bigint(self : Identifier) -> @bigint.BigInt? {
  match self {
    Num(n) => Some(n)
    Alpha(_) => None
  }
}

///|
/// The value as an `Int`, or `None` if it is alphanumeric or out of range.
pub fn Identifier::as_int(self : Identifier) -> Int32? {
  match self {
    Num(n) =>
      if n.compare_int(2147483647) <= 0 && n.compare_int(0) >= 0 {
        Some(n.to_int())
      } else {
        None
      }
    Alpha(_) => None
  }
}

///|
pub fn Identifier::numeric(n : Int32) -> Identifier {
  Num(@bigint.BigInt::from_int(n))
}

///|
pub fn Identifier::alpha(s : String) -> Identifier {
  Alpha(s)
}

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

///|
pub impl Show for Identifier with fn to_string(self : Identifier) -> String {
  match self {
    Num(n) => n.to_string()
    Alpha(s) => s
  }
}

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

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

///|
pub extend Identifier with Eq::{equal, not_equal}

///|
pub impl Eq for Identifier with fn equal(self : Identifier, other : Identifier) -> Bool {
  match (self, other) {
    (Num(a), Num(b)) => a == b
    (Alpha(a), Alpha(b)) => a == b
    _ => false
  }
}

///|
pub extend Identifier with Compare::{compare, op_lt, op_le, op_gt, op_ge}

///|
/// Precedence rules for prerelease identifiers (SemVer 2.0.0 ยง11.4):
///
/// 1. numeric identifiers compare numerically;
/// 2. numeric identifiers always rank below alphanumeric ones;
/// 3. alphanumeric identifiers compare in ASCII sort order.
pub impl Compare for Identifier with fn compare(
  self : Identifier,
  other : Identifier,
) -> Int32 {
  match (self, other) {
    (Num(a), Num(b)) => a.compare(b)
    (Num(_), Alpha(_)) => -1
    (Alpha(_), Num(_)) => 1
    (Alpha(a), Alpha(b)) => a.lexical_compare(b)
  }
}

///|
pub extend Identifier with Hash::{hash_combine, hash}

///|
pub impl Hash for Identifier with fn hash_combine(
  self : Identifier,
  hasher : Hasher,
) -> Unit {
  hasher.combine_string(self.to_string())
}

///|
fn compare_identifiers(a : Array[Identifier], b : Array[Identifier]) -> Int32 {
  if a.is_empty() && b.is_empty() {
    return 0
  }
  if a.is_empty() {
    return 1
  }
  if b.is_empty() {
    return -1
  }
  let n = if a.length() < b.length() { a.length() } else { b.length() }
  for i in 0.. b.length() {
    1
  } else {
    0
  }
}