// Copyright 2026 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

///|
fn load_u16_le(bytes : FixedArray[Byte], offset : Int) -> UInt16 {
  bytes.unsafe_get(offset).to_uint16() |
  (bytes.unsafe_get(offset + 1).to_uint16() << 8)
}

///|
fn load_u16x4_le(str : String, offset : Int) -> UInt64 {
  for i in 0..<4; word = UInt64(0) {
    continue word | (str.unsafe_get(offset + i).to_uint64() << (16 * i))
  } nobreak {
    word
  }
}

///|
fn load_u32_le(bytes : FixedArray[Byte], offset : Int) -> UInt {
  bytes.unsafe_get(offset).to_uint() |
  (bytes.unsafe_get(offset + 1).to_uint() << 8) |
  (bytes.unsafe_get(offset + 2).to_uint() << 16) |
  (bytes.unsafe_get(offset + 3).to_uint() << 24)
}

///|
fn load_u64_le(bytes : FixedArray[Byte], offset : Int) -> UInt64 {
  bytes.unsafe_get(offset).to_uint64() |
  (bytes.unsafe_get(offset + 1).to_uint64() << 8) |
  (bytes.unsafe_get(offset + 2).to_uint64() << 16) |
  (bytes.unsafe_get(offset + 3).to_uint64() << 24) |
  (bytes.unsafe_get(offset + 4).to_uint64() << 32) |
  (bytes.unsafe_get(offset + 5).to_uint64() << 40) |
  (bytes.unsafe_get(offset + 6).to_uint64() << 48) |
  (bytes.unsafe_get(offset + 7).to_uint64() << 56)
}

///|
fn store_u16_le(bytes : FixedArray[Byte], offset : Int, value : UInt16) -> Unit {
  bytes.unsafe_set(offset, value.to_byte())
  bytes.unsafe_set(offset + 1, (value >> 8).to_byte())
}

///|
fn store_u32_le(bytes : FixedArray[Byte], offset : Int, value : UInt) -> Unit {
  bytes.unsafe_set(offset, value.to_byte())
  bytes.unsafe_set(offset + 1, (value >> 8).to_byte())
  bytes.unsafe_set(offset + 2, (value >> 16).to_byte())
  bytes.unsafe_set(offset + 3, (value >> 24).to_byte())
}

///|
fn store_u64_le(bytes : FixedArray[Byte], offset : Int, value : UInt64) -> Unit {
  bytes.unsafe_set(offset, value.to_byte())
  bytes.unsafe_set(offset + 1, (value >> 8).to_byte())
  bytes.unsafe_set(offset + 2, (value >> 16).to_byte())
  bytes.unsafe_set(offset + 3, (value >> 24).to_byte())
  bytes.unsafe_set(offset + 4, (value >> 32).to_byte())
  bytes.unsafe_set(offset + 5, (value >> 40).to_byte())
  bytes.unsafe_set(offset + 6, (value >> 48).to_byte())
  bytes.unsafe_set(offset + 7, (value >> 56).to_byte())
}

///|
/// Selects bytes of `value` indexed by `indices`; indices >= 16 yield zero.
#intrinsic("%v128.i8x16_swizzle")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i8x16_swizzle(value : V128, indices : V128) -> V128 {
  pack_u8x16_by(i => {
    let index = u8_lane(indices, i).to_int()
    if index < 16 {
      u8_lane(value, index)
    } else {
      0
    }
  })
}

///|
/// Relaxed byte swizzle; behavior for out-of-range indices is implementation-defined.
#intrinsic("%v128.i8x16_relaxed_swizzle")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i8x16_relaxed_swizzle(value : V128, indices : V128) -> V128 {
  i8x16_swizzle(value, indices)
}

///|
fn shuffle_lane(value : V128, other : V128, index : Int) -> Byte {
  if index < 16 {
    u8_lane(value, index)
  } else {
    u8_lane(other, index - 16)
  }
}

