// 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 narrow_i16_to_i8_s(value : UInt16) -> Byte {
  let value = i16_signed(value)
  if value > 127 {
    0x7f
  } else if value < -128 {
    0x80
  } else {
    value.to_byte()
  }
}

///|
fn narrow_i16_to_i8_u(value : UInt16) -> Byte {
  let signed = i16_signed(value)
  if signed < 0 {
    0
  } else if signed > 255 {
    0xff
  } else {
    signed.to_byte()
  }
}

///|
fn narrow_i32_to_i16_s(value : UInt) -> UInt16 {
  let signed = value.reinterpret_as_int()
  if signed > 32767 {
    0x7fff
  } else if signed < -32768 {
    0x8000
  } else {
    signed.to_uint16()
  }
}

///|
fn narrow_i32_to_i16_u(value : UInt) -> UInt16 {
  let signed = value.reinterpret_as_int()
  if signed < 0 {
    0
  } else if signed > 65535 {
    0xffff
  } else {
    signed.to_uint16()
  }
}

///|
fn i16_q15mulr_sat_s(value : UInt16, other : UInt16) -> UInt16 {
  let rounded = (i16_signed(value) * i16_signed(other) + 0x4000) >> 15
  if rounded > 32767 {
    0x7fff
  } else if rounded < -32768 {
    0x8000
  } else {
    rounded.to_uint16()
  }
}

///|
fn i16_pair_dot(value : V128, other : V128, pair : Int) -> UInt {
  (i16_signed(u16_lane(value, pair * 2)) * i16_signed(u16_lane(other, pair * 2)) +
  i16_signed(u16_lane(value, pair * 2 + 1)) *
  i16_signed(u16_lane(other, pair * 2 + 1))).reinterpret_as_uint()
}

///|
/// Sign-extends the low half of the source lanes into eight 16-bit integer lanes.
#intrinsic("%v128.i16x8_extend_low_i8x16_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_extend_low_i8x16_s(value : V128) -> V128 {
  pack_u16x8_by(i => i8_signed(u8_lane(value, i)).to_uint16())
}

///|
/// Sign-extends the high half of the source lanes into eight 16-bit integer lanes.
#intrinsic("%v128.i16x8_extend_high_i8x16_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_extend_high_i8x16_s(value : V128) -> V128 {
  pack_u16x8_by(i => i8_signed(u8_lane(value, i + 8)).to_uint16())
}

///|
/// Zero-extends the low half of the source lanes into eight 16-bit integer lanes.
#intrinsic("%v128.i16x8_extend_low_i8x16_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_extend_low_i8x16_u(value : V128) -> V128 {
  pack_u16x8_by(i => u8_lane(value, i).to_uint16())
}

///|
/// Zero-extends the high half of the source lanes into eight 16-bit integer lanes.
#intrinsic("%v128.i16x8_extend_high_i8x16_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_extend_high_i8x16_u(value : V128) -> V128 {
  pack_u16x8_by(i => u8_lane(value, i + 8).to_uint16())
}

///|
/// Sign-extends the low half of the source lanes into four 32-bit integer lanes.
#intrinsic("%v128.i32x4_extend_low_i16x8_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_extend_low_i16x8_s(value : V128) -> V128 {
  pack_u32x4_by(i => i16_signed(u16_lane(value, i)).reinterpret_as_uint())
}

///|
/// Sign-extends the high half of the source lanes into four 32-bit integer lanes.
#intrinsic("%v128.i32x4_extend_high_i16x8_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_extend_high_i16x8_s(value : V128) -> V128 {
  pack_u32x4_by(i => i16_signed(u16_lane(value, i + 4)).reinterpret_as_uint())
}

///|
/// Zero-extends the low half of the source lanes into four 32-bit integer lanes.
#intrinsic("%v128.i32x4_extend_low_i16x8_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_extend_low_i16x8_u(value : V128) -> V128 {
  pack_u32x4_by(i => u16_lane(value, i).to_uint())
}

///|
/// Zero-extends the high half of the source lanes into four 32-bit integer lanes.
#intrinsic("%v128.i32x4_extend_high_i16x8_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_extend_high_i16x8_u(value : V128) -> V128 {
  pack_u32x4_by(i => u16_lane(value, i + 4).to_uint())
}

///|
/// Sign-extends the low half of the source lanes into two 64-bit integer lanes.
#intrinsic("%v128.i64x2_extend_low_i32x4_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i64x2_extend_low_i32x4_s(value : V128) -> V128 {
  pack_u64x2_by(i => {
    u32_lane(value, i).reinterpret_as_int().to_int64().reinterpret_as_uint64()
  })
}

