///|
// Go strconv.ParseInt with explicit base 10: optional sign, ASCII digits only,
// signed 64-bit range. Accumulate negatively so MinInt64 stays representable.
fn parse_decimal(s : String, loc : Location) -> Value raise {
  let chars = s.to_array()
  let signed = !chars.is_empty() && (chars[0] == '-' || chars[0] == '+')
  let start = if signed { 1 } else { 0 }
  if chars.length() == start {
    raise problem(Runtime, loc, s + " is not a base 10 integer")
  }
  let negative = signed && chars[0] == '-'
  let bound = if negative {
    -9223372036854775807L - 1L
  } else {
    -9223372036854775807L
  }
  let mut value = 0L
  for i in start.. 9 || value < bound / 10L {
      raise problem(Runtime, loc, s + " is not a base 10 integer")
    }
    value *= 10L
    if value < bound + digit.to_int64() {
      raise problem(Runtime, loc, s + " is not a base 10 integer")
    }
    value -= digit.to_int64()
  }
  Num((if negative { value } else { -value }).to_double())
}

///|
// Unicode 15 White_Space, used by the pinned Go strings.TrimSpace.
fn trim_space(s : String) -> String {
  s
  .trim(
    chars="\u0009\u000a\u000b\u000c\u000d\u0020\u0085\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000",
  )
  .to_owned()
}

///|
fn equal_fold(a : String, b : String) -> Bool {
  let x = a.iter()
  let y = b.iter()
  while x.next() is Some(c) {
    guard y.next() is Some(d) else { return false }
    let c = c.to_int()
    let d = d.to_int()
    if unicode_fold.get(c).unwrap_or(c) != unicode_fold.get(d).unwrap_or(d) {
      return false
    }
  }
  y.next() is None
}

///|
fn Vm::strip_characters(
  self : Vm,
  value : Value,
  chars : Value,
  name : String,
  loc : Location,
) -> Value raise {
  guard value is Str(s) else {
    raise problem(
      Runtime,
      loc,
      "Unexpected type " + value_type(value) + ", expected string",
    )
  }
  let points = s.to_array()
  let includes = fn(c : Char) raise {
    self.tick(loc)
    match chars {
      Str(chars) => chars.contains_char(c)
      ArrayValue(items) => {
        for item in items {
          if self.equal(self.force(item, loc), Str(c.to_string()), loc) {
            return true
          }
        }
        false
      }
      _ =>
        raise problem(
          Runtime,
          loc,
          "std.member first argument must be an array or a string",
        )
    }
  }
  let mut start = 0
  let mut end = points.length()
  if name != "rstripChars" {
    while start < end && includes(points[start]) {
      start += 1
    }
  }
  if name != "lstripChars" {
    while start < end && includes(points[end - 1]) {
      end -= 1
    }
  }
  Str(String::from_array(points[start:end]))
}

///|
fn Vm::split_limit(
  self : Vm,
  s : String,
  separator : String,
  maximum : Int,
  reverse : Bool,
  loc : Location,
) -> Value raise {
  if maximum < -1 {
    raise problem(
      Runtime,
      loc,
      "std." +
      (if reverse { "splitLimitR" } else { "splitLimit" }) +
      " third parameter should be -1 or non-negative, got " +
      maximum.to_string(),
    )
  }
  if separator.is_empty() {
    raise problem(
      Runtime,
      loc,
      "std." +
      (if reverse { "splitLimitR" } else { "splitLimit" }) +
      " second parameter should have length 1 or greater, got 0",
    )
  }
  let parts : Array[Int] = []
  let mut rest = s[:]
  while maximum == -1 || parts.length() < maximum {
    self.tick(loc)
    let found = if reverse {
      rest.rev_find(separator)
    } else {
      rest.find(separator)
    }
    guard found is Some(index) else { break }
    if reverse {
      parts.push(
        self.store(Ready(Str(rest[index + separator.length():].to_owned()))),
      )
      rest = rest[:index]
    } else {
      parts.push(self.store(Ready(Str(rest[:index].to_owned()))))
      rest = rest[index + separator.length():]
    }
  }
  parts.push(self.store(Ready(Str(rest.to_owned()))))
  ArrayValue(if reverse { parts.rev() } else { parts })
}