// Vector4 math functions and Quaternion operations (as Vector4::quat_*)
// Ported from raymath.h (raylib 5.5)
// Private math helpers are in math_helpers.mbt

// ----- Vector4 math functions -----

///|
/// Get Vector4 with components equal to zero.
pub fn Vector4::zero() -> Vector4 {
  { x: 0.0, y: 0.0, z: 0.0, w: 0.0 }
}

///|
/// Get Vector4 with components equal to one.
pub fn Vector4::one() -> Vector4 {
  { x: 1.0, y: 1.0, z: 1.0, w: 1.0 }
}

///|
/// Add two vectors (v1 + v2).
pub fn Vector4::add(v1 : Vector4, v2 : Vector4) -> Vector4 {
  { x: v1.x + v2.x, y: v1.y + v2.y, z: v1.z + v2.z, w: v1.w + v2.w }
}

///|
/// Add vector and float value.
pub fn Vector4::add_value(v : Vector4, add : Float) -> Vector4 {
  { x: v.x + add, y: v.y + add, z: v.z + add, w: v.w + add }
}

///|
/// Subtract two vectors (v1 - v2).
pub fn Vector4::subtract(v1 : Vector4, v2 : Vector4) -> Vector4 {
  { x: v1.x - v2.x, y: v1.y - v2.y, z: v1.z - v2.z, w: v1.w - v2.w }
}

///|
/// Subtract vector by float value.
pub fn Vector4::subtract_value(v : Vector4, sub : Float) -> Vector4 {
  { x: v.x - sub, y: v.y - sub, z: v.z - sub, w: v.w - sub }
}

///|
/// Calculate vector length.
pub fn Vector4::length(v : Vector4) -> Float {
  sqrtf(v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w)
}

///|
/// Scale vector by float value (multiply by value).
pub fn Vector4::scale(v : Vector4, scalar : Float) -> Vector4 {
  { x: v.x * scalar, y: v.y * scalar, z: v.z * scalar, w: v.w * scalar }
}

///|
/// Multiply vector by vector.
pub fn Vector4::multiply(v1 : Vector4, v2 : Vector4) -> Vector4 {
  { x: v1.x * v2.x, y: v1.y * v2.y, z: v1.z * v2.z, w: v1.w * v2.w }
}

///|
/// Negate vector.
pub fn Vector4::negate(v : Vector4) -> Vector4 {
  { x: -v.x, y: -v.y, z: -v.z, w: -v.w }
}

///|
/// Divide vector by vector.
pub fn Vector4::divide(v1 : Vector4, v2 : Vector4) -> Vector4 {
  { x: v1.x / v2.x, y: v1.y / v2.y, z: v1.z / v2.z, w: v1.w / v2.w }
}

///|
/// Normalize provided vector.
pub fn Vector4::normalize(v : Vector4) -> Vector4 {
  let len = sqrtf(v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w)
  if len > 0.0 {
    let ilength : Float = (1.0 : Float) / len
    { x: v.x * ilength, y: v.y * ilength, z: v.z * ilength, w: v.w * ilength }
  } else {
    { x: 0.0, y: 0.0, z: 0.0, w: 0.0 }
  }
}

///|
/// Get min value for each pair of components.
pub fn Vector4::min(v1 : Vector4, v2 : Vector4) -> Vector4 {
  {
    x: fminf(v1.x, v2.x),
    y: fminf(v1.y, v2.y),
    z: fminf(v1.z, v2.z),
    w: fminf(v1.w, v2.w),
  }
}

///|
/// Get max value for each pair of components.
pub fn Vector4::max(v1 : Vector4, v2 : Vector4) -> Vector4 {
  {
    x: fmaxf(v1.x, v2.x),
    y: fmaxf(v1.y, v2.y),
    z: fmaxf(v1.z, v2.z),
    w: fmaxf(v1.w, v2.w),
  }
}