///|
/// Sign-extends the high half of the source lanes into two 64-bit integer lanes.
#intrinsic("%v128.i64x2_extend_high_i32x4_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i64x2_extend_high_i32x4_s(value : V128) -> V128 {
  pack_u64x2_by(i => {
    u32_lane(value, i + 2)
    .reinterpret_as_int()
    .to_int64()
    .reinterpret_as_uint64()
  })
}

///|
/// Zero-extends the low half of the source lanes into two 64-bit integer lanes.
#intrinsic("%v128.i64x2_extend_low_i32x4_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i64x2_extend_low_i32x4_u(value : V128) -> V128 {
  pack_u64x2_by(i => u32_lane(value, i).to_uint64())
}

///|
/// Zero-extends the high half of the source lanes into two 64-bit integer lanes.
#intrinsic("%v128.i64x2_extend_high_i32x4_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i64x2_extend_high_i32x4_u(value : V128) -> V128 {
  pack_u64x2_by(i => u32_lane(value, i + 2).to_uint64())
}

///|
/// Relaxed lane-wise select over sixteen 8-bit integer lanes using each mask lane's high bit.
#intrinsic("%v128.i8x16_relaxed_laneselect")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i8x16_relaxed_laneselect(
  value : V128,
  other : V128,
  mask : V128,
) -> V128 {
  pack_u8x16_by(i => {
    if (u8_lane(mask, i) & 0x80) != 0 {
      u8_lane(value, i)
    } else {
      u8_lane(other, i)
    }
  })
}

///|
/// Relaxed lane-wise select over eight 16-bit integer lanes using each mask lane's high bit.
#intrinsic("%v128.i16x8_relaxed_laneselect")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_relaxed_laneselect(
  value : V128,
  other : V128,
  mask : V128,
) -> V128 {
  pack_u16x8_by(i => {
    if (u16_lane(mask, i) & 0x8000) != 0 {
      u16_lane(value, i)
    } else {
      u16_lane(other, i)
    }
  })
}

///|
/// Relaxed lane-wise select over four 32-bit integer lanes using each mask lane's high bit.
#intrinsic("%v128.i32x4_relaxed_laneselect")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_relaxed_laneselect(
  value : V128,
  other : V128,
  mask : V128,
) -> V128 {
  pack_u32x4_by(i => {
    if (u32_lane(mask, i) & 0x80000000) != 0 {
      u32_lane(value, i)
    } else {
      u32_lane(other, i)
    }
  })
}

///|
/// Relaxed lane-wise select over two 64-bit integer lanes using each mask lane's high bit.
#intrinsic("%v128.i64x2_relaxed_laneselect")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i64x2_relaxed_laneselect(
  value : V128,
  other : V128,
  mask : V128,
) -> V128 {
  pack_u64x2_by(i => {
    if (u64_lane(mask, i) & 0x8000000000000000UL) != 0UL {
      u64_lane(value, i)
    } else {
      u64_lane(other, i)
    }
  })
}

///|
/// Narrows two vectors into sixteen 8-bit integer lanes with signed saturation, low operand first.
#intrinsic("%v128.i8x16_narrow_i16x8_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i8x16_narrow_i16x8_s(value : V128, other : V128) -> V128 {
  pack_u8x16_by(i => {
    if i < 8 {
      narrow_i16_to_i8_s(u16_lane(value, i))
    } else {
      narrow_i16_to_i8_s(u16_lane(other, i - 8))
    }
  })
}

///|
/// Narrows two vectors into sixteen 8-bit integer lanes with unsigned saturation, low operand first.
#intrinsic("%v128.i8x16_narrow_i16x8_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i8x16_narrow_i16x8_u(value : V128, other : V128) -> V128 {
  pack_u8x16_by(i => {
    if i < 8 {
      narrow_i16_to_i8_u(u16_lane(value, i))
    } else {
      narrow_i16_to_i8_u(u16_lane(other, i - 8))
    }
  })
}

///|
/// Narrows two vectors into eight 16-bit integer lanes with signed saturation, low operand first.
#intrinsic("%v128.i16x8_narrow_i32x4_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_narrow_i32x4_s(value : V128, other : V128) -> V128 {
  pack_u16x8_by(i => {
    if i < 4 {
      narrow_i32_to_i16_s(u32_lane(value, i))
    } else {
      narrow_i32_to_i16_s(u32_lane(other, i - 4))
    }
  })
}

///|
/// Narrows two vectors into eight 16-bit integer lanes with unsigned saturation, low operand first.
#intrinsic("%v128.i16x8_narrow_i32x4_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_narrow_i32x4_u(value : V128, other : V128) -> V128 {
  pack_u16x8_by(i => {
    if i < 4 {
      narrow_i32_to_i16_u(u32_lane(value, i))
    } else {
      narrow_i32_to_i16_u(u32_lane(other, i - 4))
    }
  })
}

