// combat/pickups.js (Lane B) — the five pickup kinds C places in L2.
//
// Split of concerns (settled with C): C owns WHERE and HOW MANY (level JSON, and the design
// reasons are worth reading — "orb lines teach the racing line before they score anything",
// "the game pays you right before it charges you"). B owns what each one is WORTH
// (balance.js §pickups) and what touching it does. Placement law: `rho` is a FRACTION of the
// safe radius, exactly as for enemies — C pins sample 1 at rho 0.92 meaning "against the
// wall", and that has to mean the same thing in a 9-unit tube and a 6.8-unit one.
//
// ART_BIBLE: pickups are soft green, all five of them — the emissive code is a readability
// LAW keyed to meaning ("this is good for you"), not a palette to decorate with. So identity
// comes from SHAPE instead: orb = sphere, mucin = blob, B12 = octahedron, antacid = capsule,
// biopsy = the big slow diamond you can spot from 200 units away. One InstancedMesh per kind:
// 5 draws for the whole economy.

import * as THREE from 'three';
import { BALANCE as B } from './balance.js';
import { emissiveMat, EMISSIVE } from '../flight/emissive.js';

const _m = new THREE.Matrix4(), _q = new THREE.Quaternion(), _scale = new THREE.Vector3();
const _v = new THREE.Vector3(), _axis = new THREE.Vector3(0.3, 1, 0.2).normalize();

const KINDS = {
  nutrient_orb:  { geo: () => new THREE.SphereGeometry(0.8, 10, 8), pool: 48, scale: 1 },
  mucin_glob:    { geo: () => new THREE.IcosahedronGeometry(1.0, 0), pool: 16, scale: 1 },
  b12_cell:      { geo: () => new THREE.OctahedronGeometry(1.0, 0), pool: 8, scale: 1 },
  antacid_ammo:  { geo: () => new THREE.CapsuleGeometry(0.45, 0.9, 3, 8), pool: 12, scale: 1 },
  biopsy_sample: { geo: () => new THREE.OctahedronGeometry(1.6, 0), pool: 6, scale: 1.15 },
};

export function createPickups({ scene, world, bus, rng, player, weapons }) {
  const pools = {};
  for (const [kind, def] of Object.entries(KINDS)) {
    const geo = def.geo();
    const mat = emissiveMat(EMISSIVE.pickup, { additive: kind === 'biopsy_sample' });
    const mesh = new THREE.InstancedMesh(geo, mat, def.pool);
    mesh.frustumCulled = false;
    mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
    mesh.count = 0;
    scene.add(mesh);
    pools[kind] = { mesh, geo, mat, def, items: [] };
  }

  let time = 0;
  const discToWorld = (out, frame, x, y) =>
    out.copy(frame.pos).addScaledVector(frame.nor, y).addScaledVector(frame.bin, x);

  function spawn(kind, { s = 0, theta = null, rho = null } = {}) {
    const pool = pools[kind];
    if (!pool) { console.warn(`[combat] unknown pickup kind "${kind}"`); return null; }
    if (pool.items.length >= pool.def.pool) return null;

    s = THREE.MathUtils.clamp(s, 0, world.length);
    const th = theta ?? rng('pickups')() * Math.PI * 2;
    const free = Math.max(0, world.wallRho(s, th) - B.pickups.radius);
    // rho is C's fraction of the safe radius. Unauthored => near the racing line, because an
    // unplaced pickup is a reward, not a puzzle.
    const r = rho != null ? THREE.MathUtils.clamp(rho, 0, 1) * free : free * 0.3;

    const p = { kind, s, x: Math.sin(th) * r, y: Math.cos(th) * r, pos: new THREE.Vector3(), alive: true };
    pool.items.push(p);
    return p;
  }

  // Applies the effect, then announces it. E renders the feedback off `pickup {kind}`.
  function collect(p) {
    p.alive = false;
    const v = B.pickups[p.kind] ?? {};
    if (v.coat || v.hull || v.boost) player.refill({ coat: v.coat ?? 0, hull: v.hull ?? 0, boost: v.boost ?? 0 });
    if (v.ammo) weapons?.addAmmo(v.ammo);
    bus.emit('pickup', { kind: p.kind, s: p.s, score: v.score ?? 0, sample: v.sample ?? 0 });
    bus.emit('audio:cue', { name: p.kind === 'biopsy_sample' ? 'pickup_sample' : 'pickup' });
  }

  function update(dt) {
    time += dt;
    const ps = player.state;
    const reach = B.pickups.radius + player.radius;
    const reach2 = reach * reach;

    for (const pool of Object.values(pools)) {
      let n = 0;
      for (let i = pool.items.length - 1; i >= 0; i--) {
        const p = pool.items[i];
        if (!p.alive) { pool.items.splice(i, 1); continue; }
        discToWorld(p.pos, world.sample(p.s), p.x, p.y);
        // cheap reject along s before the distance test — pickups outlive the player's window
        if (ps.alive && Math.abs(p.s - ps.s) < reach + 2) {
          const dx = p.x - ps.x, dy = p.y - ps.y, ds = p.s - ps.s;
          if (dx * dx + dy * dy + ds * ds <= reach2) { collect(p); pool.items.splice(i, 1); continue; }
        }
      }
      for (const p of pool.items) {
        if (n >= pool.def.pool) break;
        _q.setFromAxisAngle(_axis, time * B.pickups.spin);
        pool.mesh.setMatrixAt(n++, _m.compose(p.pos, _q, _scale.setScalar(pool.def.scale)));
      }
      pool.mesh.count = n;
      pool.mesh.instanceMatrix.needsUpdate = true;
    }
  }

  function clear() { for (const pool of Object.values(pools)) pool.items.length = 0; }

  return {
    spawn, update, clear,
    get count() { return Object.values(pools).reduce((a, p) => a + p.items.length, 0); },
    dispose() {
      for (const pool of Object.values(pools)) {
        scene.remove(pool.mesh); pool.mesh.dispose(); pool.geo.dispose(); pool.mat.dispose();
      }
    },
  };
}
