// Vector3 math functions — pure MoonBit port of raymath Vector3 operations
// Private math helpers are in math_helpers.mbt

// Private helper: matrix inversion (needed by Vector3::unproject)

///|
fn matrix_invert(m : Matrix) -> Matrix {
  let a00 = m.m0
  let a01 = m.m1
  let a02 = m.m2
  let a03 = m.m3
  let a10 = m.m4
  let a11 = m.m5
  let a12 = m.m6
  let a13 = m.m7
  let a20 = m.m8
  let a21 = m.m9
  let a22 = m.m10
  let a23 = m.m11
  let a30 = m.m12
  let a31 = m.m13
  let a32 = m.m14
  let a33 = m.m15
  let b00 = a00 * a11 - a01 * a10
  let b01 = a00 * a12 - a02 * a10
  let b02 = a00 * a13 - a03 * a10
  let b03 = a01 * a12 - a02 * a11
  let b04 = a01 * a13 - a03 * a11
  let b05 = a02 * a13 - a03 * a12
  let b06 = a20 * a31 - a21 * a30
  let b07 = a20 * a32 - a22 * a30
  let b08 = a20 * a33 - a23 * a30
  let b09 = a21 * a32 - a22 * a31
  let b10 = a21 * a33 - a23 * a31
  let b11 = a22 * a33 - a23 * a32
  let inv_det : Float = (1.0 : Float) /
    (b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06)
  {
    m0: (a11 * b11 - a12 * b10 + a13 * b09) * inv_det,
    m1: (-a01 * b11 + a02 * b10 - a03 * b09) * inv_det,
    m2: (a31 * b05 - a32 * b04 + a33 * b03) * inv_det,
    m3: (-a21 * b05 + a22 * b04 - a23 * b03) * inv_det,
    m4: (-a10 * b11 + a12 * b08 - a13 * b07) * inv_det,
    m5: (a00 * b11 - a02 * b08 + a03 * b07) * inv_det,
    m6: (-a30 * b05 + a32 * b02 - a33 * b01) * inv_det,
    m7: (a20 * b05 - a22 * b02 + a23 * b01) * inv_det,
    m8: (a10 * b10 - a11 * b08 + a13 * b06) * inv_det,
    m9: (-a00 * b10 + a01 * b08 - a03 * b06) * inv_det,
    m10: (a30 * b04 - a31 * b02 + a33 * b00) * inv_det,
    m11: (-a20 * b04 + a21 * b02 - a23 * b00) * inv_det,
    m12: (-a10 * b09 + a11 * b07 - a12 * b06) * inv_det,
    m13: (a00 * b09 - a01 * b07 + a02 * b06) * inv_det,
    m14: (-a30 * b03 + a31 * b01 - a32 * b00) * inv_det,
    m15: (a20 * b03 - a21 * b01 + a22 * b00) * inv_det,
  }
}

// Vector3 math functions

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

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

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

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

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

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

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

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

///|
/// Calculate two vectors cross product.
pub fn Vector3::cross_product(v1 : Vector3, v2 : Vector3) -> Vector3 {
  {
    x: v1.y * v2.z - v1.z * v2.y,
    y: v1.z * v2.x - v1.x * v2.z,
    z: v1.x * v2.y - v1.y * v2.x,
  }
}

///|
/// Calculate one vector perpendicular to the given vector.
pub fn Vector3::perpendicular(v : Vector3) -> Vector3 {
  let mut min = fabsf(v.x)
  let mut cardinal_x : Float = 1.0
  let mut cardinal_y : Float = 0.0
  let mut cardinal_z : Float = 0.0
  if fabsf(v.y) < min {
    min = fabsf(v.y)
    cardinal_x = 0.0
    cardinal_y = 1.0
    cardinal_z = 0.0
  }
  if fabsf(v.z) < min {
    cardinal_x = 0.0
    cardinal_y = 0.0
    cardinal_z = 1.0
  }
  ignore(min)
  {
    x: v.y * cardinal_z - v.z * cardinal_y,
    y: v.z * cardinal_x - v.x * cardinal_z,
    z: v.x * cardinal_y - v.y * cardinal_x,
  }
}

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

///|
/// Calculate vector square length.
pub fn Vector3::length_sqr(v : Vector3) -> Float {
  v.x * v.x + v.y * v.y + v.z * v.z
}

///|
/// Calculate two vectors dot product.
pub fn Vector3::dot_product(v1 : Vector3, v2 : Vector3) -> Float {
  v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
}

///|
/// Calculate distance between two vectors.
pub fn Vector3::distance(v1 : Vector3, v2 : Vector3) -> Float {
  let dx = v2.x - v1.x
  let dy = v2.y - v1.y
  let dz = v2.z - v1.z
  sqrtf(dx * dx + dy * dy + dz * dz)
}

