/**
 * Shared helpers for THREE.InstancedMesh usage across bar and wave-sphere diagrams.
 */

let _mat4, _pos, _quat, _scl, _euler, _col;

function _ensureTmp(THREE) {
  if (!_mat4) {
    _mat4 = new THREE.Matrix4();
    _pos = new THREE.Vector3();
    _quat = new THREE.Quaternion();
    _scl = new THREE.Vector3();
    _euler = new THREE.Euler();
    _col = new THREE.Color();
  }
}

function _ensureCol(instMesh) {
  if (!_col) {
    const C = instMesh.material?.color?.constructor;
    _col = C ? new C() : { r: 0, g: 0, b: 0, set(v) { const c = typeof v === 'number' ? v : 0; this.r = ((c >> 16) & 0xff) / 255; this.g = ((c >> 8) & 0xff) / 255; this.b = (c & 0xff) / 255; return this; }, lerp() { return this; } };
  }
}

/**
 * Patch a MeshPhongMaterial so that emissive light follows the per-instance color (vColor).
 * Without this, Three.js only multiplies diffuseColor by instanceColor; emissive stays uniform.
 */
export function patchEmissiveForInstancing(material) {
  const prev = material.onBeforeCompile;
  material.onBeforeCompile = (shader, renderer) => {
    if (prev) prev(shader, renderer);
    shader.fragmentShader = shader.fragmentShader.replace(
      'vec3 totalEmissiveRadiance = emissive;',
      'vec3 totalEmissiveRadiance = emissive * vColor.rgb;',
    );
  };
}

/**
 * Lerp per-instance colors toward their targets, uploading only when something changed.
 * @param {THREE.InstancedMesh} instMesh
 * @param {Array<{currentColor: THREE.Color, targetColor: number|string}>} instances
 * @param {number} speed  lerp factor (0-1 per frame, already dt-scaled)
 * @param {number} threshold  color-settle threshold
 * @returns {boolean} true if any color still settling (caller should mark dirty)
 */
export function lerpInstanceColors(instMesh, instances, speed, threshold) {
  _ensureCol(instMesh);
  let dirty = false;
  for (let i = 0; i < instances.length; i++) {
    const inst = instances[i];
    if (inst.targetColor === undefined) continue;
    _col.set(inst.targetColor);
    inst.currentColor.lerp(_col, speed);
    instMesh.setColorAt(i, inst.currentColor);
    const diff = Math.abs(inst.currentColor.r - _col.r) + Math.abs(inst.currentColor.g - _col.g) + Math.abs(inst.currentColor.b - _col.b);
    if (diff > threshold) dirty = true;
  }
  if (dirty && instMesh.instanceColor) instMesh.instanceColor.needsUpdate = true;
  return dirty;
}

/**
 * Set an instance matrix from position + euler rotation + scale.
 */
export function setInstanceTransform(THREE, instMesh, index, px, py, pz, ry, rz, sx, sy, sz) {
  _ensureTmp(THREE);
  _pos.set(px, py, pz);
  _euler.set(0, ry, rz);
  _quat.setFromEuler(_euler);
  _scl.set(sx, sy, sz);
  _mat4.compose(_pos, _quat, _scl);
  instMesh.setMatrixAt(index, _mat4);
}

/**
 * Set an instance matrix from position + uniform scale (no rotation).
 */
export function setInstancePosScale(THREE, instMesh, index, px, py, pz, s) {
  _ensureTmp(THREE);
  _pos.set(px, py, pz);
  _scl.set(s, s, s);
  _mat4.compose(_pos, _quat.identity(), _scl);
  instMesh.setMatrixAt(index, _mat4);
}
