import * as THREE from 'three';

// ---------- AR nameplate ----------
function roundRect(ctx, x, y, w, h, r) {
  ctx.beginPath();
  ctx.moveTo(x + r, y); ctx.arcTo(x + w, y, x + w, y + h, r); ctx.arcTo(x + w, y + h, x, y + h, r);
  ctx.arcTo(x, y + h, x, y, r); ctx.arcTo(x, y, x + w, y, r); ctx.closePath();
}
function nameplate(target, firstScan, accent) {
  const W = 340, H = 168, c = document.createElement('canvas');
  c.width = W; c.height = H;
  const ctx = c.getContext('2d');
  const a = '#' + accent.getHexString();
  const mineral = target.type === 'mineral';
  const px = 16, py = 14, pw = W - 32, ph = 110;
  // panel
  ctx.fillStyle = 'rgba(6,18,26,0.62)';
  roundRect(ctx, px, py, pw, ph, 8); ctx.fill();
  ctx.strokeStyle = a; ctx.lineWidth = 2.5; ctx.shadowColor = a; ctx.shadowBlur = 14;
  roundRect(ctx, px, py, pw, ph, 8); ctx.stroke();
  ctx.shadowBlur = 0;
  // accent tab
  ctx.fillStyle = a; ctx.fillRect(px, py + 16, 5, ph - 32);
  // connector stem to target
  ctx.strokeStyle = a; ctx.lineWidth = 2.5;
  ctx.beginPath(); ctx.moveTo(W / 2, py + ph); ctx.lineTo(W / 2, H - 6); ctx.stroke();
  ctx.fillStyle = a; ctx.beginPath(); ctx.arc(W / 2, H - 6, 5, 0, 7); ctx.fill();
  // text
  ctx.textAlign = 'left';
  ctx.fillStyle = a; ctx.font = '700 17px Orbitron, system-ui, sans-serif';
  ctx.fillText(mineral ? '◆ MINERAL' : '✦ FLORA', px + 22, py + 32);
  ctx.fillStyle = '#eafffe'; ctx.font = '700 30px Rajdhani, system-ui, sans-serif';
  const name = target.name.length > 18 ? target.name.slice(0, 17) + '…' : target.name;
  ctx.fillText(name, px + 22, py + 66);
  ctx.fillStyle = firstScan ? '#ffd166' : 'rgba(216,251,255,0.6)';
  ctx.font = '600 19px Rajdhani, system-ui, sans-serif';
  ctx.fillText(firstScan ? '+ NEW DISCOVERY' : 'CATALOGUED', px + 22, py + 96);

  const tex = new THREE.CanvasTexture(c); tex.colorSpace = THREE.SRGBColorSpace;
  const spr = new THREE.Sprite(new THREE.SpriteMaterial({ map: tex, transparent: true, depthTest: false, depthWrite: false, fog: false }));
  spr.scale.set(9.5, 4.7, 1);
  spr.renderOrder = 999;
  return spr;
}

// ---------- generic particle burst pool ----------
function burstTex() {
  const c = document.createElement('canvas'); c.width = c.height = 32;
  const x = c.getContext('2d');
  const g = x.createRadialGradient(16, 16, 0, 16, 16, 16);
  g.addColorStop(0, '#fff'); g.addColorStop(0.4, 'rgba(255,255,255,0.6)'); g.addColorStop(1, 'rgba(255,255,255,0)');
  x.fillStyle = g; x.fillRect(0, 0, 32, 32);
  const t = new THREE.CanvasTexture(c); return t;
}