///|
/// Multiplies the low half of the source lanes (signed, widening) into eight 16-bit integer lanes.
#intrinsic("%v128.i16x8_extmul_low_i8x16_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_extmul_low_i8x16_s(value : V128, other : V128) -> V128 {
  i16x8_mul(i16x8_extend_low_i8x16_s(value), i16x8_extend_low_i8x16_s(other))
}

///|
/// Multiplies the high half of the source lanes (signed, widening) into eight 16-bit integer lanes.
#intrinsic("%v128.i16x8_extmul_high_i8x16_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_extmul_high_i8x16_s(value : V128, other : V128) -> V128 {
  i16x8_mul(i16x8_extend_high_i8x16_s(value), i16x8_extend_high_i8x16_s(other))
}

///|
/// Multiplies the low half of the source lanes (unsigned, widening) into eight 16-bit integer lanes.
#intrinsic("%v128.i16x8_extmul_low_i8x16_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_extmul_low_i8x16_u(value : V128, other : V128) -> V128 {
  i16x8_mul(i16x8_extend_low_i8x16_u(value), i16x8_extend_low_i8x16_u(other))
}

///|
/// Multiplies the high half of the source lanes (unsigned, widening) into eight 16-bit integer lanes.
#intrinsic("%v128.i16x8_extmul_high_i8x16_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_extmul_high_i8x16_u(value : V128, other : V128) -> V128 {
  i16x8_mul(i16x8_extend_high_i8x16_u(value), i16x8_extend_high_i8x16_u(other))
}

///|
/// Multiplies the low half of the source lanes (signed, widening) into four 32-bit integer lanes.
#intrinsic("%v128.i32x4_extmul_low_i16x8_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_extmul_low_i16x8_s(value : V128, other : V128) -> V128 {
  i32x4_mul(i32x4_extend_low_i16x8_s(value), i32x4_extend_low_i16x8_s(other))
}

///|
/// Multiplies the high half of the source lanes (signed, widening) into four 32-bit integer lanes.
#intrinsic("%v128.i32x4_extmul_high_i16x8_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_extmul_high_i16x8_s(value : V128, other : V128) -> V128 {
  i32x4_mul(i32x4_extend_high_i16x8_s(value), i32x4_extend_high_i16x8_s(other))
}

///|
/// Multiplies the low half of the source lanes (unsigned, widening) into four 32-bit integer lanes.
#intrinsic("%v128.i32x4_extmul_low_i16x8_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_extmul_low_i16x8_u(value : V128, other : V128) -> V128 {
  i32x4_mul(i32x4_extend_low_i16x8_u(value), i32x4_extend_low_i16x8_u(other))
}

///|
/// Multiplies the high half of the source lanes (unsigned, widening) into four 32-bit integer lanes.
#intrinsic("%v128.i32x4_extmul_high_i16x8_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_extmul_high_i16x8_u(value : V128, other : V128) -> V128 {
  i32x4_mul(i32x4_extend_high_i16x8_u(value), i32x4_extend_high_i16x8_u(other))
}

///|
/// Multiplies the low half of the source lanes (signed, widening) into two 64-bit integer lanes.
#intrinsic("%v128.i64x2_extmul_low_i32x4_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i64x2_extmul_low_i32x4_s(value : V128, other : V128) -> V128 {
  pack_u64x2_by(i => {
    (u32_lane(value, i).reinterpret_as_int().to_int64() *
    u32_lane(other, i).reinterpret_as_int().to_int64()).reinterpret_as_uint64()
  })
}

///|
/// Multiplies the high half of the source lanes (signed, widening) into two 64-bit integer lanes.
#intrinsic("%v128.i64x2_extmul_high_i32x4_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i64x2_extmul_high_i32x4_s(value : V128, other : V128) -> V128 {
  pack_u64x2_by(i => {
    (u32_lane(value, i + 2).reinterpret_as_int().to_int64() *
    u32_lane(other, i + 2).reinterpret_as_int().to_int64()).reinterpret_as_uint64()
  })
}

///|
/// Multiplies the low half of the source lanes (unsigned, widening) into two 64-bit integer lanes.
#intrinsic("%v128.i64x2_extmul_low_i32x4_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i64x2_extmul_low_i32x4_u(value : V128, other : V128) -> V128 {
  pack_u64x2_by(i => {
    u32_lane(value, i).to_uint64() * u32_lane(other, i).to_uint64()
  })
}

///|
/// Multiplies the high half of the source lanes (unsigned, widening) into two 64-bit integer lanes.
#intrinsic("%v128.i64x2_extmul_high_i32x4_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i64x2_extmul_high_i32x4_u(value : V128, other : V128) -> V128 {
  pack_u64x2_by(i => {
    u32_lane(value, i + 2).to_uint64() * u32_lane(other, i + 2).to_uint64()
  })
}

