import * as THREE from 'three';
import { Rng } from './noise.js';
import { floraName, mineralName } from './names.js';

const M4 = () => new THREE.Matrix4();
const local = (px, py, pz, rx, ry, rz, sx, sy, sz) =>
  new THREE.Matrix4().compose(
    new THREE.Vector3(px, py, pz),
    new THREE.Quaternion().setFromEuler(new THREE.Euler(rx, ry, rz)),
    new THREE.Vector3(sx, sy, sz)
  );

// --- Archetype geometry builders. Each returns parts with a local transform + kind. ---
// kinds: 'plant' (per-instance colored), 'glow' (emissive), 'trunk' (dark stalk)
function buildArchetype(id, rng) {
  const parts = [];
  const H = rng.range(2.4, 4.2);
  if (id === 'tubeTree') {
    const r = rng.range(0.10, 0.18);
    parts.push({ kind: 'trunk', geo: new THREE.CylinderGeometry(r * 0.7, r, H, 8), m: local(0, H / 2, 0, 0, 0, 0, 1, 1, 1) });
    const nb = rng.int(2, 4);
    for (let i = 0; i < nb; i++) {
      const y = H * (0.7 + i * 0.12);
      const s = rng.range(0.35, 0.6) * (1 - i * 0.12);
      parts.push({ kind: 'plant', geo: new THREE.SphereGeometry(1, 12, 10), m: local(0, y, 0, 0, 0, 0, s, s * 1.3, s) });
    }
    parts.push({ kind: 'glow', geo: new THREE.SphereGeometry(0.18, 10, 8), m: local(0, H * 1.04, 0, 0, 0, 0, 1, 1, 1) });
  } else if (id === 'balloon') {
    parts.push({ kind: 'trunk', geo: new THREE.CylinderGeometry(0.06, 0.1, H * 0.7, 6), m: local(0, H * 0.35, 0, 0, 0, 0, 1, 1, 1) });
    const s = rng.range(0.7, 1.2);
    parts.push({ kind: 'glow', geo: new THREE.SphereGeometry(1, 16, 12), m: local(0, H * 0.7 + s * 0.7, 0, 0, 0, 0, s, s * 1.2, s) });
    parts.push({ kind: 'plant', geo: new THREE.SphereGeometry(1, 12, 8), m: local(0, H * 0.7 + s * 0.7, 0, 0, 0, 0, s * 1.08, s * 1.3, s * 1.08) });
  } else if (id === 'coralFan') {
    const blades = rng.int(7, 12);
    const spread = rng.range(0.9, 1.5);
    parts.push({ kind: 'trunk', geo: new THREE.CylinderGeometry(0.12, 0.18, H * 0.4, 6), m: local(0, H * 0.2, 0, 0, 0, 0, 1, 1, 1) });
    for (let i = 0; i < blades; i++) {
      const a = (i / (blades - 1) - 0.5) * Math.PI * spread;
      const len = H * rng.range(0.5, 0.9);
      parts.push({
        kind: 'plant', geo: new THREE.BoxGeometry(0.08, len, 0.5),
        m: local(Math.sin(a) * 0.2, H * 0.4 + Math.cos(a) * len * 0.5, 0, 0, 0, a, 1, 1, 1),
      });
    }
    parts.push({ kind: 'glow', geo: new THREE.SphereGeometry(0.1, 8, 6), m: local(0, H * 0.45, 0, 0, 0, 0, 1, 1, 1) });
  } else if (id === 'crystalBush') {
    const n = rng.int(4, 7);
    for (let i = 0; i < n; i++) {
      const a = (i / n) * Math.PI * 2;
      const h = rng.range(0.8, 1.8);
      const rad = rng.range(0.15, 0.35);
      parts.push({
        kind: 'glow', geo: new THREE.ConeGeometry(rad, h, 5),
        m: local(Math.cos(a) * 0.3, h * 0.5 + 0.1, Math.sin(a) * 0.3, rng.range(-0.2, 0.2), a, rng.range(-0.25, 0.25), 1, 1, 1),
      });
    }
  } else if (id === 'spiralReed') {
    parts.push({ kind: 'trunk', geo: new THREE.CylinderGeometry(0.05, 0.08, H, 6), m: local(0, H / 2, 0, 0, 0, 0, 1, 1, 1) });
    const beads = rng.int(7, 12);
    for (let i = 0; i < beads; i++) {
      const t = i / beads;
      const a = t * Math.PI * 5;
      const rad = 0.35 * (1 - t * 0.5);
      const s = rng.range(0.12, 0.22);
      parts.push({ kind: 'plant', geo: new THREE.SphereGeometry(1, 8, 6), m: local(Math.cos(a) * rad, H * t, Math.sin(a) * rad, 0, 0, 0, s, s, s) });
    }
    parts.push({ kind: 'glow', geo: new THREE.SphereGeometry(0.14, 8, 6), m: local(0, H * 1.02, 0, 0, 0, 0, 1, 1, 1) });
  } else { // umbrella
    parts.push({ kind: 'trunk', geo: new THREE.CylinderGeometry(0.08, 0.14, H * 0.8, 7), m: local(0, H * 0.4, 0, 0, 0, 0, 1, 1, 1) });
    const cap = rng.range(0.9, 1.5);
    parts.push({ kind: 'plant', geo: new THREE.ConeGeometry(cap, cap * 0.55, 14, 1, true), m: local(0, H * 0.8, 0, 0, 0, 0, 1, 1, 1) });
    parts.push({ kind: 'glow', geo: new THREE.TorusGeometry(cap * 0.7, 0.05, 6, 18), m: local(0, H * 0.8 - cap * 0.18, 0, Math.PI / 2, 0, 0, 1, 1, 1) });
  }
  return parts;
}

