// SIMD implementation of PNG filter Up — wasm target only.
//
// Filter Up: buf[i] = (row[i] - prev[i]) mod 256
//
// `i8x16.sub` does exactly this on 16 bytes at a time (modular byte
// subtraction). Inline-WAT can take `Bytes` as input on wasm (not on
// wasm-gc, which receives a GC ref — that target falls through to scalar
// via png_filter_scalar.mbt).

///|
/// Decode-side Up reconstruction: buf[i] = (row[i] + prev[i]) mod 256.
/// Pure `i8x16.add` (modular byte add) on 16-byte chunks.
fn reconstruct_filter_up_impl(
  row : Bytes,
  prev : Bytes,
  buf : FixedArray[Byte],
) -> Unit {
  let len = row.length()
  if len == 0 {
    return
  }
  reconstruct_filter_up_v128(row, prev, buf, len)
}

///|
/// Inline-WAT for filter Up reconstruction. Same shape as the encoder
/// `apply_filter_up_v128`, but using `i8x16.add` (decode) instead of sub.
///
/// Locals: 4 = vec_end, 5 = i.
#borrow(row, prev, buf)
fn reconstruct_filter_up_v128(
  row : Bytes,
  prev : Bytes,
  buf : FixedArray[Byte],
  len : Int,
) -> Unit =
  #|(func (param i32) (param i32) (param i32) (param i32) (local i32) (local i32) local.get 3 i32.const 4 i32.shr_s i32.const 4 i32.shl local.set 4 block loop local.get 5 local.get 4 i32.ge_s br_if 1 local.get 2 local.get 5 i32.add local.get 0 local.get 5 i32.add v128.load local.get 1 local.get 5 i32.add v128.load i8x16.add v128.store local.get 5 i32.const 16 i32.add local.set 5 br 0 end end block loop local.get 5 local.get 3 i32.ge_s br_if 1 local.get 2 local.get 5 i32.add local.get 0 local.get 5 i32.add i32.load8_u local.get 1 local.get 5 i32.add i32.load8_u i32.add i32.store8 local.get 5 i32.const 1 i32.add local.set 5 br 0 end end)

///|
fn apply_filter_up_impl(row : Bytes, prev : Bytes) -> FixedArray[Byte] {
  let len = row.length()
  let buf = FixedArray::make(len, b'\x00')
  if len == 0 {
    return buf
  }
  apply_filter_up_v128(row, prev, buf, len)
  buf
}

///|
/// Inline-WAT: walks `row` and `prev` 16 bytes at a time via `v128.load`
/// + `i8x16.sub` + `v128.store` into `buf`. Tail bytes scalar.
///
/// Locals: 4 = vec_end (= len & ~15), 5 = i.
#borrow(row, prev, buf)
fn apply_filter_up_v128(
  row : Bytes,
  prev : Bytes,
  buf : FixedArray[Byte],
  len : Int,
) -> Unit =
  #|(func (param i32) (param i32) (param i32) (param i32) (local i32) (local i32) local.get 3 i32.const 4 i32.shr_s i32.const 4 i32.shl local.set 4 block loop local.get 5 local.get 4 i32.ge_s br_if 1 local.get 2 local.get 5 i32.add local.get 0 local.get 5 i32.add v128.load local.get 1 local.get 5 i32.add v128.load i8x16.sub v128.store local.get 5 i32.const 16 i32.add local.set 5 br 0 end end block loop local.get 5 local.get 3 i32.ge_s br_if 1 local.get 2 local.get 5 i32.add local.get 0 local.get 5 i32.add i32.load8_u local.get 1 local.get 5 i32.add i32.load8_u i32.sub i32.store8 local.get 5 i32.const 1 i32.add local.set 5 br 0 end end)

///|
/// Filter Sub: buf[i] = row[i] - (i >= bpp ? row[i-bpp] : 0).
///
/// Head (i < bpp) is scalar; then SIMD as long as `i + 16 <= len`, then
/// scalar tail. The SIMD load `row + i - bpp` is safe because i >= bpp.
fn apply_filter_sub_impl(row : Bytes, bpp : Int) -> FixedArray[Byte] {
  let len = row.length()
  let buf = FixedArray::make(len, b'\x00')
  if len == 0 {
    return buf
  }
  // Scalar head: i = 0..bpp-1, buf[i] = row[i] (a = 0).
  let head_end = if bpp > len { len } else { bpp }
  for i in 0.. bpp {
    apply_filter_sub_v128(row, buf, bpp, len)
  }
  buf
}

///|
/// Inline-WAT: SIMD body + scalar tail for filter Sub.
/// Params: 0 = row, 1 = buf, 2 = bpp, 3 = len.
/// Locals: 4 = vec_end (= ((len - bpp) & ~15) + bpp), 5 = i.
#borrow(row, buf)
fn apply_filter_sub_v128(
  row : Bytes,
  buf : FixedArray[Byte],
  bpp : Int,
  len : Int,
) -> Unit =
  #|(func (param i32) (param i32) (param i32) (param i32) (local i32) (local i32) local.get 3 local.get 2 i32.sub i32.const 4 i32.shr_s i32.const 4 i32.shl local.get 2 i32.add local.set 4 local.get 2 local.set 5 block loop local.get 5 local.get 4 i32.ge_s br_if 1 local.get 1 local.get 5 i32.add local.get 0 local.get 5 i32.add v128.load local.get 0 local.get 5 i32.add local.get 2 i32.sub v128.load i8x16.sub v128.store local.get 5 i32.const 16 i32.add local.set 5 br 0 end end block loop local.get 5 local.get 3 i32.ge_s br_if 1 local.get 1 local.get 5 i32.add local.get 0 local.get 5 i32.add i32.load8_u local.get 0 local.get 5 i32.add local.get 2 i32.sub i32.load8_u i32.sub i32.store8 local.get 5 i32.const 1 i32.add local.set 5 br 0 end end)