///|
/// Selects 16 bytes from the concatenation of the two vectors by constant lane indices.
#intrinsic("%v128.i8x16_shuffle")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i8x16_shuffle(
  value : V128,
  other : V128,
  i0 : Int,
  i1 : Int,
  i2 : Int,
  i3 : Int,
  i4 : Int,
  i5 : Int,
  i6 : Int,
  i7 : Int,
  i8 : Int,
  i9 : Int,
  i10 : Int,
  i11 : Int,
  i12 : Int,
  i13 : Int,
  i14 : Int,
  i15 : Int,
) -> V128 {
  pack_u8x16_by(fn(i) {
    match i {
      0 => shuffle_lane(value, other, i0)
      1 => shuffle_lane(value, other, i1)
      2 => shuffle_lane(value, other, i2)
      3 => shuffle_lane(value, other, i3)
      4 => shuffle_lane(value, other, i4)
      5 => shuffle_lane(value, other, i5)
      6 => shuffle_lane(value, other, i6)
      7 => shuffle_lane(value, other, i7)
      8 => shuffle_lane(value, other, i8)
      9 => shuffle_lane(value, other, i9)
      10 => shuffle_lane(value, other, i10)
      11 => shuffle_lane(value, other, i11)
      12 => shuffle_lane(value, other, i12)
      13 => shuffle_lane(value, other, i13)
      14 => shuffle_lane(value, other, i14)
      _ => shuffle_lane(value, other, i15)
    }
  })
}

///|
/// Loads a 128-bit vector from `bytes` at `offset` (little-endian).
#intrinsic("%v128.v128_load")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load(bytes : FixedArray[Byte], offset : Int) -> V128 {
  make(load_u64_le(bytes, offset), load_u64_le(bytes, offset + 8))
}

///|
/// Loads eight consecutive UTF-16 code units of `str` starting at code unit
/// `offset` into the eight 16-bit lanes of the vector. The caller must ensure
/// `offset + 8 <= str.length()`.
#intrinsic("%v128.v128_load_i16x8")
#borrow(str)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load_i16x8(str : String, offset : Int) -> V128 {
  make(load_u16x4_le(str, offset), load_u16x4_le(str, offset + 4))
}

///|
/// Loads packed elements from `bytes` and sign-extends them into the vector.
#intrinsic("%v128.v128_load8x8_s")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load8x8_s(bytes : FixedArray[Byte], offset : Int) -> V128 {
  let value = load_u64_le(bytes, offset)
  make(sign_extend_u8x4_to_u16x4(value), sign_extend_u8x4_to_u16x4(value >> 32))
}

///|
/// Loads packed elements from `bytes` and zero-extends them into the vector.
#intrinsic("%v128.v128_load8x8_u")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load8x8_u(bytes : FixedArray[Byte], offset : Int) -> V128 {
  let value = load_u64_le(bytes, offset)
  make(widen_u8x4_to_u16x4(value), widen_u8x4_to_u16x4(value >> 32))
}

///|
/// Loads packed elements from `bytes` and sign-extends them into the vector.
#intrinsic("%v128.v128_load16x4_s")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load16x4_s(bytes : FixedArray[Byte], offset : Int) -> V128 {
  let value = load_u64_le(bytes, offset)
  make(
    sign_extend_u16x2_to_u32x2(value),
    sign_extend_u16x2_to_u32x2(value >> 32),
  )
}

///|
/// Loads packed elements from `bytes` and zero-extends them into the vector.
#intrinsic("%v128.v128_load16x4_u")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load16x4_u(bytes : FixedArray[Byte], offset : Int) -> V128 {
  let value = load_u64_le(bytes, offset)
  make(widen_u16x2_to_u32x2(value), widen_u16x2_to_u32x2(value >> 32))
}

///|
/// Loads packed elements from `bytes` and sign-extends them into the vector.
#intrinsic("%v128.v128_load32x2_s")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load32x2_s(bytes : FixedArray[Byte], offset : Int) -> V128 {
  let value = load_u64_le(bytes, offset)
  make(sign_extend_u32_to_u64(value), sign_extend_u32_to_u64(value >> 32))
}

///|
/// Loads packed elements from `bytes` and zero-extends them into the vector.
#intrinsic("%v128.v128_load32x2_u")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load32x2_u(bytes : FixedArray[Byte], offset : Int) -> V128 {
  let value = load_u64_le(bytes, offset)
  make(value & 0xffffffffUL, value >> 32)
}

///|
/// Loads one element from `bytes` and splats it across the vector.
#intrinsic("%v128.v128_load8_splat")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load8_splat(bytes : FixedArray[Byte], offset : Int) -> V128 {
  i8x16_splat(bytes.unsafe_get(offset))
}

///|
/// Loads one element from `bytes` and splats it across the vector.
#intrinsic("%v128.v128_load16_splat")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load16_splat(bytes : FixedArray[Byte], offset : Int) -> V128 {
  i16x8_splat(load_u16_le(bytes, offset))
}