export function createFlora(spec, heightAt, opts = {}) {
  const group = new THREE.Group();
  group.name = 'flora';
  const rng = new Rng(spec.seed + 4242);
  const pal = spec.palette;

  // species: 6 scannable identities mapped over the planet's archetype subset
  const species = [];
  for (let i = 0; i < spec.flora.speciesCount; i++) {
    const arche = spec.flora.archetypes[i % spec.flora.archetypes.length];
    const baseCol = pal.flora[i % pal.flora.length].clone();
    baseCol.offsetHSL(rng.range(-0.04, 0.04), rng.range(-0.1, 0.1), rng.range(-0.05, 0.08));
    species.push({
      idx: i, arche, name: floraName(spec.seed, i),
      color: baseCol,
      scale: rng.range(spec.flora.scaleRange[0], spec.flora.scaleRange[1]),
      stretch: rng.range(0.85, 1.3),
      scanned: false,
    });
  }

  // materials (shared roles)
  const plantMat = new THREE.MeshStandardMaterial({ roughness: 0.62, metalness: 0.0, flatShading: false });
  const trunkMat = new THREE.MeshStandardMaterial({ color: pal.rock, roughness: 0.85, metalness: 0.05 });

  // build geometry per archetype once (seed-derived proportions)
  const archeParts = {};
  for (const a of spec.flora.archetypes) archeParts[a] = buildArchetype(a, new Rng(spec.seed + a.length * 131 + 9));

  // clustered scatter — plants grow in patches (denser, more natural than uniform random)
  const R = opts.radius || 220;
  const inner = opts.inner || 16;
  const clusters = Math.round((opts.clusters || 150) * spec.flora.density);
  const placements = []; // {sp, x, z, y, rotY, scl}
  const specimens = [];
  for (let ci = 0; ci < clusters; ci++) {
    const a = rng.next() * Math.PI * 2;
    const d = inner + Math.sqrt(rng.next()) * (R - inner);
    const cx = Math.cos(a) * d, cz = Math.sin(a) * d;
    const patchSp = rng.pick(species);
    const spread = rng.range(4, 9);
    const n = rng.int(3, 7);
    for (let k = 0; k < n; k++) {
      const x = cx + rng.range(-spread, spread), z = cz + rng.range(-spread, spread);
      const y = heightAt(x, z);
      const sp = rng.chance(0.72) ? patchSp : rng.pick(species);
      const scl = sp.scale * rng.range(0.78, 1.3);
      const rotY = rng.next() * Math.PI * 2;
      placements.push({ sp, x, z, y, rotY, scl });
      specimens.push({ pos: new THREE.Vector3(x, y, z), name: sp.name, speciesIdx: sp.idx, type: 'flora', sp });
    }
  }

  // group placements by archetype, build instanced meshes per part
  const glowMats = {};
  for (const a of spec.flora.archetypes) {
    const list = placements.filter((p) => p.sp.arche === a);
    if (!list.length) continue;
    const parts = archeParts[a];
    const gcol = pal.crystal.clone().offsetHSL(rng.range(-0.05, 0.05), 0, 0.05);
    const glowMat = new THREE.MeshStandardMaterial({ color: gcol, emissive: gcol, emissiveIntensity: 1.6, roughness: 0.4, metalness: 0.2 });
    glowMats[a] = glowMat;
    for (const part of parts) {
      const mat = part.kind === 'glow' ? glowMat : part.kind === 'trunk' ? trunkMat : plantMat.clone();
      const inst = new THREE.InstancedMesh(part.geo, mat, list.length);
      inst.castShadow = part.kind !== 'glow';
      inst.receiveShadow = false;
      inst.frustumCulled = false; // instance bounds are origin-centered; cull manually if ever needed
      const tmp = M4();
      const base = M4();
      const q = new THREE.Quaternion();
      const col = new THREE.Color();
      list.forEach((p, idx) => {
        base.compose(
          new THREE.Vector3(p.x, p.y, p.z),
          q.setFromEuler(new THREE.Euler(0, p.rotY, 0)),
          new THREE.Vector3(p.scl, p.scl * p.sp.stretch, p.scl)
        );
        tmp.multiplyMatrices(base, part.m);
        inst.setMatrixAt(idx, tmp);
        if (part.kind === 'plant') {
          col.copy(p.sp.color);
          inst.setColorAt(idx, col);
        }
      });
      inst.instanceMatrix.needsUpdate = true;
      if (inst.instanceColor) inst.instanceColor.needsUpdate = true;
      group.add(inst);
    }
  }

  group.userData.species = species;
  group.userData.specimens = specimens;
  return group;
}