///|
/// Check whether two given vectors are almost equal.
pub fn Vector4::equals(p : Vector4, q : Vector4) -> Bool {
  let epsilon : Float = 0.000001
  fabsf(p.x - q.x) <=
  epsilon * fmaxf((1.0 : Float), fmaxf(fabsf(p.x), fabsf(q.x))) &&
  fabsf(p.y - q.y) <=
  epsilon * fmaxf((1.0 : Float), fmaxf(fabsf(p.y), fabsf(q.y))) &&
  fabsf(p.z - q.z) <=
  epsilon * fmaxf((1.0 : Float), fmaxf(fabsf(p.z), fabsf(q.z))) &&
  fabsf(p.w - q.w) <=
  epsilon * fmaxf((1.0 : Float), fmaxf(fabsf(p.w), fabsf(q.w)))
}

// ----- Quaternion functions (as Vector4::quat_*) -----

///|
/// Get identity quaternion.
pub fn Vector4::quat_identity() -> Vector4 {
  { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }
}

///|
/// Compute the length of a quaternion.
pub fn Vector4::quat_length(q : Vector4) -> Float {
  sqrtf(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w)
}

///|
/// Normalize provided quaternion.
pub fn Vector4::quat_normalize(q : Vector4) -> Vector4 {
  let len = sqrtf(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w)
  let length : Float = if len == 0.0 { 1.0 } else { len }
  let ilength : Float = (1.0 : Float) / length
  { x: q.x * ilength, y: q.y * ilength, z: q.z * ilength, w: q.w * ilength }
}

///|
/// Invert provided quaternion.
pub fn Vector4::quat_invert(q : Vector4) -> Vector4 {
  let length_sq = q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w
  if length_sq != 0.0 {
    let inv_length : Float = (1.0 : Float) / length_sq
    {
      x: q.x * -inv_length,
      y: q.y * -inv_length,
      z: q.z * -inv_length,
      w: q.w * inv_length,
    }
  } else {
    q
  }
}

///|
/// Calculate two quaternion multiplication.
pub fn Vector4::quat_multiply(q1 : Vector4, q2 : Vector4) -> Vector4 {
  let qax = q1.x
  let qay = q1.y
  let qaz = q1.z
  let qaw = q1.w
  let qbx = q2.x
  let qby = q2.y
  let qbz = q2.z
  let qbw = q2.w
  {
    x: qax * qbw + qaw * qbx + qay * qbz - qaz * qby,
    y: qay * qbw + qaw * qby + qaz * qbx - qax * qbz,
    z: qaz * qbw + qaw * qbz + qax * qby - qay * qbx,
    w: qaw * qbw - qax * qbx - qay * qby - qaz * qbz,
  }
}

///|
/// Scale quaternion by float value.
pub fn Vector4::quat_scale(q : Vector4, mul : Float) -> Vector4 {
  { x: q.x * mul, y: q.y * mul, z: q.z * mul, w: q.w * mul }
}

///|
/// Divide two quaternions.
pub fn Vector4::quat_divide(q1 : Vector4, q2 : Vector4) -> Vector4 {
  { x: q1.x / q2.x, y: q1.y / q2.y, z: q1.z / q2.z, w: q1.w / q2.w }
}

///|
/// Calculate linear interpolation between two quaternions.
pub fn Vector4::quat_lerp(
  q1 : Vector4,
  q2 : Vector4,
  amount : Float,
) -> Vector4 {
  {
    x: q1.x + amount * (q2.x - q1.x),
    y: q1.y + amount * (q2.y - q1.y),
    z: q1.z + amount * (q2.z - q1.z),
    w: q1.w + amount * (q2.w - q1.w),
  }
}

