// flora.js — procedural alien flora + mineable crystals, kit-bashed from primitives.
import * as THREE from 'three';
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
import { RNG } from './util.js';

// Attach a flat vertex color and strip uv so geometries merge cleanly.
function tint(geo, color) {
  geo.deleteAttribute('uv');
  const n = geo.attributes.position.count;
  const arr = new Float32Array(n * 3);
  for (let i = 0; i < n; i++) { arr[i * 3] = color.r; arr[i * 3 + 1] = color.g; arr[i * 3 + 2] = color.b; }
  geo.setAttribute('color', new THREE.BufferAttribute(arr, 3));
  if (!geo.attributes.normal) geo.computeVertexNormals();
  return geo;
}
function place(geo, x, y, z, rx = 0, ry = 0, rz = 0, sx = 1, sy = 1, sz = 1) {
  const m = new THREE.Matrix4();
  m.compose(new THREE.Vector3(x, y, z),
    new THREE.Quaternion().setFromEuler(new THREE.Euler(rx, ry, rz)),
    new THREE.Vector3(sx, sy, sz));
  geo.applyMatrix4(m);
  return geo;
}

// Each builder returns { body:[geo...], glow:[geo...] } in local space, base at y=0.
const BUILDERS = {
  tubeTree(a, rng) {
    const body = [], glow = [];
    const h = 3.2 * a.height, r = 0.16 * a.girth;
    const trunk = place(new THREE.CylinderGeometry(r * 0.6, r, h, 7), 0, h / 2, 0, 0, 0, a.lean);
    body.push(tint(trunk, a.color.clone().multiplyScalar(0.7)));
    const segs = a.parts;
    for (let i = 0; i < segs; i++) {
      const t = i / segs;
      const bulb = place(new THREE.SphereGeometry(r * (2.4 - t), 10, 8), Math.sin(t * 6) * 0.2, h * (0.5 + t * 0.5), Math.cos(t * 6) * 0.2);
      glow.push(tint(bulb, a.accent.clone().multiplyScalar(1.2)));
    }
    // drooping fronds
    for (let i = 0; i < 5; i++) {
      const ang = (i / 5) * Math.PI * 2;
      const frond = place(new THREE.BoxGeometry(0.06, h * 0.4, 0.12), Math.cos(ang) * r * 2, h * 0.78, Math.sin(ang) * r * 2, 0.5, ang, 0);
      body.push(tint(frond, a.color));
    }
    return { body, glow };
  },
  balloon(a, rng) {
    const body = [], glow = [];
    const h = 2.0 * a.height, r = 0.1 * a.girth;
    body.push(tint(place(new THREE.CylinderGeometry(r * 0.7, r, h, 6), 0, h / 2, 0, 0, 0, a.lean), a.color.clone().multiplyScalar(0.6)));
    for (let i = 0; i < a.parts; i++) {
      const br = (0.5 + rng.next() * 0.5) * 0.8 * a.girth;
      const bx = (rng.next() - 0.5) * 0.6;
      const bz = (rng.next() - 0.5) * 0.6;
      glow.push(tint(place(new THREE.SphereGeometry(br, 12, 10), bx, h + br * 0.6 + i * 0.25, bz), a.accent));
    }
    return { body, glow };
  },
  coralFan(a, rng) {
    const body = [], glow = [];
    const w = 2.4 * a.height;
    const blades = 5 + a.parts;
    for (let i = 0; i < blades; i++) {
      const t = i / (blades - 1) - 0.5;
      const ang = t * 1.6;
      const len = (1 - Math.abs(t) * 0.6) * w;
      const blade = place(new THREE.BoxGeometry(0.14 * a.girth, len, 0.5), Math.sin(ang) * 0.3, len / 2, 0, 0, 0, ang);
      body.push(tint(blade, a.color));
      // glowing rim dots
      glow.push(tint(place(new THREE.SphereGeometry(0.12, 8, 6), Math.sin(ang) * len * 0.5, len * 0.95, 0), a.accent.clone().multiplyScalar(1.3)));
    }
    return { body, glow };
  },
  crystalCluster(a, rng) {
    const body = [], glow = [];
    const n = 3 + a.parts;
    for (let i = 0; i < n; i++) {
      const hh = (0.6 + rng.next() * 1.4) * a.height;
      const ang = rng.next() * Math.PI * 2;
      const rad = rng.next() * 0.6;
      const sh = place(new THREE.ConeGeometry(0.18 * a.girth + rng.next() * 0.12, hh, 5),
        Math.cos(ang) * rad, hh / 2, Math.sin(ang) * rad, (rng.next() - 0.5) * 0.4, ang, (rng.next() - 0.5) * 0.4);
      glow.push(tint(sh, a.accent.clone().multiplyScalar(1.25)));
    }
    return { body, glow };
  },
  sporeStalk(a, rng) {
    const body = [], glow = [];
    const h = 1.8 * a.height, r = 0.09 * a.girth;
    body.push(tint(place(new THREE.CylinderGeometry(r, r * 1.4, h, 7), 0, h / 2, 0, 0, 0, a.lean), a.color.clone().multiplyScalar(0.65)));
    // cap
    const cap = place(new THREE.SphereGeometry(0.8 * a.girth, 14, 8, 0, Math.PI * 2, 0, Math.PI * 0.55), 0, h, 0, Math.PI, 0, 0);
    cap.scale(1, 0.8, 1);
    body.push(tint(cap, a.color.clone().multiplyScalar(1.1)));
    // glowing gills underside
    glow.push(tint(place(new THREE.SphereGeometry(0.5 * a.girth, 12, 6), 0, h - 0.1, 0), a.accent.clone().multiplyScalar(1.2)));
    return { body, glow };
  },
  frondFan(a, rng) {
    const body = [], glow = [];
    const h = 0.6 * a.height, r = 0.12 * a.girth;
    body.push(tint(place(new THREE.CylinderGeometry(r, r * 1.3, h, 6), 0, h / 2, 0), a.color.clone().multiplyScalar(0.7)));
    const blades = 5 + a.parts;
    for (let i = 0; i < blades; i++) {
      const ang = (i / blades) * Math.PI * 2;
      const len = (1.6 + rng.next()) * a.height;
      const blade = place(new THREE.BoxGeometry(0.08, len, 0.22), Math.cos(ang) * 0.15, h + Math.sin(0.6) * 0.2, Math.sin(ang) * 0.15, 0.9, ang, 0);
      body.push(tint(blade, a.color));
    }
    glow.push(tint(place(new THREE.SphereGeometry(0.16, 8, 6), 0, h + 0.1, 0), a.accent.clone().multiplyScalar(1.3)));
    return { body, glow };
  },
};

