// Encoding and decoding codecs: Base64, URL, Unicode, Hex

///|
const HEX_UPPER = "0123456789ABCDEF"

///|
const B64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

///|
fn b64_index(c : Char) -> Int {
  let i = c.to_int()
  if i >= 'A'.to_int() && i <= 'Z'.to_int() {
    i - 'A'.to_int()
  } else if i >= 'a'.to_int() && i <= 'z'.to_int() {
    i - 'a'.to_int() + 26
  } else if i >= '0'.to_int() && i <= '9'.to_int() {
    i - '0'.to_int() + 52
  } else if c == '+' {
    62
  } else if c == '/' {
    63
  } else {
    -1
  }
}

///|
/// Check if byte value is URL-unreserved (RFC 3986).
fn is_url_unreserved(b : Int) -> Bool {
  (b >= 0x41 && b <= 0x5A) || // A-Z
  (b >= 0x61 && b <= 0x7A) || // a-z
  (b >= 0x30 && b <= 0x39) || // 0-9
  b == 0x2D ||
  b == 0x5F ||
  b == 0x2E ||
  b == 0x7E // - _ . ~
}

// ========== Base64 Encoding ==========

///|
/// Encode bytes to Base64 string.
pub fn base64_encode(data : Bytes) -> String {
  let len = data.length()
  let sb = StringBuilder()
  let mut i = 0
  while i < len {
    let b0 = data[i].to_int()
    let b1 = if i + 1 < len { data[i + 1].to_int() } else { 0 }
    let b2 = if i + 2 < len { data[i + 2].to_int() } else { 0 }
    let n = (b0 << 16) | (b1 << 8) | b2
    sb.write_char(B64_CHARS.get_char((n >> 18) & 63).unwrap())
    sb.write_char(B64_CHARS.get_char((n >> 12) & 63).unwrap())
    sb.write_char(
      if i + 1 < len {
        B64_CHARS.get_char((n >> 6) & 63).unwrap()
      } else {
        '='
      },
    )
    sb.write_char(
      if i + 2 < len {
        B64_CHARS.get_char(n & 63).unwrap()
      } else {
        '='
      },
    )
    i = i + 3
  }
  sb.to_string()
}

///|
/// Decode Base64 string to bytes.
pub fn base64_decode(text : String) -> Bytes? {
  let len = text.length()
  if len % 4 != 0 {
    return None
  }
  let bytes : Array[Byte] = []
  let mut i = 0
  while i < len {
    let v0 = b64_index(text.get_char(i).unwrap())
    let v1 = b64_index(text.get_char(i + 1).unwrap())
    let v2 = if text.get_char(i + 2).unwrap() == '=' {
      0
    } else {
      b64_index(text.get_char(i + 2).unwrap())
    }
    let v3 = if text.get_char(i + 3).unwrap() == '=' {
      0
    } else {
      b64_index(text.get_char(i + 3).unwrap())
    }
    if v0 < 0 || v1 < 0 || v2 < 0 || v3 < 0 {
      return None
    }
    let n = (v0 << 18) | (v1 << 12) | (v2 << 6) | v3
    bytes.push(((n >> 16) & 0xFF).to_byte())
    if text.get_char(i + 2).unwrap() != '=' {
      bytes.push(((n >> 8) & 0xFF).to_byte())
    }
    if text.get_char(i + 3).unwrap() != '=' {
      bytes.push((n & 0xFF).to_byte())
    }
    i = i + 4
  }
  Some(Bytes::makei(bytes.length(), k => bytes[k]))
}

// ========== URL Encoding ==========

///|
/// URL percent-encode bytes.
/// Unreserved characters (A-Z, a-z, 0-9, -, _, ., ~) pass through,
/// all others become %XX.
pub fn url_encode(data : Bytes) -> String {
  let sb = StringBuilder()
  for i = 0; i < data.length(); i = i + 1 {
    let b = data[i].to_int()
    if is_url_unreserved(b) {
      sb.write_char(b.to_char().unwrap())
    } else {
      sb.write_char('%')
      sb.write_char(HEX_UPPER.get_char((b >> 4) & 0xF).unwrap())
      sb.write_char(HEX_UPPER.get_char(b & 0xF).unwrap())
    }
  }
  sb.to_string()
}

