///|
/// UTF-8 encode a string to bytes (replaces the deprecated `String::to_bytes`).
pub fn string_to_bytes(s : String) -> Bytes {
  @utf8.encode(s)
}

///|
/// Decode bytes back to a string, replacing any malformed sequences lossily.
/// Only use for content that is expected to be text.
pub fn bytes_to_string(b : Bytes) -> String {
  @utf8.decode_lossy(b)
}

///|
/// The hex character for a nibble (lowercase).
fn hex_digit(v : Int) -> Char {
  if v < 10 {
    Int::unsafe_to_char(v + 0x30)
  } else {
    Int::unsafe_to_char(v - 10 + 0x61)
  }
}

///|
/// Encode bytes to a lowercase hex string.
pub fn bytes_to_hex(b : Bytes) -> String {
  let out = FixedArray::make(b.length() * 2, b'\x00')
  for i = 0; i < b.length(); i = i + 1 {
    let v = b[i].to_int()
    out[i * 2] = hex_digit((v >> 4) & 0xf).to_int().to_byte()
    out[i * 2 + 1] = hex_digit(v & 0xf).to_int().to_byte()
  }
  fixedarray_to_string(out, b.length() * 2)
}

///|
/// Find the byte index of `sub` in `s`, or `-1` when absent.
pub fn index_of(s : String, sub : String) -> Int {
  match s.find(sub) {
    Some(i) => i
    None => -1
  }
}

///|
/// Convert the first `len` bytes of a FixedArray into a string.
pub fn fixedarray_to_string(fa : FixedArray[Byte], len : Int) -> String {
  bytes_to_string(Bytes::from_iter(fa.iter())[0:len].to_owned())
}

///|
/// Copy `arr[start:]` into a new array.
pub fn[T] slice_array(arr : Array[T], start : Int) -> Array[T] {
  let out = []
  for i = start; i < arr.length(); i = i + 1 {
    out.push(arr[i])
  }
  out
}

///|
/// The uppercase hex character for a nibble.
pub fn to_hex_upper(v : Int) -> Byte {
  if v < 10 {
    (v + 0x30).to_byte()
  } else {
    (v - 10 + 0x41).to_byte()
  }
}

///|
/// Decode a hex string to bytes. Raises on odd length or non-hex input.
pub fn hex_to_bytes(s : String) -> Bytes raise MailFailure {
  if s.length() % 2 != 0 {
    raise MailFailure::encode("hex string has odd length")
  }
  let src = string_to_bytes(s)
  let out = FixedArray::make(src.length() / 2, b'\x00')
  for i = 0; i < src.length(); i = i + 2 {
    let hi = hex_val(src[i])
    let lo = hex_val(src[i + 1])
    out[i / 2] = ((hi << 4) | lo).to_byte()
  }
  Bytes::from_iter(out.iter())
}

///|
fn hex_val(b : Byte) -> Int raise MailFailure {
  let c = b.to_int()
  if c >= 0x30 && c <= 0x39 {
    return c - 0x30
  }
  if c >= 0x61 && c <= 0x66 {
    return c - 0x61 + 10
  }
  if c >= 0x41 && c <= 0x46 {
    return c - 0x41 + 10
  }
  raise MailFailure::encode("invalid hex character")
}