function mergedArchetype(arch, rng) {
  const { body, glow } = BUILDERS[arch.kind](arch, rng);
  return {
    body: body.length ? mergeGeometries(body, false) : null,
    glow: glow.length ? mergeGeometries(glow, false) : null,
  };
}

// Scatter flora across terrain. heightFn(x,z)->y. Returns controller.
export function scatterFlora(scene, planet, heightFn, opts = {}) {
  const rng = new RNG(planet.seed ^ 0x5eed);
  const area = opts.area || 220;
  const total = opts.count || 340;
  const group = new THREE.Group();
  scene.add(group);

  const archMeshes = [];
  const scannables = [];

  // distribute counts across archetypes
  const counts = planet.archetypes.map(() => Math.floor(total / planet.archetypes.length));

  const bodyMat = new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 0.85, metalness: 0.0, flatShading: true });
  const glowMatProto = () => new THREE.MeshBasicMaterial({ vertexColors: true, toneMapped: true });

  planet.archetypes.forEach((arch, ai) => {
    const geo = mergedArchetype(arch, new RNG(planet.seed * 17 + ai));
    const count = counts[ai];
    const bodyInst = geo.body ? new THREE.InstancedMesh(geo.body, bodyMat, count) : null;
    const glowInst = geo.glow ? new THREE.InstancedMesh(geo.glow, glowMatProto(), count) : null;
    if (bodyInst) { bodyInst.castShadow = true; bodyInst.receiveShadow = true; bodyInst.frustumCulled = false; group.add(bodyInst); }
    if (glowInst) { glowInst.frustumCulled = false; group.add(glowInst); }

    const m = new THREE.Matrix4();
    let placed = 0;
    let guard = 0;
    while (placed < count && guard < count * 6) {
      guard++;
      const x = (rng.next() - 0.5) * area;
      const z = (rng.next() - 0.5) * area;
      // avoid the ship landing pad at origin
      if (Math.abs(x) < 9 && Math.abs(z) < 9) continue;
      const y = heightFn(x, z);
      // slope check
      const e = 1.5;
      const slope = Math.abs(heightFn(x + e, z) - heightFn(x - e, z)) + Math.abs(heightFn(x, z + e) - heightFn(x, z - e));
      if (slope > 6) continue;
      const s = (0.7 + rng.next() * 0.7) * (opts.scale || 1);
      const ry = rng.next() * Math.PI * 2;
      m.compose(new THREE.Vector3(x, y - 0.1, z),
        new THREE.Quaternion().setFromEuler(new THREE.Euler((rng.next() - 0.5) * 0.12, ry, (rng.next() - 0.5) * 0.12)),
        new THREE.Vector3(s, s, s));
      if (bodyInst) bodyInst.setMatrixAt(placed, m);
      if (glowInst) glowInst.setMatrixAt(placed, m);
      // a fraction become scannable specimens
      if (placed % 5 === 0) {
        scannables.push({
          type: 'flora', kind: arch.kind, name: planet.floraNames[ai],
          pos: new THREE.Vector3(x, y + s * 1.6, z), scanned: false, value: 24,
        });
      }
      placed++;
    }
    if (bodyInst) bodyInst.count = placed;
    if (glowInst) glowInst.count = placed;
    if (bodyInst) bodyInst.instanceMatrix.needsUpdate = true;
    if (glowInst) glowInst.instanceMatrix.needsUpdate = true;
    archMeshes.push({ bodyInst, glowInst });
  });

  return { group, scannables, dispose() { scene.remove(group); } };
}