///|
/// Lane-wise signed Q15 fixed-point multiply with rounding and saturation.
#intrinsic("%v128.i16x8_q15mulr_sat_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_q15mulr_sat_s(value : V128, other : V128) -> V128 {
  pack_u16x8_by(i => i16_q15mulr_sat_s(u16_lane(value, i), u16_lane(other, i)))
}

///|
/// Relaxed signed Q15 fixed-point multiply with rounding.
#intrinsic("%v128.i16x8_relaxed_q15mulr_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_relaxed_q15mulr_s(value : V128, other : V128) -> V128 {
  i16x8_q15mulr_sat_s(value, other)
}

///|
/// Sign-extends adjacent lane pairs and adds each pair into eight 16-bit integer lanes.
#intrinsic("%v128.i16x8_extadd_pairwise_i8x16_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_extadd_pairwise_i8x16_s(value : V128) -> V128 {
  pack_u16x8_by(i => {
    (i8_signed(u8_lane(value, i * 2)) + i8_signed(u8_lane(value, i * 2 + 1))).to_uint16()
  })
}

///|
/// Zero-extends adjacent lane pairs and adds each pair into eight 16-bit integer lanes.
#intrinsic("%v128.i16x8_extadd_pairwise_i8x16_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_extadd_pairwise_i8x16_u(value : V128) -> V128 {
  pack_u16x8_by(i => {
    u8_lane(value, i * 2).to_uint16() + u8_lane(value, i * 2 + 1).to_uint16()
  })
}

///|
/// Sign-extends adjacent lane pairs and adds each pair into four 32-bit integer lanes.
#intrinsic("%v128.i32x4_extadd_pairwise_i16x8_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_extadd_pairwise_i16x8_s(value : V128) -> V128 {
  pack_u32x4_by(i => {
    (i16_signed(u16_lane(value, i * 2)) + i16_signed(u16_lane(value, i * 2 + 1))).reinterpret_as_uint()
  })
}

///|
/// Zero-extends adjacent lane pairs and adds each pair into four 32-bit integer lanes.
#intrinsic("%v128.i32x4_extadd_pairwise_i16x8_u")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_extadd_pairwise_i16x8_u(value : V128) -> V128 {
  pack_u32x4_by(i => {
    u16_lane(value, i * 2).to_uint() + u16_lane(value, i * 2 + 1).to_uint()
  })
}

///|
/// Relaxed dot product of signed 8-bit by 7-bit lanes into 16-bit lanes.
///
/// No `#intrinsic` annotation: the existing intrinsic
/// `%v128.i16x8_relaxed_dot_s_i8x16` does not follow the spec/function
/// naming convention (`i16x8.relaxed_dot_i8x16_i7x16_s`) and is slated to
/// be renamed in the compiler. Until then this runs via the portable
/// fallback below, which is correct on every backend.
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i16x8_relaxed_dot_i8x16_i7x16_s(value : V128, other : V128) -> V128 {
  pack_u16x8_by(i => {
    (i8_signed(u8_lane(value, i * 2)) * i8_signed(u8_lane(other, i * 2)) +
    i8_signed(u8_lane(value, i * 2 + 1)) * i8_signed(u8_lane(other, i * 2 + 1))).to_uint16()
  })
}

///|
/// Multiplies adjacent pairs of signed 16-bit lanes and sums each pair into a 32-bit lane.
#intrinsic("%v128.i32x4_dot_i16x8_s")
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_dot_i16x8_s(value : V128, other : V128) -> V128 {
  pack_u32x4_by(i => i16_pair_dot(value, other, i))
}

///|
/// Relaxed dot product of signed 8-bit by 7-bit lanes, accumulated into 32-bit lanes.
///
/// No `#intrinsic` annotation: the existing intrinsic
/// `%v128.i32x4_relaxed_dot_add_s_i16x8` is misnamed. Its `_i16x8` suffix
/// describes the intermediate vector, not the i8x16/i7x16 inputs, and the
/// NEON lowering followed the name and multiplied the inputs as i16 lanes
/// (vmull_s16) instead of i8 lanes (vmull_s8), producing wrong results.
/// Omit the annotation until the compiler's intrinsic name and lowering are
/// fixed; the portable fallback below is correct on every backend.
#internal(experimental, "subject to breaking change without notice")
#doc(hidden)
pub fn i32x4_relaxed_dot_i8x16_i7x16_add_s(
  value : V128,
  other : V128,
  accumulator : V128,
) -> V128 {
  i32x4_add(
    i32x4_extadd_pairwise_i16x8_s(i16x8_relaxed_dot_i8x16_i7x16_s(value, other)),
    accumulator,
  )
}