///|
/// Encode arbitrary metadata bytes using the RFC 4648 basic alphabet.
/// The result is always padded and therefore has one canonical spelling.
pub fn encode_base64(input : Bytes) -> String {
  let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  let output = StringBuilder()
  let mut index = 0
  while index + 3 <= input.length() {
    let a = input[index].to_int()
    let b = input[index + 1].to_int()
    let c = input[index + 2].to_int()
    output.write_char(alphabet[a >> 2].to_int().unsafe_to_char())
    output.write_char(
      alphabet[((a & 3) << 4) | (b >> 4)].to_int().unsafe_to_char(),
    )
    output.write_char(
      alphabet[((b & 15) << 2) | (c >> 6)].to_int().unsafe_to_char(),
    )
    output.write_char(alphabet[c & 63].to_int().unsafe_to_char())
    index = index + 3
  }
  let remaining = input.length() - index
  if remaining == 1 {
    let a = input[index].to_int()
    output.write_char(alphabet[a >> 2].to_int().unsafe_to_char())
    output.write_char(alphabet[(a & 3) << 4].to_int().unsafe_to_char())
    output.write_string("==")
  } else if remaining == 2 {
    let a = input[index].to_int()
    let b = input[index + 1].to_int()
    output.write_char(alphabet[a >> 2].to_int().unsafe_to_char())
    output.write_char(
      alphabet[((a & 3) << 4) | (b >> 4)].to_int().unsafe_to_char(),
    )
    output.write_char(alphabet[(b & 15) << 2].to_int().unsafe_to_char())
    output.write_char('=')
  }
  output.to_string()
}

///|
/// Decode one strict RFC 4648 value from Upload-Metadata.
///
/// Whitespace, URL-safe symbols, missing padding and non-zero unused bits are
/// rejected. Strictness is intentional: adapters receive a single portable
/// interpretation rather than framework-dependent cleanup behavior.
pub fn decode_base64(
  input : String,
  max_output : Int,
) -> Result[Bytes, TusError] {
  if max_output < 0 {
    return Err(invalid_base64("decoded byte limit cannot be negative", input))
  }
  if input.length() == 0 {
    return Ok(b"")
  }
  if input.length() % 4 != 0 {
    return Err(
      invalid_base64(
        "encoded value must contain complete four-symbol quanta", input,
      ),
    )
  }
  let output : Array[Byte] = []
  let symbols : Array[Int] = []
  for character in input {
    match base64_symbol(character) {
      Some(value) => symbols.push(value)
      None =>
        return Err(
          invalid_base64(
            "encoded value contains a symbol outside the basic alphabet", input,
          ),
        )
    }
  }
  let mut index = 0
  while index < symbols.length() {
    let a = symbols[index]
    let b = symbols[index + 1]
    let c = symbols[index + 2]
    let d = symbols[index + 3]
    let last = index + 4 == symbols.length()
    if a < 0 || b < 0 {
      return Err(
        invalid_base64("padding cannot occur in the first two positions", input),
      )
    }
    if c < 0 {
      if !last || d >= 0 {
        return Err(
          invalid_base64(
            "double padding is only valid in the final quantum", input,
          ),
        )
      }
      if (b & 15) != 0 {
        return Err(
          invalid_base64("one-byte tail has non-zero unused bits", input),
        )
      }
      if output.length() + 1 > max_output {
        return Err(base64_too_large(max_output, output.length() + 1))
      }
      output.push(((a << 2) | (b >> 4)).to_byte())
    } else if d < 0 {
      if !last {
        return Err(
          invalid_base64("padding is only valid in the final quantum", input),
        )
      }
      if (c & 3) != 0 {
        return Err(
          invalid_base64("two-byte tail has non-zero unused bits", input),
        )
      }
      if output.length() + 2 > max_output {
        return Err(base64_too_large(max_output, output.length() + 2))
      }
      output.push(((a << 2) | (b >> 4)).to_byte())
      output.push((((b & 15) << 4) | (c >> 2)).to_byte())
    } else {
      if output.length() + 3 > max_output {
        return Err(base64_too_large(max_output, output.length() + 3))
      }
      output.push(((a << 2) | (b >> 4)).to_byte())
      output.push((((b & 15) << 4) | (c >> 2)).to_byte())
      output.push((((c & 3) << 6) | d).to_byte())
    }
    index = index + 4
  }
  Ok(Bytes::from_array(output))
}

///|
fn base64_symbol(character : Char) -> Int? {
  if character >= 'A' && character <= 'Z' {
    Some(character.to_int() - 'A'.to_int())
  } else if character >= 'a' && character <= 'z' {
    Some(character.to_int() - 'a'.to_int() + 26)
  } else if character >= '0' && character <= '9' {
    Some(character.to_int() - '0'.to_int() + 52)
  } else if character == '+' {
    Some(62)
  } else if character == '/' {
    Some(63)
  } else if character == '=' {
    Some(-1)
  } else {
    None
  }
}

///|
fn invalid_base64(message : String, actual : String) -> TusError {
  tus_error(
    InvalidBase64,
    "TUS_METADATA_BASE64_INVALID",
    message,
    header_name=Some("upload-metadata"),
    actual=Some(actual),
  )
}

///|
fn base64_too_large(limit : Int, actual : Int) -> TusError {
  tus_error(
    MetadataTooLarge,
    "TUS_METADATA_VALUE_TOO_LARGE",
    "decoded metadata value exceeds the configured byte budget",
    status=413,
    header_name=Some("upload-metadata"),
    expected=Some(limit.to_string()),
    actual=Some(actual.to_string()),
  )
}