export class Bursts {
  constructor(scene) {
    this.scene = scene;
    this.tex = burstTex();
    this.active = [];
  }
  spawn(pos, color, n = 18, spread = 5, life = 0.7, size = 0.5) {
    const p = new Float32Array(n * 3), v = new Float32Array(n * 3);
    for (let i = 0; i < n; i++) {
      p[i * 3] = pos.x; p[i * 3 + 1] = pos.y; p[i * 3 + 2] = pos.z;
      const d = new THREE.Vector3().randomDirection().multiplyScalar(spread * (0.4 + Math.random()));
      d.y = Math.abs(d.y) * 0.8 + 1.5;
      v[i * 3] = d.x; v[i * 3 + 1] = d.y; v[i * 3 + 2] = d.z;
    }
    const geo = new THREE.BufferGeometry();
    geo.setAttribute('position', new THREE.BufferAttribute(p, 3));
    const mat = new THREE.PointsMaterial({ size, map: this.tex, color, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, fog: false });
    const pts = new THREE.Points(geo, mat);
    pts.frustumCulled = false;
    this.scene.add(pts);
    this.active.push({ pts, v, p, life, max: life });
  }
  update(dt) {
    for (let i = this.active.length - 1; i >= 0; i--) {
      const b = this.active[i];
      b.life -= dt;
      const arr = b.p;
      for (let k = 0; k < b.v.length / 3; k++) {
        b.v[k * 3 + 1] -= 9 * dt;
        arr[k * 3] += b.v[k * 3] * dt;
        arr[k * 3 + 1] += b.v[k * 3 + 1] * dt;
        arr[k * 3 + 2] += b.v[k * 3 + 2] * dt;
      }
      b.pts.geometry.attributes.position.needsUpdate = true;
      b.pts.material.opacity = Math.max(0, b.life / b.max);
      if (b.life <= 0) { this.scene.remove(b.pts); b.pts.geometry.dispose(); b.pts.material.dispose(); this.active.splice(i, 1); }
    }
  }
}

// ---------- Scanner ----------
export class Scanner {
  constructor(scene, accent) {
    this.scene = scene;
    this.accent = accent;
    this.ring = null;
    this.t = 0; this.active = false;
    this.maxR = 70; this.dur = 1.1;
    this.pending = [];
    this.plates = [];
    // ground ring (expanding)
    const geo = new THREE.RingGeometry(0.9, 1.0, 64);
    geo.rotateX(-Math.PI / 2);
    this.ringMat = new THREE.MeshBasicMaterial({ color: accent, transparent: true, side: THREE.DoubleSide, depthWrite: false, blending: THREE.AdditiveBlending, fog: false });
    this.ring = new THREE.Mesh(geo, this.ringMat);
    this.ring.visible = false;
    scene.add(this.ring);
    // dome
    const dgeo = new THREE.SphereGeometry(1, 24, 16);
    this.domeMat = new THREE.MeshBasicMaterial({ color: accent, transparent: true, wireframe: true, depthWrite: false, blending: THREE.AdditiveBlending, opacity: 0.0, fog: false });
    this.dome = new THREE.Mesh(dgeo, this.domeMat);
    this.dome.visible = false;
    scene.add(this.dome);
  }

  trigger(origin, targets, onReveal) {
    if (this.active) return false;
    this.active = true; this.t = 0;
    this.origin = origin.clone();
    this.ring.position.copy(origin); this.ring.position.y -= 1.6;
    this.ring.visible = true; this.dome.visible = true;
    this.dome.position.copy(origin);
    this.onReveal = onReveal;
    this.pending = targets
      .map((tg) => ({ tg, d: origin.distanceTo(tg.pos) }))
      .filter((o) => o.d < this.maxR)
      .sort((a, b) => a.d - b.d)
      .map((o) => ({ ...o, done: false }));
    return true;
  }

  // Deterministic reveal (used for screenshots; live play uses the timed wave).
  forceReveal(n = 10) {
    if (!this.active) return;
    this.t = this.dur * 0.45;
    const r = this.maxR * 0.55;
    this.ring.scale.set(r, 1, r); this.ringMat.opacity = 0.6;
    this.dome.scale.setScalar(r); this.domeMat.opacity = 0.18;
    let c = 0;
    for (const o of this.pending) {
      if (c >= n) break;
      if (!o.done) { o.done = true; const first = this.onReveal ? this.onReveal(o.tg) : false; this.spawnPlate(o.tg, first); c++; }
    }
  }

  update(dt) {
    // fade plates
    for (let i = this.plates.length - 1; i >= 0; i--) {
      const pl = this.plates[i];
      pl.life -= dt;
      const k = pl.life;
      if (k < 1) pl.spr.material.opacity = Math.max(0, k);
      pl.spr.position.y = pl.baseY + (1 - Math.min(1, pl.life / pl.max)) * 0.0;
      if (pl.life <= 0) { this.scene.remove(pl.spr); pl.spr.material.map.dispose(); pl.spr.material.dispose(); this.plates.splice(i, 1); }
    }
    if (!this.active) return;
    this.t += dt;
    const p = Math.min(1, this.t / this.dur);
    const r = p * this.maxR;
    this.ring.scale.set(r, 1, r);
    this.ringMat.opacity = (1 - p) * 0.9;
    this.dome.scale.setScalar(r);
    this.domeMat.opacity = (1 - p) * 0.25;
    for (const o of this.pending) {
      if (!o.done && r >= o.d) {
        o.done = true;
        const first = this.onReveal ? this.onReveal(o.tg) : false;
        this.spawnPlate(o.tg, first);
      }
    }
    if (p >= 1) { this.active = false; this.ring.visible = false; this.dome.visible = false; }
  }

