///|
/// One decoded signed Base64-VLQ value and the first unread UTF-16 offset.
pub(all) struct VlqValue {
  value : Int
  next_offset : Int
} derive(Eq, Debug)

///|
const MAX_VLQ_INT : Int = 2147483647

///|
const MAX_VLQ_UNSIGNED : Int64 = 4294967295L

///|
fn base64_digit_to_char(value : Int) -> Char {
  let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  match alphabet.get_char(value) {
    Some(ch) => ch
    None => 'A'
  }
}

///|
fn base64_char_to_digit(ch : Char) -> Int? {
  match ch {
    'A'..='Z' => Some(ch.to_int() - 'A')
    'a'..='z' => Some(ch.to_int() - 'a' + 26)
    '0'..='9' => Some(ch.to_int() - '0' + 52)
    '+' => Some(62)
    '/' => Some(63)
    _ => None
  }
}

///|
/// Encode one signed integer using the Base64-VLQ alphabet required by
/// ECMA-426.
///
pub fn encode_vlq(value : Int) -> String {
  let signed : Int64 = if value == -2147483648 {
    // ECMA-426 reserves the negative-zero bit pattern for the minimum i32.
    1L
  } else {
    let wide = value.to_int64()
    if wide < 0L {
      -wide * 2L + 1L
    } else {
      wide * 2L
    }
  }
  let output = StringBuilder()
  let mut remaining = signed
  for ;; {
    let mut digit = (remaining % 32L).to_int()
    remaining = remaining / 32L
    if remaining > 0L {
      digit = digit + 32
    }
    output.write_char(base64_digit_to_char(digit))
    if remaining == 0 {
      break
    }
  }
  output.to_string()
}

///|
/// Decode one signed Base64-VLQ value at `offset`.
///
/// The returned `next_offset` can be passed into another call when decoding a
/// Source Map segment.
pub fn decode_vlq_at(
  input : String,
  offset~ : Int,
) -> VlqValue raise SourceMapError {
  if offset < 0 || offset >= input.length() {
    raise InvalidVlq(offset~, message="expected a Base64-VLQ value")
  }
  let mut cursor = offset
  let mut accumulated = 0L
  let mut multiplier = 1L
  let mut digits = 0
  for ;; {
    let ch = match input.get_char(cursor) {
      Some(ch) => ch
      None =>
        raise InvalidVlq(offset=cursor, message="unterminated Base64-VLQ value")
    }
    let digit = match base64_char_to_digit(ch) {
      Some(value) => value
      None =>
        raise InvalidVlq(offset=cursor, message="invalid Base64-VLQ character")
    }
    let continuation = digit >= 32
    let payload = digit % 32
    if payload > 0 && multiplier > MAX_VLQ_UNSIGNED / payload.to_int64() {
      raise InvalidVlq(offset=cursor, message="Base64-VLQ value overflows Int")
    }
    let contribution = payload.to_int64() * multiplier
    if accumulated > MAX_VLQ_UNSIGNED - contribution {
      raise InvalidVlq(offset=cursor, message="Base64-VLQ value overflows Int")
    }
    accumulated = accumulated + contribution
    cursor = cursor + 1
    digits = digits + 1
    if !continuation {
      break
    }
    if digits >= 7 {
      raise InvalidVlq(offset=cursor, message="Base64-VLQ value is too long")
    }
    if cursor >= input.length() {
      raise InvalidVlq(offset=cursor, message="unterminated Base64-VLQ value")
    }
    if multiplier > MAX_VLQ_UNSIGNED / 32L {
      raise InvalidVlq(
        offset=cursor,
        message="Base64-VLQ multiplier overflows Int",
      )
    }
    multiplier = multiplier * 32L
  }
  let negative = accumulated % 2L == 1L
  let magnitude = accumulated / 2L
  if !negative && magnitude > MAX_VLQ_INT.to_int64() {
    raise InvalidVlq(offset~, message="positive Base64-VLQ value overflows Int")
  }
  let value = if negative && magnitude == 0L {
    -2147483648
  } else if negative {
    (-magnitude).to_int()
  } else {
    magnitude.to_int()
  }
  { value, next_offset: cursor }
}

///|
/// Decode a string containing exactly one Base64-VLQ value.
pub fn decode_vlq(input : String) -> Int raise SourceMapError {
  let decoded = decode_vlq_at(input, offset=0)
  if decoded.next_offset != input.length() {
    raise InvalidVlq(
      offset=decoded.next_offset,
      message="unexpected data after Base64-VLQ value",
    )
  }
  decoded.value
}