///|
/// Loads one element from `bytes` and splats it across the vector.
#intrinsic("%v128.v128_load32_splat")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load32_splat(bytes : FixedArray[Byte], offset : Int) -> V128 {
  i32x4_splat(load_u32_le(bytes, offset))
}

///|
/// Loads one element from `bytes` and splats it across the vector.
#intrinsic("%v128.v128_load64_splat")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load64_splat(bytes : FixedArray[Byte], offset : Int) -> V128 {
  i64x2_splat(load_u64_le(bytes, offset))
}

///|
/// Loads one element from `bytes` into the low lane, zeroing the rest.
#intrinsic("%v128.v128_load32_zero")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load32_zero(bytes : FixedArray[Byte], offset : Int) -> V128 {
  i32x4_const(load_u32_le(bytes, offset), 0, 0, 0)
}

///|
/// Loads one element from `bytes` into the low lane, zeroing the rest.
#intrinsic("%v128.v128_load64_zero")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load64_zero(bytes : FixedArray[Byte], offset : Int) -> V128 {
  make(load_u64_le(bytes, offset), 0UL)
}

///|
/// Loads one element from `bytes` into lane `lane`, keeping the other lanes.
#intrinsic("%v128.v128_load8_lane")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load8_lane(
  bytes : FixedArray[Byte],
  offset : Int,
  value : V128,
  lane : Int,
) -> V128 {
  i8x16_replace_lane(value, bytes.unsafe_get(offset).to_uint(), lane)
}

///|
/// Loads one element from `bytes` into lane `lane`, keeping the other lanes.
#intrinsic("%v128.v128_load16_lane")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load16_lane(
  bytes : FixedArray[Byte],
  offset : Int,
  value : V128,
  lane : Int,
) -> V128 {
  i16x8_replace_lane(value, load_u16_le(bytes, offset).to_uint(), lane)
}

///|
/// Loads one element from `bytes` into lane `lane`, keeping the other lanes.
#intrinsic("%v128.v128_load32_lane")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load32_lane(
  bytes : FixedArray[Byte],
  offset : Int,
  value : V128,
  lane : Int,
) -> V128 {
  i32x4_replace_lane(value, load_u32_le(bytes, offset), lane)
}

///|
/// Loads one element from `bytes` into lane `lane`, keeping the other lanes.
#intrinsic("%v128.v128_load64_lane")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_load64_lane(
  bytes : FixedArray[Byte],
  offset : Int,
  value : V128,
  lane : Int,
) -> V128 {
  i64x2_replace_lane(value, load_u64_le(bytes, offset), lane)
}

///|
/// Stores a 128-bit vector to `bytes` at `offset` (little-endian).
#intrinsic("%v128.v128_store")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_store(bytes : FixedArray[Byte], offset : Int, value : V128) -> Unit {
  store_u64_le(bytes, offset, lo(value))
  store_u64_le(bytes, offset + 8, hi(value))
}

///|
/// Stores lane `lane` to `bytes` at `offset` (little-endian).
#intrinsic("%v128.v128_store8_lane")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_store8_lane(
  bytes : FixedArray[Byte],
  offset : Int,
  value : V128,
  lane : Int,
) -> Unit {
  bytes.unsafe_set(offset, u8_lane(value, lane))
}

///|
/// Stores lane `lane` to `bytes` at `offset` (little-endian).
#intrinsic("%v128.v128_store16_lane")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_store16_lane(
  bytes : FixedArray[Byte],
  offset : Int,
  value : V128,
  lane : Int,
) -> Unit {
  store_u16_le(bytes, offset, u16_lane(value, lane))
}

///|
/// Stores lane `lane` to `bytes` at `offset` (little-endian).
#intrinsic("%v128.v128_store32_lane")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_store32_lane(
  bytes : FixedArray[Byte],
  offset : Int,
  value : V128,
  lane : Int,
) -> Unit {
  store_u32_le(bytes, offset, u32_lane(value, lane))
}

///|
/// Stores lane `lane` to `bytes` at `offset` (little-endian).
#intrinsic("%v128.v128_store64_lane")
#borrow(bytes)
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn v128_store64_lane(
  bytes : FixedArray[Byte],
  offset : Int,
  value : V128,
  lane : Int,
) -> Unit {
  store_u64_le(bytes, offset, u64_lane(value, lane))
}