///|
/// Calculate slerp-optimized interpolation between two quaternions (normalized lerp).
pub fn Vector4::quat_nlerp(
  q1 : Vector4,
  q2 : Vector4,
  amount : Float,
) -> Vector4 {
  // QuaternionLerp(q1, q2, amount)
  let rx = q1.x + amount * (q2.x - q1.x)
  let ry = q1.y + amount * (q2.y - q1.y)
  let rz = q1.z + amount * (q2.z - q1.z)
  let rw = q1.w + amount * (q2.w - q1.w)
  // QuaternionNormalize(q)
  let len = sqrtf(rx * rx + ry * ry + rz * rz + rw * rw)
  let length : Float = if len == 0.0 { 1.0 } else { len }
  let ilength : Float = (1.0 : Float) / length
  { x: rx * ilength, y: ry * ilength, z: rz * ilength, w: rw * ilength }
}

///|
/// Calculate spherical linear interpolation between two quaternions.
pub fn Vector4::quat_slerp(
  q1 : Vector4,
  q2 : Vector4,
  amount : Float,
) -> Vector4 {
  let epsilon : Float = 0.000001
  let mut cos_half_theta = q1.x * q2.x + q1.y * q2.y + q1.z * q2.z + q1.w * q2.w
  let mut q2x = q2.x
  let mut q2y = q2.y
  let mut q2z = q2.z
  let mut q2w = q2.w
  if cos_half_theta < 0.0 {
    q2x = -q2x
    q2y = -q2y
    q2z = -q2z
    q2w = -q2w
    cos_half_theta = -cos_half_theta
  }
  if fabsf(cos_half_theta) >= (1.0 : Float) {
    q1
  } else if cos_half_theta > (0.95 : Float) {
    Vector4::quat_nlerp(q1, { x: q2x, y: q2y, z: q2z, w: q2w }, amount)
  } else {
    let half_theta = acosf(cos_half_theta)
    let sin_half_theta = sqrtf((1.0 : Float) - cos_half_theta * cos_half_theta)
    // NOTE: This branch is unreachable — carried over from upstream raylib
    // (raymath.h QuaternionSlerp). The cos > 0.95 guard above ensures we
    // only reach here when cos <= 0.95, giving sin >= ~0.312, far above
    // epsilon. Kept for parity with the C implementation.
    if fabsf(sin_half_theta) < epsilon {
      let half : Float = 0.5
      {
        x: q1.x * half + q2x * half,
        y: q1.y * half + q2y * half,
        z: q1.z * half + q2z * half,
        w: q1.w * half + q2w * half,
      }
    } else {
      let ratio_a = sinf(((1.0 : Float) - amount) * half_theta) / sin_half_theta
      let ratio_b = sinf(amount * half_theta) / sin_half_theta
      {
        x: q1.x * ratio_a + q2x * ratio_b,
        y: q1.y * ratio_a + q2y * ratio_b,
        z: q1.z * ratio_a + q2z * ratio_b,
        w: q1.w * ratio_a + q2w * ratio_b,
      }
    }
  }
}

///|
/// Calculate quaternion cubic hermite spline interpolation.
pub fn Vector4::quat_cubic_hermite_spline(
  q1 : Vector4,
  out_tangent1 : Vector4,
  q2 : Vector4,
  in_tangent2 : Vector4,
  t : Float,
) -> Vector4 {
  let t2 = t * t
  let t3 = t2 * t
  let two : Float = 2.0
  let three : Float = 3.0
  let one : Float = 1.0
  let h00 = two * t3 - three * t2 + one
  let h10 = t3 - two * t2 + t
  let h01 = -two * t3 + three * t2
  let h11 = t3 - t2
  let p0 = Vector4::quat_scale(q1, h00)
  let m0 = Vector4::quat_scale(out_tangent1, h10)
  let p1 = Vector4::quat_scale(q2, h01)
  let m1 = Vector4::quat_scale(in_tangent2, h11)
  let result = Vector4::quat_add(p0, m0)
  let result = Vector4::quat_add(result, p1)
  let result = Vector4::quat_add(result, m1)
  Vector4::quat_normalize(result)
}

