///|
fn compress_u32(state : FixedArray[UInt], block : FixedArray[UInt]) -> Unit {
  let mut a : UInt = state[0]
  let mut b : UInt = state[1]
  let mut c : UInt = state[2]
  let mut d : UInt = state[3]
  let mut e : UInt = state[4]
  let mut f : UInt = state[5]
  let mut g : UInt = state[6]
  let mut h : UInt = state[7]
  let k = get_k32() // Get constants array once
  for i in 0..<64 {
    let w : UInt = if i < 16 {
      block[i]
    } else {
      let w15 = block[(i - 15) % 16]
      let s0 = rotate_right_u32(w15, 7) ^ rotate_right_u32(w15, 18) ^ (w15 >> 3)
      let w2 = block[(i - 2) % 16]
      let s1 = rotate_right_u32(w2, 17) ^ rotate_right_u32(w2, 19) ^ (w2 >> 10)
      let new_w = block[(i - 16) % 16] + s0 + block[(i - 7) % 16] + s1
      block[i % 16] = new_w
      new_w
    }
    let s1 = rotate_right_u32(e, 6) ^
      rotate_right_u32(e, 11) ^
      rotate_right_u32(e, 25)
    let ch = (e & f) ^ ((e ^ 0xFFFFFFFF) & g)
    let temp1 = s1 + ch + k[i] + w + h
    let s0 = rotate_right_u32(a, 2) ^
      rotate_right_u32(a, 13) ^
      rotate_right_u32(a, 22)
    let maj = (a & b) ^ (a & c) ^ (b & c)
    let temp2 = s0 + maj
    h = g
    g = f
    f = e
    e = d + temp1
    d = c
    c = b
    b = a
    a = temp1 + temp2
  }
  state[0] = state[0] + a
  state[1] = state[1] + b
  state[2] = state[2] + c
  state[3] = state[3] + d
  state[4] = state[4] + e
  state[5] = state[5] + f
  state[6] = state[6] + g
  state[7] = state[7] + h
}

///|
fn compress256(
  state : FixedArray[UInt],
  blocks : FixedArray[FixedArray[UInt]],
) -> Unit {
  for block in blocks {
    compress_u32(state, block)
  }
}

///|
pub struct Sha256 {
  state : FixedArray[UInt]
  buffer : FixedArray[UInt]
  mut buffer_len : Int
  mut total_len : UInt64
}

///|
pub fn Sha256::new() -> Sha256 {
  // Create a fresh copy of the initial state
  let initial_state = sha256_state()
  let state = FixedArray::make(8, 0U)
  for i in 0..<8 {
    state[i] = initial_state[i]
  }
  return Sha256::{
    state,
    buffer: FixedArray::make(64, 0U),
    buffer_len: 0,
    total_len: 0UL,
  }
}

///|
pub fn Sha256::update(self : Sha256, input : Array[UInt]) -> Unit {
  let mut input_offset = 0
  let input_len = input.length()
  self.total_len = self.total_len + input_len.to_uint64()

  // If we have buffered data, try to fill the buffer and process it
  if self.buffer_len > 0 {
    let space_left = 64 - self.buffer_len
    let to_copy = if input_len < space_left { input_len } else { space_left }
    for i in 0.. FixedArray[UInt] {
  // Save the message length in bits
  let bit_len = self.total_len * 8UL

  // Append 0x80 byte (1 bit followed by zeros)
  self.buffer[self.buffer_len] = 0x80U
  self.buffer_len = self.buffer_len + 1

  // If there's not enough room for length (8 bytes), process this block and pad next
  if self.buffer_len > 56 {
    // Fill rest with zeros
    while self.buffer_len < 64 {
      self.buffer[self.buffer_len] = 0U
      self.buffer_len = self.buffer_len + 1
    }

    // Process this block
    let block = FixedArray::make(16, 0U)
    for i in 0..<16 {
      let idx = i * 4
      block[i] = (self.buffer[idx] << 24) |
        (self.buffer[idx + 1] << 16) |
        (self.buffer[idx + 2] << 8) |
        self.buffer[idx + 3]
    }
    let blocks = FixedArray::make(1, block)
    compress256(self.state, blocks)
    self.buffer_len = 0
  }

  // Pad with zeros until we reach 56 bytes (leaving 8 bytes for length)
  while self.buffer_len < 56 {
    self.buffer[self.buffer_len] = 0U
    self.buffer_len = self.buffer_len + 1
  }

  // Append length in bits as 64-bit big-endian
  self.buffer[56] = ((bit_len >> 56) & 0xFFUL).to_uint()
  self.buffer[57] = ((bit_len >> 48) & 0xFFUL).to_uint()
  self.buffer[58] = ((bit_len >> 40) & 0xFFUL).to_uint()
  self.buffer[59] = ((bit_len >> 32) & 0xFFUL).to_uint()
  self.buffer[60] = ((bit_len >> 24) & 0xFFUL).to_uint()
  self.buffer[61] = ((bit_len >> 16) & 0xFFUL).to_uint()
  self.buffer[62] = ((bit_len >> 8) & 0xFFUL).to_uint()
  self.buffer[63] = (bit_len & 0xFFUL).to_uint()

  // Process final block
  let block = FixedArray::make(16, 0U)
  for i in 0..<16 {
    let idx = i * 4
    block[i] = (self.buffer[idx] << 24) |
      (self.buffer[idx + 1] << 16) |
      (self.buffer[idx + 2] << 8) |
      self.buffer[idx + 3]
  }
  let blocks = FixedArray::make(1, block)
  compress256(self.state, blocks)

  // Convert state from UInt32 to bytes
  let result = FixedArray::make(32, 0U)
  for i in 0..<8 {
    let v = self.state[i]
    result[i * 4] = (v >> 24) & 0xFFU
    result[i * 4 + 1] = (v >> 16) & 0xFFU
    result[i * 4 + 2] = (v >> 8) & 0xFFU
    result[i * 4 + 3] = v & 0xFFU
  }
  return result
}

///|
pub fn Sha256::digest(input : Array[UInt]) -> FixedArray[UInt] {
  let sha256 = Sha256::new()
  sha256.update(input)
  return sha256.finalize()
}

// Additional methods for different input types

///|
pub fn Sha256::digest_bytes(input : Bytes) -> FixedArray[UInt] {
  let sha256 = Sha256::new()
  let arr : Array[UInt] = []
  // Filter out zero bytes to handle UTF-16LE-like encodings from some to_bytes() implementations
  for i in 0.. FixedArray[UInt] {
  let sha256 = Sha256::new()
  let bytes = @utf8.encode(input)
  let arr : Array[UInt] = []
  for i in 0..