///|
/// Calculate square distance between two vectors.
pub fn Vector3::distance_sqr(v1 : Vector3, v2 : Vector3) -> Float {
  let dx = v2.x - v1.x
  let dy = v2.y - v1.y
  let dz = v2.z - v1.z
  dx * dx + dy * dy + dz * dz
}

///|
/// Calculate angle between two vectors.
pub fn Vector3::angle(v1 : Vector3, v2 : Vector3) -> Float {
  let cross_x = v1.y * v2.z - v1.z * v2.y
  let cross_y = v1.z * v2.x - v1.x * v2.z
  let cross_z = v1.x * v2.y - v1.y * v2.x
  let len = sqrtf(cross_x * cross_x + cross_y * cross_y + cross_z * cross_z)
  let dot = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
  atan2f(len, dot)
}

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

///|
/// Calculate the projection of the vector v1 on to v2.
pub fn Vector3::project(v1 : Vector3, v2 : Vector3) -> Vector3 {
  let v1dv2 = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
  let v2dv2 = v2.x * v2.x + v2.y * v2.y + v2.z * v2.z
  let mag = v1dv2 / v2dv2
  { x: v2.x * mag, y: v2.y * mag, z: v2.z * mag }
}

///|
/// Calculate the rejection of the vector v1 on to v2.
pub fn Vector3::reject(v1 : Vector3, v2 : Vector3) -> Vector3 {
  let v1dv2 = v1.x * v2.x + v1.y * v2.y + v1.z * v2.z
  let v2dv2 = v2.x * v2.x + v2.y * v2.y + v2.z * v2.z
  let mag = v1dv2 / v2dv2
  { x: v1.x - v2.x * mag, y: v1.y - v2.y * mag, z: v1.z - v2.z * mag }
}

///|
/// Calculate linear interpolation between two vectors.
pub fn Vector3::lerp(v1 : Vector3, v2 : Vector3, amount : Float) -> Vector3 {
  {
    x: v1.x + amount * (v2.x - v1.x),
    y: v1.y + amount * (v2.y - v1.y),
    z: v1.z + amount * (v2.z - v1.z),
  }
}

///|
/// Calculate reflected vector to normal.
pub fn Vector3::reflect(v : Vector3, normal : Vector3) -> Vector3 {
  let dot = v.x * normal.x + v.y * normal.y + v.z * normal.z
  {
    x: v.x - (2.0 : Float) * normal.x * dot,
    y: v.y - (2.0 : Float) * normal.y * dot,
    z: v.z - (2.0 : Float) * normal.z * dot,
  }
}

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

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

///|
/// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c).
pub fn Vector3::barycenter(
  p : Vector3,
  a : Vector3,
  b : Vector3,
  c : Vector3,
) -> Vector3 {
  let v0 : Vector3 = { x: b.x - a.x, y: b.y - a.y, z: b.z - a.z }
  let v1 : Vector3 = { x: c.x - a.x, y: c.y - a.y, z: c.z - a.z }
  let v2 : Vector3 = { x: p.x - a.x, y: p.y - a.y, z: p.z - a.z }
  let d00 = v0.x * v0.x + v0.y * v0.y + v0.z * v0.z
  let d01 = v0.x * v1.x + v0.y * v1.y + v0.z * v1.z
  let d11 = v1.x * v1.x + v1.y * v1.y + v1.z * v1.z
  let d20 = v2.x * v0.x + v2.y * v0.y + v2.z * v0.z
  let d21 = v2.x * v1.x + v2.y * v1.y + v2.z * v1.z
  let denom = d00 * d11 - d01 * d01
  let ry = (d11 * d20 - d01 * d21) / denom
  let rz = (d00 * d21 - d01 * d20) / denom
  let rx : Float = (1.0 : Float) - (rz + ry)
  { x: rx, y: ry, z: rz }
}

