/**
 * Plain-object math types.
 *
 * box3d-wasm passes vectors as `{x,y,z}` and quaternions as `{x,y,z,w}`, which
 * is also what three.js `.set()` / `.copy()` accept, so these cross the physics
 * boundary with no marshalling.
 */

export interface Vec3 {
  x: number;
  y: number;
  z: number;
}

export interface Quat {
  x: number;
  y: number;
  z: number;
  w: number;
}

export interface Transform {
  position: Vec3;
  rotation: Quat;
}

export const VEC3_ZERO: Readonly<Vec3> = Object.freeze({ x: 0, y: 0, z: 0 });
export const VEC3_UP: Readonly<Vec3> = Object.freeze({ x: 0, y: 1, z: 0 });
export const QUAT_IDENTITY: Readonly<Quat> = Object.freeze({ x: 0, y: 0, z: 0, w: 1 });

export const vec3 = (x = 0, y = 0, z = 0): Vec3 => ({ x, y, z });

export const addVec3 = (a: Vec3, b: Vec3): Vec3 => ({
  x: a.x + b.x,
  y: a.y + b.y,
  z: a.z + b.z,
});

export const subVec3 = (a: Vec3, b: Vec3): Vec3 => ({
  x: a.x - b.x,
  y: a.y - b.y,
  z: a.z - b.z,
});

export const scaleVec3 = (v: Vec3, s: number): Vec3 => ({
  x: v.x * s,
  y: v.y * s,
  z: v.z * s,
});

export const dotVec3 = (a: Vec3, b: Vec3): number => a.x * b.x + a.y * b.y + a.z * b.z;

export const lengthVec3 = (v: Vec3): number => Math.sqrt(dotVec3(v, v));

export function normaliseVec3(v: Vec3): Vec3 {
  const len = lengthVec3(v);
  return len > 1e-8 ? scaleVec3(v, 1 / len) : { x: 0, y: 0, z: 0 };
}

/** Yaw-only quaternion — the only rotation a tank-controlled character needs. */
export function quatFromYaw(yaw: number): Quat {
  const half = yaw * 0.5;
  return { x: 0, y: Math.sin(half), z: 0, w: Math.cos(half) };
}

export function yawFromQuat(q: Quat): number {
  return Math.atan2(2 * (q.w * q.y + q.x * q.z), 1 - 2 * (q.y * q.y + q.z * q.z));
}

/** Shortest signed angular delta from `a` to `b`, in (-PI, PI]. */
export function angleDelta(a: number, b: number): number {
  let d = (b - a) % (Math.PI * 2);
  if (d > Math.PI) d -= Math.PI * 2;
  if (d < -Math.PI) d += Math.PI * 2;
  return d;
}

export const lerp = (a: number, b: number, t: number): number => a + (b - a) * t;

export const clamp = (v: number, min: number, max: number): number =>
  v < min ? min : v > max ? max : v;

/**
 * Frame-rate independent exponential smoothing.
 * `smoothing` is the fraction of remaining distance left after one second.
 */
export const damp = (a: number, b: number, smoothing: number, dt: number): number =>
  lerp(a, b, 1 - Math.pow(smoothing, dt));
