import { Vec3 } from "engine/math/Vec3"
import { STRIDE, type Mesh } from "engine/scene/Mesh"

// Shared faceted-geometry primitives + the per-tree RNG used by concrete tree
// prefab modules.

export const TAU = Math.PI * 2

/** A tapered tube between two points (trunk or branch), `sides`-gonal. */
export function limb(mesh: Mesh, a: Vec3, b: Vec3, ra: number, rb: number, sides: number): void {
  const axis = Vec3.normalize(Vec3.sub(b, a))
  const [u, v] = basis(axis)
  const len = Vec3.length(Vec3.sub(b, a))
  const start = mesh.verts.length / STRIDE
  for (let i = 0; i <= sides; i++) {
    const angle = (i / sides) * TAU
    const dx = u.x * Math.cos(angle) + v.x * Math.sin(angle)
    const dy = u.y * Math.cos(angle) + v.y * Math.sin(angle)
    const dz = u.z * Math.cos(angle) + v.z * Math.sin(angle)
    const s = i / sides
    mesh.verts.push(a.x + dx * ra, a.y + dy * ra, a.z + dz * ra, s * 1.5, 0)
    mesh.verts.push(b.x + dx * rb, b.y + dy * rb, b.z + dz * rb, s * 1.5, len * 0.5)
  }
  for (let i = 0; i < sides; i++) {
    const p = start + i * 2
    mesh.indices.push(p, p + 2, p + 3, p, p + 3, p + 1)
  }
}

/** A cone standing on a base ring, apex `height` above it (one spruce tier). */
export function cone(mesh: Mesh, base: Vec3, height: number, radius: number, sides: number): void {
  const start = mesh.verts.length / STRIDE
  mesh.verts.push(base.x, base.y + height, base.z, 0.5, 0)
  for (let i = 0; i <= sides; i++) {
    const angle = (i / sides) * TAU
    mesh.verts.push(base.x + Math.cos(angle) * radius, base.y, base.z + Math.sin(angle) * radius, (i / sides) * 2, 1)
  }
  for (let i = 0; i < sides; i++) {
    // Wound so the outer surface faces out, matching the backface-cull sign.
    mesh.indices.push(start, start + 2 + i, start + 1 + i)
  }
}

/** A lumpy low-poly sphere (one canopy blob). Per-ring radius wobble keeps it
 *  organic without cracking the longitude seam. */
export function blob(mesh: Mesh, center: Vec3, radius: number, rand: () => number, seg = 5, rings = 3): void {
  const start = mesh.verts.length / STRIDE
  for (let r = 0; r <= rings; r++) {
    const phi = (r / rings) * Math.PI
    const cy = Math.cos(phi)
    const cr = Math.sin(phi)
    const scale = radius * (0.85 + rand() * 0.3)
    for (let s = 0; s <= seg; s++) {
      const theta = (s / seg) * TAU
      mesh.verts.push(
        center.x + cr * Math.cos(theta) * scale,
        center.y + cy * scale,
        center.z + cr * Math.sin(theta) * scale,
        (s / seg) * 2,
        (r / rings) * 2,
      )
    }
  }
  const row = seg + 1
  for (let r = 0; r < rings; r++) {
    for (let s = 0; s < seg; s++) {
      const p = start + r * row + s
      mesh.indices.push(p, p + 1, p + row + 1, p, p + row + 1, p + row)
    }
  }
}

/** Linear interpolation, for the sapling -> full-grown ramps. */
export function lerp(a: number, b: number, t: number): number {
  return a + (b - a) * t
}

/** Deterministic 0..1 generator (mulberry32) seeded per tree. */
export function rng(seed: number): () => number {
  let a = seed >>> 0
  return () => {
    a = (a + 0x6D2B79F5) | 0
    let t = Math.imul(a ^ (a >>> 15), 1 | a)
    t ^= t + Math.imul(t ^ (t >>> 7), 61 | t)
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296
  }
}

/** Two unit vectors spanning the plane perpendicular to `axis`. */
function basis(axis: Vec3): [Vec3, Vec3] {
  const ref = Math.abs(axis.y) < 0.99 ? { x: 0, y: 1, z: 0 } : { x: 1, y: 0, z: 0 }
  const u = Vec3.normalize(Vec3.cross(ref, axis))
  return [u, Vec3.cross(axis, u)]
}