///|
/// Projects a Vector3 from screen space into object space.
pub fn Vector3::unproject(
  source : Vector3,
  projection : Matrix,
  view : Matrix,
) -> Vector3 {
  // Calculate unprojected matrix (multiply view matrix by projection matrix) and invert it
  let mat_view_proj : Matrix = {
    m0: view.m0 * projection.m0 +
    view.m1 * projection.m4 +
    view.m2 * projection.m8 +
    view.m3 * projection.m12,
    m1: view.m0 * projection.m1 +
    view.m1 * projection.m5 +
    view.m2 * projection.m9 +
    view.m3 * projection.m13,
    m2: view.m0 * projection.m2 +
    view.m1 * projection.m6 +
    view.m2 * projection.m10 +
    view.m3 * projection.m14,
    m3: view.m0 * projection.m3 +
    view.m1 * projection.m7 +
    view.m2 * projection.m11 +
    view.m3 * projection.m15,
    m4: view.m4 * projection.m0 +
    view.m5 * projection.m4 +
    view.m6 * projection.m8 +
    view.m7 * projection.m12,
    m5: view.m4 * projection.m1 +
    view.m5 * projection.m5 +
    view.m6 * projection.m9 +
    view.m7 * projection.m13,
    m6: view.m4 * projection.m2 +
    view.m5 * projection.m6 +
    view.m6 * projection.m10 +
    view.m7 * projection.m14,
    m7: view.m4 * projection.m3 +
    view.m5 * projection.m7 +
    view.m6 * projection.m11 +
    view.m7 * projection.m15,
    m8: view.m8 * projection.m0 +
    view.m9 * projection.m4 +
    view.m10 * projection.m8 +
    view.m11 * projection.m12,
    m9: view.m8 * projection.m1 +
    view.m9 * projection.m5 +
    view.m10 * projection.m9 +
    view.m11 * projection.m13,
    m10: view.m8 * projection.m2 +
    view.m9 * projection.m6 +
    view.m10 * projection.m10 +
    view.m11 * projection.m14,
    m11: view.m8 * projection.m3 +
    view.m9 * projection.m7 +
    view.m10 * projection.m11 +
    view.m11 * projection.m15,
    m12: view.m12 * projection.m0 +
    view.m13 * projection.m4 +
    view.m14 * projection.m8 +
    view.m15 * projection.m12,
    m13: view.m12 * projection.m1 +
    view.m13 * projection.m5 +
    view.m14 * projection.m9 +
    view.m15 * projection.m13,
    m14: view.m12 * projection.m2 +
    view.m13 * projection.m6 +
    view.m14 * projection.m10 +
    view.m15 * projection.m14,
    m15: view.m12 * projection.m3 +
    view.m13 * projection.m7 +
    view.m14 * projection.m11 +
    view.m15 * projection.m15,
  }
  // Invert the view-projection matrix
  let inv = matrix_invert(mat_view_proj)
  // Create quaternion from source point and transform by inverted matrix
  let qx = inv.m0 * source.x + inv.m4 * source.y + inv.m8 * source.z + inv.m12
  let qy = inv.m1 * source.x + inv.m5 * source.y + inv.m9 * source.z + inv.m13
  let qz = inv.m2 * source.x + inv.m6 * source.y + inv.m10 * source.z + inv.m14
  let qw = inv.m3 * source.x + inv.m7 * source.y + inv.m11 * source.z + inv.m15
  // Normalized world points
  { x: qx / qw, y: qy / qw, z: qz / qw }
}

///|
/// Orthonormalize provided vectors. Makes vectors normalized and orthogonal to each other.
pub fn Vector3::ortho_normalize(
  v1 : Vector3,
  v2 : Vector3,
) -> (Vector3, Vector3) {
  // Normalize v1
  let mut length = sqrtf(v1.x * v1.x + v1.y * v1.y + v1.z * v1.z)
  if length == 0.0 {
    length = 1.0
  }
  let ilength : Float = (1.0 : Float) / length
  let nv1 : Vector3 = {
    x: v1.x * ilength,
    y: v1.y * ilength,
    z: v1.z * ilength,
  }
  // Cross product of nv1 and v2
  let vn1_x = nv1.y * v2.z - nv1.z * v2.y
  let vn1_y = nv1.z * v2.x - nv1.x * v2.z
  let vn1_z = nv1.x * v2.y - nv1.y * v2.x
  // Normalize vn1
  let mut length2 = sqrtf(vn1_x * vn1_x + vn1_y * vn1_y + vn1_z * vn1_z)
  if length2 == 0.0 {
    length2 = 1.0
  }
  let ilength2 : Float = (1.0 : Float) / length2
  let nvn1_x = vn1_x * ilength2
  let nvn1_y = vn1_y * ilength2
  let nvn1_z = vn1_z * ilength2
  // Cross product of nvn1 and nv1
  let nv2 : Vector3 = {
    x: nvn1_y * nv1.z - nvn1_z * nv1.y,
    y: nvn1_z * nv1.x - nvn1_x * nv1.z,
    z: nvn1_x * nv1.y - nvn1_y * nv1.x,
  }
  (nv1, nv2)
}

///|
/// Transforms a Vector3 by a given Matrix.
pub fn Vector3::transform(v : Vector3, mat : Matrix) -> Vector3 {
  let x = v.x
  let y = v.y
  let z = v.z
  {
    x: mat.m0 * x + mat.m4 * y + mat.m8 * z + mat.m12,
    y: mat.m1 * x + mat.m5 * y + mat.m9 * z + mat.m13,
    z: mat.m2 * x + mat.m6 * y + mat.m10 * z + mat.m14,
  }
}

