///|
/// Helper functions

///|

///|

///|
fn au_trim_string(s : String) -> String {
  str_trim_string(s)
}

///|
fn au_char_at(s : String, idx : Int) -> Int {
  str_char_at(s, idx)
}

///|
fn au_string_slice(s : String, start : Int, end : Int) -> String {
  str_string_slice(s, start, end)
}

///|
fn au_string_slice_from(s : String, start : Int) -> String {
  str_string_slice_from(s, start)
}

///|
fn au_find_char(s : String, target : Int) -> Int? {
  let mut idx = 0
  while idx < s.length() {
    if au_char_at(s, idx) == target {
      return Some(idx)
    }
    idx = idx + 1
  }
  None
}

///|
fn au_split_string(s : String, sep : String) -> Array[String] {
  str_split_string(s, sep)
}

///|
fn au_to_lower_case(s : String) -> String {
  str_to_lower_case(s)
}

///|
fn au_starts_with_ignore_case(s : String, prefix : String) -> Bool {
  let s_lower = au_to_lower_case(s)
  let prefix_lower = au_to_lower_case(prefix)
  let prefix_len = prefix_lower.length()
  if s_lower.length() < prefix_len {
    return false
  }
  let mut idx = 0
  while idx < prefix_len {
    if s_lower[idx] != prefix_lower[idx] {
      return false
    }
    idx = idx + 1
  }
  true
}

///|
fn au_strip_scheme(input : String, scheme : String) -> String? {
  let s = au_trim_string(input)
  let scheme_len = scheme.length()
  if s.length() <= scheme_len {
    return None
  }

  // Get prefix
  let prefix = au_string_slice(s, 0, scheme_len)
  if !au_starts_with_ignore_case(prefix, scheme) {
    return None
  }
  let rest = au_string_slice_from(s, scheme_len)
  if rest.length() == 0 {
    return None
  }
  let first_char = au_char_at(rest, 0)
  if first_char != 32 && first_char != 9 {
    return None
  }
  Some(au_trim_string(au_string_slice_from(rest, 1)))
}

///|
fn au_is_ows(b : Int) -> Bool {
  b == 32 || b == 9
}

///|
fn au_is_token_char(b : Int) -> Bool {
  b == 33 ||
  b == 35 ||
  b == 36 ||
  b == 37 ||
  b == 38 ||
  b == 39 ||
  b == 42 ||
  b == 43 ||
  b == 45 ||
  b == 46 ||
  (b >= 48 && b <= 57) || // 0-9
  (b >= 65 && b <= 90) || // A-Z
  b == 94 ||
  b == 95 ||
  b == 96 ||
  (b >= 97 && b <= 122) || // a-z
  b == 124 ||
  b == 126
}

///|
fn au_is_token68_char(b : Int) -> Bool {
  (b >= 65 && b <= 90) || // A-Z
  (b >= 97 && b <= 122) || // a-z
  (b >= 48 && b <= 57) || // 0-9
  b == 45 ||
  b == 46 ||
  b == 95 ||
  b == 126 ||
  b == 43 ||
  b == 47
}

///|
fn au_is_token68(value : String) -> Bool {
  if value.length() == 0 {
    return false
  }
  let mut idx = 0
  while idx < value.length() {
    let ch = au_char_at(value, idx)
    if !au_is_token68_char(ch) {
      return false
    }
    idx = idx + 1
  }
  true
}

