// Renders Starlock's glow shaders the way the cutscene does, in real WebGL (headless Chromium),
// and saves the raw pixels: the 8-bit intermediate target (linear light) and the canvas after the
// painterly pass (tone-mapped, sRGB). Uses the game's own three.js (public/ideas/starlock/vendor)
// and copies the painterly pass's shader out of reveal3d.js, so it renders what ships.
//
//   node --input-type=module < webgl.mjs.txt <spec.json> <out-prefix>
//
// (named .txt because the site serves code/ as text; see README.md)
// spec.json: { W, H, radius, stride (taps apart: the pixel ratio; default 1), quads: [{ frag, uniforms: {name: number|[r,g,b]}, x, y, w, h }],
//              noise: { size, data: [bytes] } (optional: a sampler2D uNoise, nearest, repeating) }
// writes <out-prefix>.target.rgba and <out-prefix>.canvas.rgba (W*H*4 bytes, rows bottom-up as GL reads them)
import fs from 'node:fs';
import path from 'node:path';
import { createRequire } from 'node:module';

const require = createRequire(path.join(process.cwd(), 'x.js'));
const { chromium } = (() => {
  for (const id of [process.env.PLAYWRIGHT, 'playwright', '/opt/node-tools/node_modules/playwright'].filter(Boolean)) {
    try { return require(id); } catch {}
  }
  throw new Error('Playwright not found: set PLAYWRIGHT to its path.');
})();

const [specFile, outPrefix] = process.argv.slice(2);
const starlock = process.env.STARLOCK || path.resolve(process.cwd(), '../../../public/ideas/starlock');
const three = fs.readFileSync(path.join(starlock, 'vendor/three.min.js'), 'utf8');
const reveal = fs.readFileSync(path.join(starlock, 'reveal3d.js'), 'utf8');
const kuwahara = reveal.slice(reveal.indexOf('fragmentShader: `uniform sampler2D tScene'));
const paintFrag = kuwahara.slice(kuwahara.indexOf('`') + 1, kuwahara.indexOf('`,\n}'));
const spec = JSON.parse(fs.readFileSync(specFile, 'utf8'));

const browser = await chromium.launch({ args: ['--use-gl=angle', '--use-angle=swiftshader', '--enable-unsafe-swiftshader'] });
const page = await browser.newPage();
await page.route('http://local/**', (r) => r.fulfill({ contentType: r.request().url().endsWith('.js') ? 'text/javascript' : 'text/html', body: r.request().url().endsWith('.js') ? three : '<!doctype html><canvas id=c></canvas>' }));
await page.goto('http://local/index.html');
const out = await page.evaluate(async ({ spec, paintFrag }) => {
  const T = await import('http://local/three.min.js');
  const { W, H } = spec;
  const canvas = document.getElementById('c');
  canvas.width = W; canvas.height = H;
  const renderer = new T.WebGLRenderer({ canvas, antialias: false, preserveDrawingBuffer: true });
  renderer.setPixelRatio(1); renderer.setSize(W, H, false);
  renderer.toneMapping = T.ACESFilmicToneMapping; renderer.toneMappingExposure = 1.15;
  const scene = new T.Scene(), cam = new T.OrthographicCamera(0, W, H, 0, -1, 1);
  const QUAD_V = 'varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.); }';
  let noise = null;
  if (spec.noise) {
    const n = spec.noise.size, c = document.createElement('canvas'); c.width = c.height = n;
    const g = c.getContext('2d'), img = g.createImageData(n, n);
    for (let i = 0; i < n * n; i++) { const v = spec.noise.data[i]; img.data.set([v, v, v, 255], i * 4); }
    g.putImageData(img, 0, 0);
    noise = new T.CanvasTexture(c); noise.minFilter = noise.magFilter = T.NearestFilter; noise.wrapS = noise.wrapT = T.RepeatWrapping; noise.generateMipmaps = false;
  }
  for (const q of spec.quads) {
    const uniforms = {};
    for (const [k, v] of Object.entries(q.uniforms)) uniforms[k] = { value: Array.isArray(v) ? new T.Color(...v) : v };
    if (noise) uniforms.uNoise = { value: noise };
    const m = new T.Mesh(new T.PlaneGeometry(1, 1), new T.ShaderMaterial({ vertexShader: QUAD_V, fragmentShader: q.frag, uniforms, transparent: true, blending: T.AdditiveBlending, depthWrite: false, side: T.DoubleSide, toneMapped: false }));
    m.position.set(q.x, q.y, 0); m.scale.set(q.w, q.h, 1); scene.add(m);
  }
  // the painterly pass, as reveal3d.js sets it up
  const target = new T.WebGLRenderTarget(W, H, { samples: 4 });
  const mat = new T.ShaderMaterial({ uniforms: { tScene: { value: target.texture }, uTexel: { value: new T.Vector2((spec.stride || 1) / W, (spec.stride || 1) / H) } },
    vertexShader: 'varying vec2 vUv; void main() { vUv = uv; gl_Position = vec4(position.xy, 0., 1.); }', fragmentShader: paintFrag,
    defines: { RADIUS: spec.radius }, depthTest: false, depthWrite: false });
  const post = new T.Scene(); post.add(new T.Mesh(new T.PlaneGeometry(2, 2), mat));
  const pcam = new T.OrthographicCamera(-1, 1, 1, -1, 0, 1);
  renderer.setRenderTarget(target); renderer.setClearColor(0x000000, 1); renderer.clear(); renderer.render(scene, cam);
  const tgt = new Uint8Array(W * H * 4); renderer.readRenderTargetPixels(target, 0, 0, W, H, tgt);
  renderer.setRenderTarget(null); renderer.render(post, pcam);
  const gl = renderer.getContext(), cv = new Uint8Array(W * H * 4); gl.readPixels(0, 0, W, H, gl.RGBA, gl.UNSIGNED_BYTE, cv);
  const info = { renderer: gl.getParameter(gl.RENDERER), highp: gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT).precision };
  const b64 = (a) => { let s = ''; for (let i = 0; i < a.length; i += 0x8000) s += String.fromCharCode(...a.subarray(i, i + 0x8000)); return btoa(s); };
  return { tgt: b64(tgt), cv: b64(cv), info };
}, { spec, paintFrag });
await browser.close();
fs.writeFileSync(outPrefix + '.target.rgba', Buffer.from(out.tgt, 'base64'));
fs.writeFileSync(outPrefix + '.canvas.rgba', Buffer.from(out.cv, 'base64'));
console.error(JSON.stringify(out.info));