// ---- mineable crystals ----
export function createMinerals(spec, heightAt, opts = {}) {
  const group = new THREE.Group();
  group.name = 'minerals';
  const rng = new Rng(spec.seed + 7777);
  const pal = spec.palette;
  const count = opts.count || 46;
  const R = opts.radius || 200;
  const inner = opts.inner || 14;

  const cCol = pal.crystal.clone();
  const mat = new THREE.MeshStandardMaterial({
    color: cCol, emissive: pal.crystalEmissive, emissiveIntensity: 1.5,
    roughness: 0.18, metalness: 0.35, flatShading: true,
  });
  const geo = new THREE.OctahedronGeometry(1, 0);
  const inst = new THREE.InstancedMesh(geo, mat, count);
  inst.name = 'crystalInst';
  inst.castShadow = true;
  inst.frustumCulled = false;

  // a couple of secondary shards per cluster for silhouette
  const geo2 = new THREE.ConeGeometry(0.4, 1.4, 5);
  const inst2 = new THREE.InstancedMesh(geo2, mat, count * 2);
  inst2.frustumCulled = false;

  const crystals = [];
  const m = M4(), q = new THREE.Quaternion(), zero = new THREE.Matrix4().makeScale(0, 0, 0);
  let i2 = 0;
  for (let i = 0; i < count; i++) {
    const a = rng.next() * Math.PI * 2;
    const d = inner + Math.sqrt(rng.next()) * (R - inner);
    const x = Math.cos(a) * d, z = Math.sin(a) * d;
    const y = heightAt(x, z);
    const s = rng.range(0.7, 1.5);
    m.compose(new THREE.Vector3(x, y + s * 0.7, z), q.setFromEuler(new THREE.Euler(rng.range(-0.2, 0.2), rng.next() * 6.28, rng.range(-0.2, 0.2))), new THREE.Vector3(s, s * rng.range(1.3, 2.1), s));
    inst.setMatrixAt(i, m);
    // shards
    for (let k = 0; k < 2; k++) {
      const sa = rng.next() * 6.28, sr = s * rng.range(0.4, 0.8);
      const ss = s * rng.range(0.4, 0.8);
      m.compose(new THREE.Vector3(x + Math.cos(sa) * sr, y + ss * 0.6, z + Math.sin(sa) * sr), q.setFromEuler(new THREE.Euler(rng.range(-0.3, 0.3), rng.next() * 6.28, rng.range(-0.3, 0.3))), new THREE.Vector3(ss, ss * 1.4, ss));
      inst2.setMatrixAt(i2, m);
      crystals.push({ shard: true, parent: i, instId: i2 });
      i2++;
    }
    crystals.push({
      pos: new THREE.Vector3(x, y + s * 0.7, z), name: mineralName(spec.seed),
      type: 'mineral', instId: i, scale: s, hp: 3, yield: rng.int(3, 6), mined: false,
      shards: [i2 - 2, i2 - 1],
    });
  }
  inst.instanceMatrix.needsUpdate = true;
  inst2.instanceMatrix.needsUpdate = true;
  group.add(inst);
  group.add(inst2);

  group.userData.crystals = crystals.filter((c) => !c.shard);
  group.userData.inst = inst;
  group.userData.inst2 = inst2;
  group.userData.zero = zero;
  return group;
}