///|
/// Calculate quaternion based on the rotation from one vector to another.
pub fn Vector4::quat_from_vector3_to_vector3(
  from_ : Vector3,
  to : Vector3,
) -> Vector4 {
  // Vector3DotProduct(from_, to)
  let cos2_theta = from_.x * to.x + from_.y * to.y + from_.z * to.z
  // Vector3CrossProduct(from_, to)
  let cross_x = from_.y * to.z - from_.z * to.y
  let cross_y = from_.z * to.x - from_.x * to.z
  let cross_z = from_.x * to.y - from_.y * to.x
  let qx = cross_x
  let qy = cross_y
  let qz = cross_z
  let qw : Float = (1.0 : Float) + cos2_theta
  // QuaternionNormalize(q)
  let len = sqrtf(qx * qx + qy * qy + qz * qz + qw * qw)
  let length : Float = if len == 0.0 { 1.0 } else { len }
  let ilength : Float = (1.0 : Float) / length
  { x: qx * ilength, y: qy * ilength, z: qz * ilength, w: qw * ilength }
}

///|
/// Get a quaternion for a given rotation matrix.
pub fn Vector4::quat_from_matrix(mat : Matrix) -> Vector4 {
  let four_w_sq_minus1 = mat.m0 + mat.m5 + mat.m10
  let four_x_sq_minus1 = mat.m0 - mat.m5 - mat.m10
  let four_y_sq_minus1 = mat.m5 - mat.m0 - mat.m10
  let four_z_sq_minus1 = mat.m10 - mat.m0 - mat.m5
  let mut biggest_index = 0
  let mut four_biggest_sq_minus1 = four_w_sq_minus1
  if four_x_sq_minus1 > four_biggest_sq_minus1 {
    four_biggest_sq_minus1 = four_x_sq_minus1
    biggest_index = 1
  }
  if four_y_sq_minus1 > four_biggest_sq_minus1 {
    four_biggest_sq_minus1 = four_y_sq_minus1
    biggest_index = 2
  }
  if four_z_sq_minus1 > four_biggest_sq_minus1 {
    four_biggest_sq_minus1 = four_z_sq_minus1
    biggest_index = 3
  }
  let biggest_val = sqrtf(four_biggest_sq_minus1 + (1.0 : Float)) *
    (0.5 : Float)
  let mult : Float = (0.25 : Float) / biggest_val
  if biggest_index == 0 {
    {
      x: (mat.m6 - mat.m9) * mult,
      y: (mat.m8 - mat.m2) * mult,
      z: (mat.m1 - mat.m4) * mult,
      w: biggest_val,
    }
  } else if biggest_index == 1 {
    {
      x: biggest_val,
      y: (mat.m1 + mat.m4) * mult,
      z: (mat.m8 + mat.m2) * mult,
      w: (mat.m6 - mat.m9) * mult,
    }
  } else if biggest_index == 2 {
    {
      x: (mat.m1 + mat.m4) * mult,
      y: biggest_val,
      z: (mat.m6 + mat.m9) * mult,
      w: (mat.m8 - mat.m2) * mult,
    }
  } else {
    {
      x: (mat.m8 + mat.m2) * mult,
      y: (mat.m6 + mat.m9) * mult,
      z: biggest_val,
      w: (mat.m1 - mat.m4) * mult,
    }
  }
}

///|
/// Get a matrix for a given quaternion.
pub fn Vector4::quat_to_matrix(q : Vector4) -> Matrix {
  let a2 = q.x * q.x
  let b2 = q.y * q.y
  let c2 = q.z * q.z
  let ac = q.x * q.z
  let ab = q.x * q.y
  let bc = q.y * q.z
  let ad = q.w * q.x
  let bd = q.w * q.y
  let cd = q.w * q.z
  let two : Float = 2.0
  let one : Float = 1.0
  {
    m0: one - two * (b2 + c2),
    m1: two * (ab + cd),
    m2: two * (ac - bd),
    m3: 0.0,
    m4: two * (ab - cd),
    m5: one - two * (a2 + c2),
    m6: two * (bc + ad),
    m7: 0.0,
    m8: two * (ac + bd),
    m9: two * (bc - ad),
    m10: one - two * (a2 + b2),
    m11: 0.0,
    m12: 0.0,
    m13: 0.0,
    m14: 0.0,
    m15: 1.0,
  }
}

