/**
 * CPU-side cloth physics mirroring the WGSL compute shaders.
 * All data uses the same Float32Array vec4 layout: [x, y, z, invMass] per vertex.
 */

export interface ClothPhysicsParams {
  dt: number;
  gravityY: number;
  compliance: number;
  numVertices: number;
  windX: number;
  windY: number;
  windZ: number;
  time: number;
  elongation: number;
}

/**
 * Mirrors CLOTH_INTEGRATE_COMPUTE_WGSL:
 * Applies gravity and wind forces, predicts new positions.
 */
export function integratePositions(
  positions: Float32Array,
  prevPositions: Float32Array,
  velocities: Float32Array,
  params: ClothPhysicsParams
): void {
  for (let idx = 0; idx < params.numVertices; idx++) {
    const base = idx * 4;
    const invMass = positions[base + 3];

    if (invMass <= 0.0) {
      continue;
    }

    velocities[base + 1] += params.gravityY * params.dt;

    const posX = positions[base + 0];
    const posY = positions[base + 1];

    const flutter = Math.sin(posX * 2.0 + params.time * 5.0) * Math.cos(posY * 2.0 + params.time * 3.0);

    const windForceX = params.windX + (flutter * params.windX * 0.8);
    const windForceY = params.windY + (flutter * 2.0);
    const windForceZ = params.windZ + (flutter * params.windZ * 0.8);

    velocities[base + 0] += windForceX * params.dt;
    velocities[base + 1] += windForceY * params.dt;
    velocities[base + 2] += windForceZ * params.dt;

    prevPositions[base + 0] = positions[base + 0];
    prevPositions[base + 1] = positions[base + 1];
    prevPositions[base + 2] = positions[base + 2];
    prevPositions[base + 3] = positions[base + 3];

    positions[base + 0] += velocities[base + 0] * params.dt;
    positions[base + 1] += velocities[base + 1] * params.dt;
    positions[base + 2] += velocities[base + 2] * params.dt;
  }
}

/**
 * Mirrors CLOTH_SOLVE_COMPUTE_WGSL:
 * XPBD constraint solving for one phase of constraints.
 * Each constraint is stored as [idA, idB, restLength, type] (4 floats).
 */
export function solveConstraints(
  positions: Float32Array,
  constraints: Float32Array,
  params: ClothPhysicsParams
): void {
  const numConstraints = constraints.length / 4;

  for (let idx = 0; idx < numConstraints; idx++) {
    const cBase = idx * 4;
    const constraintType = constraints[cBase + 3];

    if (constraintType < 0.5) {
      continue;
    }

    const idA = constraints[cBase + 0];
    const idB = constraints[cBase + 1];
    const restLength = constraints[cBase + 2] * params.elongation;

    const baseA = idA * 4;
    const baseB = idB * 4;

    const wA = positions[baseA + 3];
    const wB = positions[baseB + 3];
    const wSum = wA + wB;

    if (wSum <= 0.0) {
      continue;
    }

    const dirX = positions[baseA + 0] - positions[baseB + 0];
    const dirY = positions[baseA + 1] - positions[baseB + 1];
    const dirZ = positions[baseA + 2] - positions[baseB + 2];
    const dist = Math.sqrt(dirX * dirX + dirY * dirY + dirZ * dirZ);

    if (dist < 0.0001) {
      continue;
    }

    const nX = dirX / dist;
    const nY = dirY / dist;
    const nZ = dirZ / dist;
    const c = dist - restLength;

    const alpha = params.compliance / (params.dt * params.dt);
    const lambda = -c / (wSum + alpha);

    if (wA > 0.0) {
      positions[baseA + 0] += nX * (lambda * wA);
      positions[baseA + 1] += nY * (lambda * wA);
      positions[baseA + 2] += nZ * (lambda * wA);
    }
    if (wB > 0.0) {
      positions[baseB + 0] += nX * (-lambda * wB);
      positions[baseB + 1] += nY * (-lambda * wB);
      positions[baseB + 2] += nZ * (-lambda * wB);
    }
  }
}

/**
 * Mirrors CLOTH_VELOCITY_COMPUTE_WGSL:
 * Derives velocity from position changes: v = (pos - prevPos) / dt
 */
export function updateVelocities(
  positions: Float32Array,
  prevPositions: Float32Array,
  velocities: Float32Array,
  params: ClothPhysicsParams
): void {
  for (let idx = 0; idx < params.numVertices; idx++) {
    const base = idx * 4;
    const invMass = positions[base + 3];

    if (invMass <= 0.0) {
      continue;
    }

    velocities[base + 0] = (positions[base + 0] - prevPositions[base + 0]) / params.dt;
    velocities[base + 1] = (positions[base + 1] - prevPositions[base + 1]) / params.dt;
    velocities[base + 2] = (positions[base + 2] - prevPositions[base + 2]) / params.dt;
  }
}