///|
fn au_parse_auth_params(
  input : String,
) -> Result[Array[(String, String)], AuthError] {
  let params : Array[(String, String)] = []
  let mut i = 0
  while i < input.length() {
    // Skip OWS
    while i < input.length() && au_is_ows(au_char_at(input, i)) {
      i = i + 1
    }
    // Check for comma
    if i < input.length() && au_char_at(input, i) == 44 {
      i = i + 1
      continue
    }
    // Skip OWS again
    while i < input.length() && au_is_ows(au_char_at(input, i)) {
      i = i + 1
    }
    if i >= input.length() {
      break
    }

    // Parse name (token)
    let name_start = i
    while i < input.length() && au_is_token_char(au_char_at(input, i)) {
      i = i + 1
    }
    if i == name_start {
      return Err(InvalidParameter)
    }
    let name = au_to_lower_case(au_string_slice(input, name_start, i))

    // Skip OWS
    while i < input.length() && au_is_ows(au_char_at(input, i)) {
      i = i + 1
    }
    if i >= input.length() || au_char_at(input, i) != 61 {
      return Err(InvalidParameter)
    }
    i = i + 1

    // Skip OWS
    while i < input.length() && au_is_ows(au_char_at(input, i)) {
      i = i + 1
    }
    if i >= input.length() {
      return Err(InvalidParameter)
    }

    // Parse value (token or quoted-string)
    let value = if au_char_at(input, i) == 34 {
      // Quoted string
      i = i + 1
      let mut value = ""
      let mut escaped = false
      let mut closed = false
      while i < input.length() {
        let ch = Int::unsafe_to_char(au_char_at(input, i))
        if escaped {
          value = value + ch.to_string()
          escaped = false
        } else if ch == '\\' {
          escaped = true
        } else if ch == '"' {
          i = i + 1
          closed = true
          break
        } else {
          value = value + ch.to_string()
        }
        i = i + 1
      }
      if escaped || !closed {
        return Err(InvalidParameter)
      }
      value
    } else {
      // Token
      let value_start = i
      while i < input.length() &&
            !au_is_ows(au_char_at(input, i)) &&
            au_char_at(input, i) != 44 {
        i = i + 1
      }
      let token = au_string_slice(input, value_start, i)
      if token.length() == 0 || !au_is_valid_token(token) {
        return Err(InvalidParameter)
      }
      token
    }
    params.push((name, value))

    // Skip OWS
    while i < input.length() && au_is_ows(au_char_at(input, i)) {
      i = i + 1
    }
    if i < input.length() && au_char_at(input, i) == 44 {
      i = i + 1
    }
  }
  if params.length() == 0 {
    return Err(InvalidFormat)
  }
  Ok(params)
}

///|
fn au_is_valid_token(value : String) -> Bool {
  if value.length() == 0 {
    return false
  }
  let mut idx = 0
  while idx < value.length() {
    if !au_is_token_char(au_char_at(value, idx)) {
      return false
    }
    idx = idx + 1
  }
  true
}

///|
fn au_has_required_params(
  params : Array[(String, String)],
  required : Array[String],
) -> Bool {
  let mut idx = 0
  let mut found = 0
  while idx < required.length() {
    let req = au_to_lower_case(required[idx])
    let mut j = 0
    let mut found_this = false
    while j < params.length() {
      let (n, _) = params[j]
      if n == req {
        found_this = true
        break
      }
      j = j + 1
    }
    if found_this {
      found = found + 1
    }
    idx = idx + 1
  }
  found == required.length()
}

///|
fn au_needs_quoting(value : String) -> Bool {
  if value.length() == 0 {
    return true
  }
  let mut idx = 0
  while idx < value.length() {
    if !au_is_token_char(au_char_at(value, idx)) {
      return true
    }
    idx = idx + 1
  }
  false
}

///|
fn au_escape_quotes(value : String) -> String {
  let mut result = ""
  let mut idx = 0
  while idx < value.length() {
    let ch = value[idx]
    let ch_int = ch.to_int()
    if ch_int == 92 {
      // backslash
      result = result + "\\\\"
    } else if ch_int == 34 {
      // quote
      result = result + "\\\""
    } else {
      result = result + Int::unsafe_to_char(ch_int).to_string()
    }
    idx = idx + 1
  }
  result
}

///|
fn au_format_auth_params(params : Array[(String, String)]) -> String {
  let mut result = ""
  let mut idx = 0
  while idx < params.length() {
    if idx > 0 {
      result = result + ", "
    }
    let (name, value) = params[idx]
    if au_needs_quoting(value) {
      result = result + name + "=\"" + au_escape_quotes(value) + "\""
    } else {
      result = result + name + "=" + value
    }
    idx = idx + 1
  }
  result
}