///|
/// Get rotation quaternion for an angle and axis.
pub fn Vector4::quat_from_axis_angle(axis : Vector3, angle : Float) -> Vector4 {
  let axis_length = sqrtf(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z)
  if axis_length != 0.0 {
    let half_angle = angle * (0.5 : Float)
    // Vector3Normalize(axis)
    let length : Float = if axis_length == 0.0 { 1.0 } else { axis_length }
    let ilength : Float = (1.0 : Float) / length
    let ax = axis.x * ilength
    let ay = axis.y * ilength
    let az = axis.z * ilength
    let sinres = sinf(half_angle)
    let cosres = cosf(half_angle)
    let qx = ax * sinres
    let qy = ay * sinres
    let qz = az * sinres
    let qw = cosres
    // QuaternionNormalize(q)
    let len = sqrtf(qx * qx + qy * qy + qz * qz + qw * qw)
    let norm_length : Float = if len == 0.0 { 1.0 } else { len }
    let inv_len : Float = (1.0 : Float) / norm_length
    { x: qx * inv_len, y: qy * inv_len, z: qz * inv_len, w: qw * inv_len }
  } else {
    { x: 0.0, y: 0.0, z: 0.0, w: 1.0 }
  }
}

///|
/// Get the rotation angle and axis for a given quaternion.
pub fn Vector4::quat_to_axis_angle(q : Vector4) -> (Vector3, Float) {
  let epsilon : Float = 0.000001
  let mut qx = q.x
  let mut qy = q.y
  let mut qz = q.z
  let mut qw = q.w
  if fabsf(qw) > (1.0 : Float) {
    // QuaternionNormalize(q)
    let len = sqrtf(qx * qx + qy * qy + qz * qz + qw * qw)
    let length : Float = if len == 0.0 { 1.0 } else { len }
    let ilength : Float = (1.0 : Float) / length
    qx = qx * ilength
    qy = qy * ilength
    qz = qz * ilength
    qw = qw * ilength
  }
  let res_angle : Float = (2.0 : Float) * acosf(qw)
  let den = sqrtf((1.0 : Float) - qw * qw)
  let res_axis : Vector3 = if den > epsilon {
    { x: qx / den, y: qy / den, z: qz / den }
  } else {
    // Angle is zero, set arbitrary normalized axis
    { x: 1.0, y: 0.0, z: 0.0 }
  }
  (res_axis, res_angle)
}

///|
/// Get the quaternion equivalent to Euler angles (pitch, yaw, roll).
pub fn Vector4::quat_from_euler(
  pitch : Float,
  yaw : Float,
  roll : Float,
) -> Vector4 {
  let half : Float = 0.5
  let x0 = cosf(pitch * half)
  let x1 = sinf(pitch * half)
  let y0 = cosf(yaw * half)
  let y1 = sinf(yaw * half)
  let z0 = cosf(roll * half)
  let z1 = sinf(roll * half)
  {
    x: x1 * y0 * z0 - x0 * y1 * z1,
    y: x0 * y1 * z0 + x1 * y0 * z1,
    z: x0 * y0 * z1 - x1 * y1 * z0,
    w: x0 * y0 * z0 + x1 * y1 * z1,
  }
}