///|
/// Transform a vector by quaternion rotation.
pub fn Vector3::rotate_by_quaternion(v : Vector3, q : Vector4) -> Vector3 {
  {
    x: v.x * (q.x * q.x + q.w * q.w - q.y * q.y - q.z * q.z) +
    v.y * ((2.0 : Float) * q.x * q.y - (2.0 : Float) * q.w * q.z) +
    v.z * ((2.0 : Float) * q.x * q.z + (2.0 : Float) * q.w * q.y),
    y: v.x * ((2.0 : Float) * q.w * q.z + (2.0 : Float) * q.x * q.y) +
    v.y * (q.w * q.w - q.x * q.x + q.y * q.y - q.z * q.z) +
    v.z * (-(2.0 : Float) * q.w * q.x + (2.0 : Float) * q.y * q.z),
    z: v.x * (-(2.0 : Float) * q.w * q.y + (2.0 : Float) * q.x * q.z) +
    v.y * ((2.0 : Float) * q.w * q.x + (2.0 : Float) * q.y * q.z) +
    v.z * (q.w * q.w - q.x * q.x - q.y * q.y + q.z * q.z),
  }
}

///|
/// Rotates a vector around an axis.
pub fn Vector3::rotate_by_axis_angle(
  v : Vector3,
  axis : Vector3,
  angle : Float,
) -> Vector3 {
  // Normalize axis
  let mut length = sqrtf(axis.x * axis.x + axis.y * axis.y + axis.z * axis.z)
  if length == 0.0 {
    length = 1.0
  }
  let ilength : Float = (1.0 : Float) / length
  let ax = axis.x * ilength
  let ay = axis.y * ilength
  let az = axis.z * ilength
  let half_angle = angle / (2.0 : Float)
  let a_sin = sinf(half_angle)
  let b = ax * a_sin
  let c = ay * a_sin
  let d = az * a_sin
  let a_cos = cosf(half_angle)
  let w : Vector3 = { x: b, y: c, z: d }
  // Cross product w x v
  let wv_x = w.y * v.z - w.z * v.y
  let wv_y = w.z * v.x - w.x * v.z
  let wv_z = w.x * v.y - w.y * v.x
  // Cross product w x wv
  let wwv_x = w.y * wv_z - w.z * wv_y
  let wwv_y = w.z * wv_x - w.x * wv_z
  let wwv_z = w.x * wv_y - w.y * wv_x
  // Scale wv by 2*a, scale wwv by 2
  let sa = a_cos * (2.0 : Float)
  {
    x: v.x + wv_x * sa + wwv_x * (2.0 : Float),
    y: v.y + wv_y * sa + wwv_y * (2.0 : Float),
    z: v.z + wv_z * sa + wwv_z * (2.0 : Float),
  }
}

///|
/// Move vector towards target.
pub fn Vector3::move_towards(
  v : Vector3,
  target : Vector3,
  max_distance : Float,
) -> Vector3 {
  let dx = target.x - v.x
  let dy = target.y - v.y
  let dz = target.z - v.z
  let value = dx * dx + dy * dy + dz * dz
  if value == 0.0 ||
    (max_distance >= 0.0 && value <= max_distance * max_distance) {
    return target
  }
  let dist = sqrtf(value)
  {
    x: v.x + dx / dist * max_distance,
    y: v.y + dy / dist * max_distance,
    z: v.z + dz / dist * max_distance,
  }
}

///|
/// Invert the given vector.
pub fn Vector3::invert(v : Vector3) -> Vector3 {
  { x: (1.0 : Float) / v.x, y: (1.0 : Float) / v.y, z: (1.0 : Float) / v.z }
}

///|
/// Clamp vector between min and max vectors.
pub fn Vector3::clamp(v : Vector3, min : Vector3, max : Vector3) -> Vector3 {
  {
    x: fminf(max.x, fmaxf(min.x, v.x)),
    y: fminf(max.y, fmaxf(min.y, v.y)),
    z: fminf(max.z, fmaxf(min.z, v.z)),
  }
}

///|
/// Check whether two given vectors are almost equal.
pub fn Vector3::equals(p : Vector3, q : Vector3) -> 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)))
}

///|
/// Compute the direction of a refracted ray.
pub fn Vector3::refract(v : Vector3, n : Vector3, r : Float) -> Vector3 {
  let dot = v.x * n.x + v.y * n.y + v.z * n.z
  let d : Float = (1.0 : Float) - r * r * ((1.0 : Float) - dot * dot)
  if d >= 0.0 {
    let d_sqrt = sqrtf(d)
    {
      x: r * v.x - (r * dot + d_sqrt) * n.x,
      y: r * v.y - (r * dot + d_sqrt) * n.y,
      z: r * v.z - (r * dot + d_sqrt) * n.z,
    }
  } else {
    { x: 0.0, y: 0.0, z: 0.0 }
  }
}