///|
/// Whether a string is a decimal amount this importer accepts.
///
/// The accepted shape is an optional `+` or `-`, one or more integer digits, an
/// optional `.` followed by one or more digits. Nothing else: no thousands
/// separators, no trailing point, no exponent, no surrounding whitespace. The
/// check is strict on purpose, because a bank that writes `1.234,56` means
/// something the importer must not guess at.
pub fn is_valid_amount(amount : String) -> Bool {
  let mut digits_before = 0
  let mut digits_after = 0
  let mut has_point = false
  let mut index = 0
  for c in amount.iter() {
    if (c == '-' || c == '+') && index == 0 {
      ()
    } else if c == '.' && !has_point {
      has_point = true
    } else if c >= '0' && c <= '9' {
      if has_point {
        digits_after += 1
      } else {
        digits_before += 1
      }
    } else {
      return false
    }
    index += 1
  }
  digits_before > 0 && (!has_point || digits_after > 0)
}

///|
/// The scale check shared by the decimal and the integer direction, so both
/// refuse the same scales.
fn valid_scale(scale : Int) -> Bool {
  scale >= 0 && scale <= 6
}

///|
/// Convert a decimal amount string to an integer count of minor units.
///
/// `scale` is the number of fractional digits the target ledger stores (`2` for
/// cents); scales from `0` to `6` are accepted. The conversion never uses
/// floating point: it shifts digits and checks `Int64` overflow at every step,
/// so `"9223372036854775808"` at scale `0` returns `None` instead of a wrapped
/// value. Input with more fractional digits than `scale` is rejected rather than
/// rounded, because silently losing a digit is exactly the kind of error an
/// importer must not hide.
pub fn amount_to_minor_units(amount : String, scale : Int) -> Int64? {
  if !is_valid_amount(amount) || !valid_scale(scale) {
    return None
  }
  let mut negative = false
  let mut seen_point = false
  let mut fractional = 0
  let mut value = 0L
  for c in amount.iter() {
    if c == '-' {
      negative = true
    } else if c == '+' {
      ()
    } else if c == '.' {
      seen_point = true
    } else {
      let digit = (c.to_int() - 48).to_int64()
      if value > (9223372036854775807L - digit) / 10L {
        return None
      }
      value = value * 10L + digit
      if seen_point {
        fractional += 1
      }
    }
  }
  if fractional > scale {
    return None
  }
  for _ in fractional.. 922337203685477580L {
      return None
    }
    value = value * 10L
  }
  Some(if negative { -value } else { value })
}

///|
/// Convert an integer count of minor units back into a decimal amount string.
///
/// This is the inverse of [`amount_to_minor_units`] at the same scale:
/// `-4250` at scale `2` becomes `"-42.50"` and `5` becomes `"0.05"`, so a ledger
/// can be rendered back into the decimal form a bank used. `None` is returned
/// only for a scale outside `0` to `6`; every `Int64` is representable,
/// including the smallest one.
pub fn minor_units_to_amount(minor : Int64, scale : Int) -> String? {
  if !valid_scale(scale) {
    return None
  }
  let negative = minor < 0L
  let digits : Array[Int] = []
  let mut value = minor
  // Digits are taken in the negative domain so that the smallest Int64 works:
  // dividing it by -10 yields a positive quotient, which is flipped back.
  while value != 0L {
    let quotient = value / -10L
    let remainder = value % -10L
    digits.push((if remainder < 0L { -remainder } else { remainder }).to_int())
    value = if quotient > 0L { -quotient } else { quotient }
  }
  if digits.length() == 0 {
    digits.push(0)
  }
  while digits.length() < scale + 1 {
    digits.push(0)
  }
  let out = StringBuilder()
  if negative {
    out.write_char('-')
  }
  let count = digits.length()
  for position in 0.. String? {
  if !is_valid_amount(amount) {
    return None
  }
  let chars = amount.iter().to_array()
  let negative = chars[0] == '-'
  let start = if chars[0] == '-' || chars[0] == '+' { 1 } else { 0 }
  let mut point = chars.length()
  let mut any_nonzero = false
  for position in start..= '1' && chars[position] <= '9' {
      any_nonzero = true
    }
  }
  let out = StringBuilder()
  if negative && any_nonzero {
    out.write_char('-')
  }
  let mut first_significant = -1
  for position in start.. Int64? {
  let sum = left + right
  if right > 0L && sum < left {
    None
  } else if right < 0L && sum > left {
    None
  } else {
    Some(sum)
  }
}

///|
/// Subtract two counts of minor units, or return `None` when the difference
/// would overflow.
pub fn checked_sub(left : Int64, right : Int64) -> Int64? {
  let difference = left - right
  if right < 0L && difference < left {
    None
  } else if right > 0L && difference > left {
    None
  } else {
    Some(difference)
  }
}

///|
/// Sum a list of amounts at one scale, or return `None` when an amount cannot be
/// converted or the total overflows.
///
/// `opening` is added first, so a caller can fold a running balance over a batch
/// of statements without reimplementing the overflow checks.
pub fn sum_amounts(
  amounts : Array[String],
  scale : Int,
  opening : Int64,
) -> Int64? {
  let mut total = opening
  for amount in amounts {
    let minor = match amount_to_minor_units(amount, scale) {
      Some(value) => value
      None => return None
    }
    total = match checked_add(total, minor) {
      Some(sum) => sum
      None => return None
    }
  }
  Some(total)
}