///|
/// Get the Euler angles equivalent to quaternion (roll, pitch, yaw).
pub fn Vector4::quat_to_euler(q : Vector4) -> Vector3 {
  let two : Float = 2.0
  let one : Float = 1.0
  // Roll (x-axis rotation)
  let x0 = two * (q.w * q.x + q.y * q.z)
  let x1 = one - two * (q.x * q.x + q.y * q.y)
  let roll = atan2f(x0, x1)
  // Pitch (y-axis rotation)
  let mut y0 = two * (q.w * q.y - q.z * q.x)
  if y0 > (1.0 : Float) {
    y0 = 1.0
  }
  if y0 < (-1.0 : Float) {
    y0 = -1.0
  }
  let pitch = asinf(y0)
  // Yaw (z-axis rotation)
  let z0 = two * (q.w * q.z + q.x * q.y)
  let z1 = one - two * (q.y * q.y + q.z * q.z)
  let yaw = atan2f(z0, z1)
  { x: roll, y: pitch, z: yaw }
}

///|
/// Transform a quaternion given a transformation matrix.
pub fn Vector4::quat_transform(q : Vector4, mat : Matrix) -> Vector4 {
  {
    x: mat.m0 * q.x + mat.m4 * q.y + mat.m8 * q.z + mat.m12 * q.w,
    y: mat.m1 * q.x + mat.m5 * q.y + mat.m9 * q.z + mat.m13 * q.w,
    z: mat.m2 * q.x + mat.m6 * q.y + mat.m10 * q.z + mat.m14 * q.w,
    w: mat.m3 * q.x + mat.m7 * q.y + mat.m11 * q.z + mat.m15 * q.w,
  }
}

///|
/// Check whether two given quaternions are almost equal.
pub fn Vector4::quat_equals(p : Vector4, q : Vector4) -> Bool {
  let epsilon : Float = 0.000001
  (
    fabsf(p.x - q.x) <=
    epsilon * fmaxf((1.0 : Float), fmaxf(fabsf(p.x), fabsf(q.x))) &&
    fabsf(p.y - q.y) <=
    epsilon * fmaxf((1.0 : Float), fmaxf(fabsf(p.y), fabsf(q.y))) &&
    fabsf(p.z - q.z) <=
    epsilon * fmaxf((1.0 : Float), fmaxf(fabsf(p.z), fabsf(q.z))) &&
    fabsf(p.w - q.w) <=
    epsilon * fmaxf((1.0 : Float), fmaxf(fabsf(p.w), fabsf(q.w)))
  ) ||
  (
    fabsf(p.x + q.x) <=
    epsilon * fmaxf((1.0 : Float), fmaxf(fabsf(p.x), fabsf(q.x))) &&
    fabsf(p.y + q.y) <=
    epsilon * fmaxf((1.0 : Float), fmaxf(fabsf(p.y), fabsf(q.y))) &&
    fabsf(p.z + q.z) <=
    epsilon * fmaxf((1.0 : Float), fmaxf(fabsf(p.z), fabsf(q.z))) &&
    fabsf(p.w + q.w) <=
    epsilon * fmaxf((1.0 : Float), fmaxf(fabsf(p.w), fabsf(q.w)))
  )
}

///|
/// Add two quaternions.
pub fn Vector4::quat_add(q1 : Vector4, q2 : Vector4) -> Vector4 {
  { x: q1.x + q2.x, y: q1.y + q2.y, z: q1.z + q2.z, w: q1.w + q2.w }
}

///|
/// Add quaternion and float value.
pub fn Vector4::quat_add_value(q : Vector4, add : Float) -> Vector4 {
  { x: q.x + add, y: q.y + add, z: q.z + add, w: q.w + add }
}

///|
/// Subtract two quaternions.
pub fn Vector4::quat_subtract(q1 : Vector4, q2 : Vector4) -> Vector4 {
  { x: q1.x - q2.x, y: q1.y - q2.y, z: q1.z - q2.z, w: q1.w - q2.w }
}

///|
/// Subtract quaternion and float value.
pub fn Vector4::quat_subtract_value(q : Vector4, sub : Float) -> Vector4 {
  { x: q.x - sub, y: q.y - sub, z: q.z - sub, w: q.w - sub }
}