/**
 * LANE-RENDER — Stream-Crust ground plane. THE FLOOR (round 5).
 *
 * Round 1 shipped a grid over flat crust. That read as "the void with lines on it",
 * which the review named: the game about place had no place. This makes the plane read
 * as terrain without ever competing with belts and machines — it's the stage, not the
 * set. Three barely-there layers:
 *
 *   - fbm tone variation: large, slow value-noise mottling so the crust isn't a flat
 *     fill. Amplitude is tiny on purpose; you feel it more than see it.
 *   - the grid, unchanged (it's the placement affordance and must stay legible).
 *   - a neutral rim: the crust darkens and dissolves into the void at the world edge.
 *
 * ERA HINTING lives in seams.ts, not here. The orders asked for strata whispers toward
 * the world edges, and I first wrote exactly that — a directional guess (sepia north,
 * phosphor east...). Then DATA landed `data/seams.json`, whose doc says "RENDER: `era`
 * skins the ground", and its layout has reel in BOTH the north-west and north-east and
 * disc both north-centre and south-west. A directional guess would have contradicted the
 * data on screen. So era is skinned per seam region, with a wide soft falloff so it still
 * reads as a stratum whisper across the ground — and the richest seams sit near the rim
 * anyway, which is where the orders wanted the hinting to show up.
 */
import * as THREE from 'three';
import { WORLD } from './palette';
import { WORLD_HALF } from './camera';

const PLANE = WORLD_HALF * 5; // extends past the world; detail only drawn inside ±WORLD_HALF

const VERT = /* glsl */ `
  varying vec2 vWorld;
  void main() {
    vec4 wp = modelMatrix * vec4(position, 1.0);
    vWorld = wp.xz;
    gl_Position = projectionMatrix * viewMatrix * wp;
  }
`;

const FRAG = /* glsl */ `
  precision highp float;
  uniform vec3 uBase;
  uniform vec3 uLine;
  uniform vec3 uMajor;
  uniform float uHalf;
  varying vec2 vWorld;

  // value noise + 3-octave fbm — cheap terrain mottle, no texture fetch.
  float hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }
  float vnoise(vec2 p){
    vec2 i = floor(p), f = fract(p);
    f = f*f*(3.0-2.0*f);
    float a = hash(i), b = hash(i+vec2(1,0)), c = hash(i+vec2(0,1)), d = hash(i+vec2(1,1));
    return mix(mix(a,b,f.x), mix(c,d,f.x), f.y);
  }
  float fbm(vec2 p){
    float s = 0.0, a = 0.5;
    for (int i=0;i<3;i++){ s += a*vnoise(p); p*=2.03; a*=0.5; }
    return s;
  }

  // Antialiased grid: distance to the nearest cell border, in pixels.
  float gridMask(vec2 coord, float width){
    vec2 g = abs(fract(coord - 0.5) - 0.5) / fwidth(coord);
    return 1.0 - min(min(g.x, g.y) / width, 1.0);
  }

  void main(){
    // --- terrain tone: fbm mottle, tiny amplitude ---
    float m = fbm(vWorld * 0.09);
    vec3 col = uBase * (0.86 + m * 0.28);          // ±~14% brightness swells
    col += (sin(vWorld.y * 26.0) * 0.5 + 0.5) * 0.006; // fine crust grain (kept from R1)

    // --- the crust cools toward the rim: older, deeper, less lit ---
    vec2 n = clamp(vWorld / uHalf, -1.0, 1.0);
    float rim = smoothstep(0.55, 1.0, max(abs(n.x), abs(n.y)));
    col *= 1.0 - rim * 0.35 * (0.6 + m * 0.8); // fbm-broken so it reads as sediment

    // --- grid (unchanged — the placement affordance) ---
    col = mix(col, uLine, gridMask(vWorld, 1.0) * 0.85);
    col = mix(col, uMajor, gridMask(vWorld / 8.0, 1.0) * 0.9);

    // --- world-edge dissolve into the void ---
    vec2 d = abs(vWorld) / uHalf;
    float inside = 1.0 - smoothstep(0.82, 1.0, max(d.x, d.y));
    gl_FragColor = vec4(mix(uBase, col, inside), 1.0);

    // COLOR MANAGEMENT — load-bearing, not boilerplate. THREE.Color.setHex() converts
    // sRGB -> the linear working space, but a raw ShaderMaterial gets none of the output
    // includes, so without this the linear value goes to the framebuffer untouched and
    // the floor renders ~6x too dark: #0c0a15 measured as RGB(1,1,2). Every built-in
    // material does this via the same chunk.
    #include <colorspace_fragment>
  }
`;

export function createGround(): THREE.Mesh {
  const mat = new THREE.ShaderMaterial({
    uniforms: {
      uBase: { value: new THREE.Color(WORLD.crust) },
      uLine: { value: new THREE.Color(WORLD.gridLine) },
      uMajor: { value: new THREE.Color(WORLD.gridMajor) },
      uHalf: { value: WORLD_HALF },
    },
    vertexShader: VERT,
    fragmentShader: FRAG,
  });
  const mesh = new THREE.Mesh(new THREE.PlaneGeometry(PLANE, PLANE), mat);
  mesh.rotation.x = -Math.PI / 2; // +y up; tile (x,y) -> world (x, 0, y)
  mesh.receiveShadow = true;
  mesh.renderOrder = -1;
  mesh.name = 'ground';
  return mesh;
}

/** Screen-space vignette. Cheap, and keeps post-processing out of LANE-SCREEN's way. */
export function createVignette(): HTMLDivElement {
  const el = document.createElement('div');
  el.style.cssText = [
    'position:absolute',
    'inset:0',
    'pointer-events:none',
    'background:radial-gradient(ellipse at center, rgba(0,0,0,0) 45%, rgba(4,3,9,0.78) 100%)',
  ].join(';');
  return el;
}
