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

/** A bounded ground surface. Implementations may be procedural, sampled, or
 * loaded; callers only depend on world-space height sampling. */
export type Terrain = {
  minX: number
  minZ: number
  maxX: number
  maxZ: number
  heightAt: (x: number, z: number) => number
}

/** Parameters for the built-in rolling terrain generator. Values describe the
 * surface only; level-specific holes and materials belong to level data. */
export type RollingTerrainConfig = {
  inner: number
  outer: number
  blend: number
  amplitude: number
  frequency: number
  peakHeight: number
  peakFrequency: number
  peakStart: number
}

export namespace Terrain {
  /** Built-in square world with a flat center, rolling hills, and edge ridges. */
  export function rolling(config: RollingTerrainConfig): Terrain {
    return {
      minX: -config.outer,
      minZ: -config.outer,
      maxX: config.outer,
      maxZ: config.outer,
      heightAt(x, z) {
        const r = Math.max(Math.abs(x), Math.abs(z))
        if (r <= config.inner) {
          return 0
        }
        const rise = smoothstep(config.inner, config.inner + config.blend, r)
        const hills = config.amplitude * bumps(x, z, config.frequency)
        const k = Math.min(
          1,
          (r - config.inner) / (config.outer - config.inner),
        )
        const peaks =
          config.peakHeight *
          ridges(x, z, config.peakFrequency) *
          smoothstep(config.peakStart, 1, k)
        return rise * (hills + peaks)
      },
    }
  }

  export function height(terrain: Terrain, x: number, z: number): number {
    return terrain.heightAt(x, z)
  }

  /** Append one sampled heightfield patch. `include` is level policy evaluated at
   * each quad center, allowing arbitrary holes without teaching terrain what
   * occupies them. */
  export function patch(
    terrain: Terrain,
    mesh: Mesh,
    x0: number,
    z0: number,
    x1: number,
    z1: number,
    cols: number,
    rows: number,
    uvScale: number,
    include?: (x: number, z: number) => boolean,
  ): void {
    const base = mesh.verts.length / STRIDE
    const dx = (x1 - x0) / cols
    const dz = (z1 - z0) / rows
    const rowLength = cols + 1
    for (let row = 0; row <= rows; row++) {
      const z = z0 + row * dz
      for (let col = 0; col <= cols; col++) {
        const x = x0 + col * dx
        mesh.verts.push(x, terrain.heightAt(x, z), z, x * uvScale, z * uvScale)
      }
    }
    for (let row = 0; row < rows; row++) {
      for (let col = 0; col < cols; col++) {
        const cx = x0 + (col + 0.5) * dx
        const cz = z0 + (row + 0.5) * dz
        if (include !== undefined && !include(cx, cz)) {
          continue
        }
        const p = base + row * rowLength + col
        mesh.indices.push(
          p,
          p + rowLength + 1,
          p + 1,
          p,
          p + rowLength,
          p + rowLength + 1,
        )
      }
    }
  }

  function bumps(x: number, z: number, frequency: number): number {
    const a = Math.sin(x * frequency) * Math.cos(z * frequency)
    const b = Math.sin((x + z) * frequency * 0.5 + 1.7) * 0.5
    return (a + b + 1.5) / 3
  }

  function ridges(x: number, z: number, frequency: number): number {
    const n =
      Math.sin(x * frequency + 1.3) * Math.cos(z * frequency - 0.7) * 0.7 +
      Math.sin((x + z) * frequency * 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)
  }
}
