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

/** A procedural heightfield surrounding the room. It is the single source of
 *  ground height: the outdoor mesh is built from it and the player stands on the
 *  same `height` samples, so what you see and what you collide with agree. The
 *  center (out to `inner`) is a flat clearing where the room sits; from there the
 *  land rolls outward and ramps up into tall peaks at the far edge. Every field
 *  is a live knob -- edit them in the level to reshape the world. */
export type Terrain = {
  /** Half-extent of the flat central clearing (the room lives here); height 0. */
  inner: number
  /** World half-extent. Peaks ramp up toward this outer rim. */
  outer: number
  /** Ease-up distance just outside `inner`, so the clearing meets the hills with
   *  a slope instead of a wall. */
  blend: number
  /** Rolling-hill height across the open ground. */
  amplitude: number
  /** Rolling-hill frequency (low = broad hills over the big world). */
  frequency: number
  /** Extra height of the mountains near the edge -- make this big for peaks. */
  peakHeight: number
  /** Mountain frequency (low = few, massive ridges). */
  peakFrequency: number
  /** Fraction of the way out (0..1) where the peaks begin rising. */
  peakStart: number
}

export namespace Terrain {
  /** Ground height at world (x, z). 0 inside the clearing, rolling hills beyond,
   *  ramping into peaks toward the edge. Uses a square (Chebyshev) radius so the
   *  clearing is a square that lines up with the square room. */
  export function height(t: Terrain, x: number, z: number): number {
    const r = Math.max(Math.abs(x), Math.abs(z))
    if (r <= t.inner) {
      return 0
    }
    const rise = smoothstep(t.inner, t.inner + t.blend, r)
    const hills = t.amplitude * bumps(x, z, t.frequency)
    const k = Math.min(1, (r - t.inner) / (t.outer - t.inner))
    const peaks = t.peakHeight * ridges(x, z, t.peakFrequency) * smoothstep(t.peakStart, 1, k)
    return rise * (hills + peaks)
  }

  /** Append one ground patch: a `cols`x`rows` heightfield grid over the rectangle
   *  [x0,x1] x [z0,z1], each vertex lifted onto the heightfield. Quads whose
   *  center is inside the clearing are skipped (the room floor's hole). UVs use
   *  world position * `uvScale`, so neighboring patches tile seamlessly. Callers
   *  keep the spacing uniform and cell edges aligned, so shared edges weld with
   *  no cracks. Used to build the terrain per spatial chunk. */
  export function patch(
    t: Terrain,
    mesh: Mesh,
    x0: number,
    z0: number,
    x1: number,
    z1: number,
    cols: number,
    rows: number,
    uvScale: number,
  ): void {
    const base = mesh.verts.length / STRIDE
    const dx = (x1 - x0) / cols
    const dz = (z1 - z0) / rows
    const rowLen = cols + 1
    for (let i = 0; i <= rows; i++) {
      const z = z0 + i * dz
      for (let j = 0; j <= cols; j++) {
        const x = x0 + j * dx
        mesh.verts.push(x, height(t, x, z), z, x * uvScale, z * uvScale)
      }
    }
    for (let i = 0; i < rows; i++) {
      for (let j = 0; j < cols; j++) {
        const cx = x0 + (j + 0.5) * dx
        const cz = z0 + (i + 0.5) * dz
        if (Math.max(Math.abs(cx), Math.abs(cz)) < t.inner) {
          continue
        }
        const p = base + i * rowLen + j
        // Wound so the surface faces up/out, matching the backface-cull sign.
        mesh.indices.push(p, p + rowLen + 1, p + 1, p, p + rowLen, p + rowLen + 1)
      }
    }
  }

  /** Rolling hills in 0..1, always non-negative so the ground never dips below
   *  the clearing. */
  function bumps(x: number, z: number, f: number): number {
    const a = Math.sin(x * f) * Math.cos(z * f)
    const b = Math.sin((x + z) * f * 0.5 + 1.7) * 0.5
    return (a + b + 1.5) / 3
  }

  /** Ridged noise in 0..1: crests where the field crosses zero give sharp
   *  mountain ridgelines rather than round blobs. */
  function ridges(x: number, z: number, f: number): number {
    const n = Math.sin(x * f + 1.3) * Math.cos(z * f - 0.7) * 0.7 + Math.sin((x + z) * f * 0.6 + 2.5) * 0.3
    return 1 - Math.abs(n)
  }

  function smoothstep(a: number, b: number, x: number): number {
    const t = Math.max(0, Math.min(1, (x - a) / (b - a || 1e-4)))
    return t * t * (3 - 2 * t)
  }
}