// Mineable crystal field — emissive clusters that can shatter.
export function makeCrystalField(scene, planet, heightFn, opts = {}) {
  const rng = new RNG(planet.seed ^ 0xc0ffee);
  const area = opts.area || 200;
  const count = opts.count || 64;
  const group = new THREE.Group();
  scene.add(group);

  const col = planet.colors.crystal;
  const mat = new THREE.MeshStandardMaterial({
    color: col.clone().multiplyScalar(0.7), emissive: col.clone(), emissiveIntensity: 2.2,
    roughness: 0.15, metalness: 0.1, flatShading: true,
  });
  const geo = new THREE.OctahedronGeometry(1, 0);
  const inst = new THREE.InstancedMesh(geo, mat, count);
  inst.frustumCulled = false;
  group.add(inst);

  const nodes = [];
  const m = new THREE.Matrix4();
  let placed = 0, guard = 0;
  while (placed < count && guard < count * 8) {
    guard++;
    const x = (rng.next() - 0.5) * area;
    const z = (rng.next() - 0.5) * area;
    if (Math.abs(x) < 11 && Math.abs(z) < 11) continue;
    const y = heightFn(x, z);
    const e = 1.5;
    const slope = Math.abs(heightFn(x + e, z) - heightFn(x - e, z)) + Math.abs(heightFn(x, z + e) - heightFn(x, z - e));
    if (slope > 8) continue;
    const h = 1.2 + rng.next() * 2.2;
    const node = {
      pos: new THREE.Vector3(x, y + h * 0.5, z), base: y, scale: h, ore: 4 + Math.floor(rng.next() * 4),
      alive: true, idx: placed,
      // scannable specimen
    };
    m.compose(node.pos, new THREE.Quaternion().setFromEuler(new THREE.Euler(0, rng.next() * 6.28, 0)),
      new THREE.Vector3(h * 0.45, h, h * 0.45));
    inst.setMatrixAt(placed, m);
    nodes.push(node);
    placed++;
  }
  inst.count = placed;
  inst.instanceMatrix.needsUpdate = true;

  // a few crystals are scannable as the mineral specimen
  const scannables = nodes.filter((_, i) => i % 9 === 0).map((n) => ({
    type: 'mineral', kind: 'crystal', name: planet.mineralName,
    pos: n.pos.clone().add(new THREE.Vector3(0, n.scale * 0.5, 0)), scanned: false, value: 16, node: n,
  }));

  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
  return {
    group, nodes, scannables, inst,
    // shatter a node: hide instance
    shatter(node) {
      node.alive = false;
      inst.setMatrixAt(node.idx, hidden);
      inst.instanceMatrix.needsUpdate = true;
    },
    dispose() { scene.remove(group); },
  };
}
