// Single-shot ChatGPT; absurd this works out of the box. AI will eat us all.

// Prompt:
//
// I am making a script for kicad that uses the cli that will
// render a fly around the board. I want to give an initial
// rotation of the board, then from the board's perspective
// rotate it about its y-axis. Should I use quaternions and
// can you provide code that would create the series of x, y,
// z rotation values I need?
//
// Note: I had to further specify JavaScript (it generated Python)
//       and a handful of types were added to make it TypeScript.

// ==============================
// Quaternion utilities
// Stored as [w, x, y, z]
// ==============================

type Vector3 = [ number, number, number ];
type Vector4 = [ number, number, number, number ];

export type XYZ = { x: number, y: number, z: number };

function quatMul(a: Vector4, b: Vector4): Vector4 {
  const [aw, ax, ay, az] = a;
  const [bw, bx, by, bz] = b;

  return [
    aw * bw - ax * bx - ay * by - az * bz,
    aw * bx + ax * bw + ay * bz - az * by,
    aw * by - ax * bz + ay * bw + az * bx,
    aw * bz + ax * by - ay * bx + az * bw,
  ];
}

function quatNormalize(q: Vector4): Vector4 {
  const [w, x, y, z] = q;
  const n = Math.hypot(w, x, y, z);
  return [w / n, x / n, y / n, z / n];
}

function quatFromAxisAngle(axis: Vector3, angleRad: number): Vector4 {
  const [ax, ay, az] = axis;
  const half = angleRad * 0.5;
  const s = Math.sin(half);
  return quatNormalize([
    Math.cos(half),
    ax * s,
    ay * s,
    az * s,
  ]);
}

// Intrinsic XYZ (local X, then Y, then Z)
function quatFromEulerXYZ(x: number, y: number, z: number) {
  const qx = quatFromAxisAngle([1, 0, 0], x);
  const qy = quatFromAxisAngle([0, 1, 0], y);
  const qz = quatFromAxisAngle([0, 0, 1], z);
  return quatNormalize(quatMul(quatMul(qx, qy), qz));
}

function quatToEulerXYZ(q: Vector4): Vector3 {
  const [w, x, y, z] = q;

  // Rotation matrix elements
  const r00 = 1 - 2 * (y * y + z * z);
  const r01 = 2 * (x * y - w * z);
  const r02 = 2 * (x * z + w * y);

  const r12 = 2 * (y * z - w * x);
  const r22 = 1 - 2 * (x * x + y * y);

  // Clamp for numerical safety
  const clamped = Math.max(-1, Math.min(1, r02));

  const yAngle = Math.asin(clamped);
  const xAngle = Math.atan2(-r12, r22);
  const zAngle = Math.atan2(-r01, r00);

  return [xAngle, yAngle, zAngle];
}

// ==============================
// Main generator
// ==============================

export function generateSpinFrames(
  rot: XYZ,   // [x, y, z] degrees
  totalSpinDeg: number,      // degrees
  frames: number,            // number of frames
) {
  const deg2rad = (d: number) => d * Math.PI / 180;
  const rad2deg = (r: number) => r * 180 / Math.PI;

  const [x0, y0, z0] = [ rot.x, rot.y, rot.z ].map(deg2rad);
  const q0 = quatFromEulerXYZ(x0, y0, z0);

  const result: Array<XYZ> = [];

  for (let i = 0; i < frames; i++) {
    const t = frames > 1 ? i / (frames - 1) : 0;
    const spinRad = deg2rad(totalSpinDeg * t);

    // Local Y rotation (post-multiply)
    const qSpinY = quatFromAxisAngle([0, 1, 0], spinRad);
    const q = quatNormalize(quatMul(q0, qSpinY));

    const [xr, yr, zr] = quatToEulerXYZ(q);
    result.push({
      x: rad2deg(xr),
      y: rad2deg(yr),
      z: rad2deg(zr),
    });
  }

  return result;
}

// ==============================
// Example usage
// ==============================
/*
const frames = generateLocalYSpinFrames({
  initialEulerDeg: [20, 30, 0], // initial board orientation
  totalSpinDeg: 360,            // full local-Y spin
  frames: 120,
});

// Print first few frames
for (let i = 0; i < 5; i++) {
  const [x, y, z] = frames[i];
  console.log(
    i,
    x.toFixed(2),
    y.toFixed(2),
    z.toFixed(2)
  );
}
*/