  spawnPlate(tg, first) {
    const spr = nameplate(tg, first, this.accent);
    const y = tg.pos.y + (tg.type === 'mineral' ? tg.scale * 2.2 + 2 : 4.5);
    spr.position.set(tg.pos.x, y, tg.pos.z);
    spr.material.opacity = 1;
    this.scene.add(spr);
    this.plates.push({ spr, life: 4.5, max: 4.5, baseY: y });
  }
}

// ---------- Miner ----------
function orientCyl(mesh, a, b) {
  const dir = new THREE.Vector3().subVectors(b, a);
  const len = dir.length();
  mesh.position.copy(a).addScaledVector(dir, 0.5);
  mesh.scale.set(1, len, 1);
  mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), dir.normalize());
}

export class Miner {
  constructor(scene, bursts, color) {
    this.scene = scene; this.bursts = bursts;
    this.beam = new THREE.Mesh(
      new THREE.CylinderGeometry(0.04, 0.09, 1, 6),
      new THREE.MeshBasicMaterial({ color: color, transparent: true, opacity: 0.9, blending: THREE.AdditiveBlending, depthWrite: false, fog: false })
    );
    this.beam.visible = false; scene.add(this.beam);
    this.impact = new THREE.Sprite(new THREE.SpriteMaterial({ map: burstTex(), color, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, fog: false }));
    this.impact.visible = false; this.impact.scale.setScalar(1.5); scene.add(this.impact);
    this.color = color;
    this.sparkT = 0;
  }

  // returns {mined, amount, target} when a crystal is destroyed this frame
  update(dt, active, camera, minerals, range = 14) {
    let result = null;
    if (!active || !minerals) { this.beam.visible = false; this.impact.visible = false; return result; }
    // aim ray
    const origin = camera.position.clone();
    const dir = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion);
    // nearest crystal roughly along aim
    let best = null, bestScore = -1;
    for (const cr of minerals.userData.crystals) {
      if (cr.mined) continue;
      const to = new THREE.Vector3().subVectors(cr.pos, origin);
      const d = to.length();
      if (d > range) continue;
      const align = to.normalize().dot(dir);
      if (align < 0.965) continue;
      const score = align - d * 0.002;
      if (score > bestScore) { bestScore = score; best = cr; }
    }
    if (!best) { this.beam.visible = false; this.impact.visible = false; return result; }

    // gun muzzle slightly below-right of camera
    const muzzle = origin.clone()
      .add(new THREE.Vector3(0.35, -0.35, -0.4).applyQuaternion(camera.quaternion));
    this.beam.visible = true;
    orientCyl(this.beam, muzzle, best.pos);
    this.impact.visible = true; this.impact.position.copy(best.pos);
    this.impact.scale.setScalar(1.2 + Math.sin(this.sparkT * 30) * 0.3);

    this.sparkT += dt;
    if (this.sparkT > 0.06) { this.sparkT = 0; this.bursts.spawn(best.pos, this.color, 5, 2.5, 0.35, 0.3); }

    best.hp -= dt * 2.2;
    if (best.hp <= 0) {
      best.mined = true;
      // hide instance + shards
      minerals.userData.inst.setMatrixAt(best.instId, minerals.userData.zero);
      minerals.userData.inst.instanceMatrix.needsUpdate = true;
      for (const s of best.shards) { minerals.userData.inst2.setMatrixAt(s, minerals.userData.zero); }
      minerals.userData.inst2.instanceMatrix.needsUpdate = true;
      this.bursts.spawn(best.pos, this.color, 26, 7, 0.8, 0.6);
      this.beam.visible = false; this.impact.visible = false;
      result = { mined: true, amount: best.yield, target: best };
    }
    return result;
  }
}
