///|
let k : ReadOnlyArray[UInt] = [
  0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4,
  0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe,
  0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f,
  0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
  0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc,
  0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
  0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116,
  0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
  0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7,
  0xc67178f2,
]

///|
fn padding(data : Bytes) -> Array[Byte] {
  let length = data.length()
  let n = loop 1 {
    n => if length * 8 < 1 << (8 * n) { n } else { continue n + 1 }
  }
  let total_length = if length % 64 < 56 {
    (length / 64 + 1) * 64
  } else {
    (length / 64 + 2) * 64
  }
  let padded = Array::make(total_length, (0).to_byte())
  for i = 0; i < length; i = i + 1 {
    padded[i] = data[i]
  }
  padded[length] = (0x80).to_byte()
  let bit_length = length * 8
  for i = 0; i < n; i = i + 1 {
    let shift = (n - 1 - i) * 8
    let value = (bit_length >> shift) & 0xff
    padded[total_length - n + i] = value.to_byte()
  }
  padded
}

///|
fn bytes_to_u32_be(data : Array[Byte], offset : Int) -> UInt {
  let b0 = data[offset].to_uint()
  let b1 = data[offset + 1].to_uint()
  let b2 = data[offset + 2].to_uint()
  let b3 = data[offset + 3].to_uint()
  (b0 << 24) | (b1 << 16) | (b2 << 8) | b3
}

///|
fn preprocess(input : Bytes) -> Array[Array[UInt]] {
  let padded = padding(input)
  let num_blocks = padded.length() / 64
  let blocks : Array[Array[UInt]] = Array::new()
  for block_index = 0; block_index < num_blocks; block_index = block_index + 1 {
    let base = block_index * 64
    let words = Array::make(16, UInt::default())
    for t = 0; t < 16; t = t + 1 {
      words[t] = bytes_to_u32_be(padded, base + t * 4)
    }
    blocks.push(words)
  }
  blocks
}

///|
/// Rotate right by `shift` bits on a 32-bit word.
pub fn rtor(data : UInt, shift : Int) -> UInt {
  (data >> shift) | (data << (32 - shift))
}

///|
/// SHA-256 lower sigma0 function.
pub fn lower_sigma0(word : UInt) -> UInt {
  rtor(word, 7) ^ rtor(word, 18) ^ (word >> 3)
}

///|
/// SHA-256 lower sigma1 function.
pub fn lower_sigma1(word : UInt) -> UInt {
  rtor(word, 17) ^ rtor(word, 19) ^ (word >> 10)
}

///|
/// SHA-256 upper sigma0 function.
pub fn upper_sigma0(word : UInt) -> UInt {
  rtor(word, 2) ^ rtor(word, 13) ^ rtor(word, 22)
}

///|
/// SHA-256 upper sigma1 function.
pub fn upper_sigma1(word : UInt) -> UInt {
  rtor(word, 6) ^ rtor(word, 11) ^ rtor(word, 25)
}

///|
/// SHA-256 choose function.
pub fn ch(x : UInt, y : UInt, z : UInt) -> UInt {
  (x & y) ^ (x.lnot() & z)
}

///|
/// SHA-256 majority function.
pub fn maj(x : UInt, y : UInt, z : UInt) -> UInt {
  (x & y) ^ (x & z) ^ (y & z)
}

///|
fn write_u32_be(out : Array[Byte], offset : Int, word : UInt) -> Unit {
  out[offset] = ((word >> 24) & 0xff).to_byte()
  out[offset + 1] = ((word >> 16) & 0xff).to_byte()
  out[offset + 2] = ((word >> 8) & 0xff).to_byte()
  out[offset + 3] = (word & 0xff).to_byte()
}

///|
fn bytes_to_hex(bytes : Bytes) -> String {
  for i = 0, acc = ""
      i < bytes.length()
      i = i + 1, acc = acc + bytes[i].to_hex() {

  } else {
    acc
  }
}

///|
fn compute_hash(blocks : Array[Array[UInt]]) -> Bytes {
  let ws : Array[UInt] = Array::make(64, UInt::default())
  let h : Array[UInt] = [
    0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab,
    0x5be0cd19,
  ]
  for block_index = 0
      block_index < blocks.length()
      block_index = block_index + 1 {
    let block = blocks[block_index]
    for t = 0; t < 16; t = t + 1 {
      ws[t] = block[t]
    }
    for t = 16; t < 64; t = t + 1 {
      ws[t] = lower_sigma1(ws[t - 2]) +
        ws[t - 7] +
        lower_sigma0(ws[t - 15]) +
        ws[t - 16]
    }
    let (a, b, c, d, e, f, g, h1) = loop
      (0, h[0], h[1], h[2], h[3], h[4], h[5], h[6], h[7]) {
      (64, a, b, c, d, e, f, g, h1) => (a, b, c, d, e, f, g, h1)
      (i, a, b, c, d, e, f, g, h1) => {
        let t1 = h1 + upper_sigma1(e) + ch(e, f, g) + k[i] + ws[i]
        let t2 = upper_sigma0(a) + maj(a, b, c)
        continue (i + 1, t1 + t2, a, b, c, d + t1, e, f, g)
      }
    }
    h[0] = h[0] + a
    h[1] = h[1] + b
    h[2] = h[2] + c
    h[3] = h[3] + d
    h[4] = h[4] + e
    h[5] = h[5] + f
    h[6] = h[6] + g
    h[7] = h[7] + h1
  }
  let out = Array::make(32, (0).to_byte())
  for i = 0; i < 8; i = i + 1 {
    write_u32_be(out, i * 4, h[i])
  }
  Bytes::from_array(out)
}

///|
/// Compute the SHA-256 digest as raw bytes (32 bytes).
pub fn sha256(input : Bytes) -> Bytes {
  compute_hash(preprocess(input))
}

///|
/// Compute the SHA-256 digest as a hex string (64 chars).
pub fn sha256_hexdigest(input : Bytes) -> String {
  bytes_to_hex(sha256(input))
}