///|

///|
/// Base64 encoding/decoding (RFC 4648)

///|

///|
fn au_base64_encode(input : String) -> String {
  // Base64 alphabet
  let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  let mut result = ""
  let mut i = 0
  let len = input.length()
  while i < len {
    let b0 = au_char_at(input, i)
    let b1 = if i + 1 < len { au_char_at(input, i + 1) } else { 0 }
    let b2 = if i + 2 < len { au_char_at(input, i + 2) } else { 0 }

    // Combine 3 bytes into 24-bit number by shifting
    // b0 << 16 | b1 << 8 | b2
    let i0 = b0 >> 2
    let i1 = ((b0 & 3) << 4) | (b1 >> 4)
    let i2 = if i + 1 < len { ((b1 & 15) << 2) | (b2 >> 6) } else { -1 }
    let i3 = if i + 2 < len { b2 & 63 } else { -1 }

    // Get the character from alphabet at the calculated indices
    result = result + Int::unsafe_to_char(au_char_at(alphabet, i0)).to_string()
    result = result + Int::unsafe_to_char(au_char_at(alphabet, i1)).to_string()
    if i2 >= 0 {
      result = result +
        Int::unsafe_to_char(au_char_at(alphabet, i2)).to_string()
    } else {
      result = result + "="
    }
    if i3 >= 0 {
      result = result +
        Int::unsafe_to_char(au_char_at(alphabet, i3)).to_string()
    } else {
      result = result + "="
    }
    i = i + 3
  }
  result
}

///|
fn au_base64_decode_char(c : Int) -> Int? {
  if c >= 65 && c <= 90 {
    // A-Z
    Some(c - 65)
  } else if c >= 97 && c <= 122 {
    // a-z
    Some(c - 97 + 26)
  } else if c >= 48 && c <= 57 {
    // 0-9
    Some(c - 48 + 52)
  } else if c == 43 {
    // +
    Some(62)
  } else if c == 47 {
    // /
    Some(63)
  } else if c == 61 {
    // = (padding) - handled in decode loop
    Some(-2)
  } else if c == 32 || c == 9 || c == 10 || c == 13 {
    // Whitespace (skip in decoding loop)
    Some(-1)
  } else {
    None
  }
}

///|
fn au_base64_decode(input : String) -> Result[String, AuthError] {
  let mut result = ""
  let mut buf = 0
  let mut bits = 0
  let mut idx = 0
  while idx < input.length() {
    let ch = au_char_at(input, idx)
    match au_base64_decode_char(ch) {
      None => return Err(Base64DecodeError)
      Some(-1) => () // Skip whitespace
      Some(-2) =>
        // Padding character - end of input
        break
      Some(val) => {
        buf = buf * 64 + val
        bits = bits + 6
        if bits >= 8 {
          bits = bits - 8
          let byte = buf / au_pow2(bits)
          result = result + Int::unsafe_to_char(byte).to_string()
          buf = buf % au_pow2(bits)
        }
      }
    }
    idx = idx + 1
  }
  Ok(result)
}

///|
fn au_pow2(n : Int) -> Int {
  if n == 0 {
    1
  } else if n == 1 {
    2
  } else if n == 2 {
    4
  } else if n == 3 {
    8
  } else if n == 4 {
    16
  } else if n == 5 {
    32
  } else if n == 6 {
    64
  } else if n == 7 {
    128
  } else if n == 8 {
    256
  } else if n == 9 {
    512
  } else if n == 10 {
    1024
  } else if n == 11 {
    2048
  } else if n == 12 {
    4096
  } else if n == 13 {
    8192
  } else if n == 14 {
    16384
  } else if n == 15 {
    32768
  } else if n == 16 {
    65536
  } else if n == 17 {
    131072
  } else if n == 18 {
    262144
  } else if n == 19 {
    524288
  } else if n == 20 {
    1048576
  } else {
    1
  }
}