///|
/// URL percent-decode a string to bytes.
/// %XX is decoded, + is decoded as space.
/// Returns None on invalid input.
pub fn url_decode(text : String) -> Bytes? {
  let out : Array[Byte] = []
  let len = text.length()
  let mut i = 0
  while i < len {
    let ch = text.get_char(i).unwrap()
    if ch == '%' {
      if i + 2 >= len {
        return None
      }
      let h = text.get_char(i + 1).unwrap()
      let l = text.get_char(i + 2).unwrap()
      if !is_hex_digit(h) || !is_hex_digit(l) {
        return None
      }
      let val = (hex_char_to_int(h) << 4) | hex_char_to_int(l)
      out.push(val.to_byte())
      i = i + 3
    } else if ch == '+' {
      out.push(b' ')
      i = i + 1
    } else {
      let code = ch.to_int()
      if code > 127 {
        return None
      }
      out.push(code.to_byte())
      i = i + 1
    }
  }
  Some(Bytes::makei(out.length(), fn(k) { out[k] }))
}

// ========== Unicode Encoding ==========

///|
/// Unicode encode: each byte → \u00XX notation.
pub fn unicode_encode(data : Bytes) -> String {
  let sb = StringBuilder()
  for i = 0; i < data.length(); i = i + 1 {
    let b = data[i].to_int()
    sb.write_string("\\u00")
    sb.write_char(HEX_UPPER.get_char((b >> 4) & 0xF).unwrap())
    sb.write_char(HEX_UPPER.get_char(b & 0xF).unwrap())
  }
  sb.to_string()
}

///|
/// Unicode decode: parse \uXXXX sequences to bytes.
/// Plain ASCII characters are kept as-is.
/// Codepoints <= 0x7F → 1 byte, <= 0x7FF → 2 bytes, else → 3 bytes (UTF-8).
/// Returns None on invalid input.
pub fn unicode_decode(text : String) -> Bytes? {
  let out : Array[Byte] = []
  let len = text.length()
  let mut i = 0
  while i < len {
    let ch = text.get_char(i).unwrap()
    if ch == '\\' && i + 1 < len {
      let next = text.get_char(i + 1).unwrap()
      if next == 'u' && i + 5 < len {
        let h3 = text.get_char(i + 2).unwrap()
        let h2 = text.get_char(i + 3).unwrap()
        let h1 = text.get_char(i + 4).unwrap()
        let h0 = text.get_char(i + 5).unwrap()
        if !is_hex_digit(h3) ||
          !is_hex_digit(h2) ||
          !is_hex_digit(h1) ||
          !is_hex_digit(h0) {
          return None
        }
        let val = (hex_char_to_int(h3) << 12) |
          (hex_char_to_int(h2) << 8) |
          (hex_char_to_int(h1) << 4) |
          hex_char_to_int(h0)
        if val <= 0x7F {
          out.push(val.to_byte())
        } else if val <= 0x7FF {
          out.push((0xC0 | (val >> 6)).to_byte())
          out.push((0x80 | (val & 0x3F)).to_byte())
        } else {
          out.push((0xE0 | (val >> 12)).to_byte())
          out.push((0x80 | ((val >> 6) & 0x3F)).to_byte())
          out.push((0x80 | (val & 0x3F)).to_byte())
        }
        i = i + 6
      } else {
        return None
      }
    } else {
      let code = ch.to_int()
      if code > 127 {
        return None
      }
      out.push(code.to_byte())
      i = i + 1
    }
  }
  Some(Bytes::makei(out.length(), fn(k) { out[k] }))
}

// ========== Hex Encoding ==========

///|
/// Hex encode: bytes → space-separated hex values.
/// Example: b"\x48\x65" → "48 65"
pub fn hex_encode(data : Bytes) -> String {
  let sb = StringBuilder()
  for i = 0; i < data.length(); i = i + 1 {
    if i > 0 {
      sb.write_char(' ')
    }
    sb.write_char(HEX_UPPER.get_char((data[i].to_int() >> 4) & 0xF).unwrap())
    sb.write_char(HEX_UPPER.get_char(data[i].to_int() & 0xF).unwrap())
  }
  sb.to_string()
}

///|
/// Hex decode: space-separated hex values → bytes.
/// Example: "48 65 6C" → b"\x48\x65\x6C"
pub fn hex_decode(text : String) -> Bytes? {
  parse_hex_string(text)
}