/**
 * Logo generator — one geometry source, emits:
 *   web/public/logo.svg          (crisp, themeable, used across the web + favicon)
 *   extension/icons/icon{16,48,128}.png  (Chrome needs raster; pure-Node, no deps)
 *
 * The mark: a neon TESSERACT (hypercube — a cube nested in a cube, all eight
 * corners linked) framing a black-hole void at its centre, in violet + tritium
 * frost blue. A floating geometric artifact. Run: node gen-logo.mjs
 */
import fs from 'node:fs';
import path from 'node:path';
import zlib from 'node:zlib';
import { fileURLToPath } from 'node:url';

const ROOT = path.dirname(fileURLToPath(import.meta.url));

const VIOLET = [177, 77, 255];
const FROST = [95, 215, 238];
const LINK = [150, 140, 245]; // violet↔frost connectors
const CORE = [235, 225, 255];

// ---- geometry: a 4-cube (tesseract) as nested 3-cubes -----------------------
const CUBE = [];
for (const x of [-1, 1]) for (const y of [-1, 1]) for (const z of [-1, 1]) CUBE.push([x, y, z]);
const CUBE_EDGES = [];
for (let i = 0; i < 8; i++) for (let j = i + 1; j < 8; j++) {
  let d = 0; for (let k = 0; k < 3; k++) if (CUBE[i][k] !== CUBE[j][k]) d++;
  if (d === 1) CUBE_EDGES.push([i, j]);
}
const RX = 0.44, RY = 0.62; // tilt so it reads as a solid, 3D-ish object
function project(v, scale, cx, cy) {
  let [x, y, z] = v;
  const x1 = x * Math.cos(RY) + z * Math.sin(RY), z1 = -x * Math.sin(RY) + z * Math.cos(RY);
  const y1 = y * Math.cos(RX) - z1 * Math.sin(RX);
  return [cx + x1 * scale, cy + y1 * scale];
}
// Returns { outer:[8×[x,y]], inner:[8×[x,y]] } for a given size.
function tesseract(size) {
  const c = size / 2, A = size * 0.30, B = A * 0.44;
  return {
    outer: CUBE.map(v => project(v, A, c, c)),
    inner: CUBE.map(v => project(v, B, c, c)),
  };
}

// ---- SVG --------------------------------------------------------------------
function buildSVG() {
  const S = 200, t = tesseract(S);
  const V = 'var(--violet, #b14dff)', F = 'var(--frost, #5fd7ee)', L = 'rgb(' + LINK.join(',') + ')';
  const seg = (a, b, col, sw, op) =>
    `<line x1="${a[0].toFixed(1)}" y1="${a[1].toFixed(1)}" x2="${b[0].toFixed(1)}" y2="${b[1].toFixed(1)}" stroke="${col}" stroke-width="${sw}" stroke-linecap="round" opacity="${op}"/>`;
  const links = t.outer.map((o, i) => seg(o, t.inner[i], L, 1.6, 0.6)).join('');
  const outerEdges = CUBE_EDGES.map(([i, j]) => seg(t.outer[i], t.outer[j], V, 3, 0.95)).join('');
  const innerEdges = CUBE_EDGES.map(([i, j]) => seg(t.inner[i], t.inner[j], F, 2.2, 0.95)).join('');
  return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${S} ${S}" role="img" aria-label="RHA">
  <defs>
    <filter id="glow" x="-40%" y="-40%" width="180%" height="180%">
      <feGaussianBlur stdDeviation="2.6" result="b"/>
      <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
    </filter>
    <radialGradient id="hole" cx="50%" cy="50%" r="30%">
      <stop offset="0%" stop-color="#000"/>
      <stop offset="55%" stop-color="#000" stop-opacity="0.92"/>
      <stop offset="100%" stop-color="#000" stop-opacity="0"/>
    </radialGradient>
  </defs>
  <g filter="url(#glow)">${links}${outerEdges}</g>
  <ellipse cx="${S / 2}" cy="${S / 2}" rx="34" ry="26" fill="url(#hole)"/>
  <g filter="url(#glow)">${innerEdges}</g>
  <circle cx="${S / 2}" cy="${S / 2}" r="2.2" fill="rgb(${CORE.join(',')})"/>
</svg>
`;
}

// ---- PNG (pure Node) --------------------------------------------------------
const crc32 = (() => {
  if (typeof zlib.crc32 === 'function') return (b) => zlib.crc32(b) >>> 0;
  const T = new Uint32Array(256);
  for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; T[n] = c >>> 0; }
  return (buf) => { let c = 0xffffffff; for (const b of buf) c = T[(c ^ b) & 0xff] ^ (c >>> 8); return (c ^ 0xffffffff) >>> 0; };
})();
function encodePNG(W, H, rgba) {
  const stride = W * 4, raw = Buffer.alloc((stride + 1) * H);
  for (let y = 0; y < H; y++) { raw[y * (stride + 1)] = 0; rgba.copy(raw, y * (stride + 1) + 1, y * stride, y * stride + stride); }
  const idat = zlib.deflateSync(raw, { level: 9 });
  const chunk = (type, data) => {
    const len = Buffer.alloc(4); len.writeUInt32BE(data.length, 0);
    const body = Buffer.concat([Buffer.from(type, 'ascii'), data]);
    const crc = Buffer.alloc(4); crc.writeUInt32BE(crc32(body), 0);
    return Buffer.concat([len, body, crc]);
  };
  const ihdr = Buffer.alloc(13);
  ihdr.writeUInt32BE(W, 0); ihdr.writeUInt32BE(H, 4); ihdr[8] = 8; ihdr[9] = 6;
  return Buffer.concat([Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), chunk('IHDR', ihdr), chunk('IDAT', idat), chunk('IEND', Buffer.alloc(0))]);
}
function raster(size) {
  const W = size, H = size, buf = Buffer.alloc(W * H * 4);
  for (let i = 0; i < W * H; i++) buf[i * 4 + 3] = 255;
  const brush = Math.max(0.9, size * 0.03);
  const add = (x, y, col, inten) => {
    x = Math.round(x); y = Math.round(y);
    if (x < 0 || y < 0 || x >= W || y >= H) return;
    const i = (y * W + x) * 4;
    for (let ch = 0; ch < 3; ch++) buf[i + ch] = Math.min(255, buf[i + ch] + col[ch] * inten);
  };
  const stamp = (x, y, col, inten, rad) => {
    const s = rad / 2.1, R = Math.ceil(rad);
    for (let dy = -R; dy <= R; dy++) for (let dx = -R; dx <= R; dx++) {
      const d = Math.hypot(dx, dy); if (d > rad) continue;
      add(x + dx, y + dy, col, inten * Math.exp(-(d * d) / (2 * s * s)));
    }
  };
  const line = (a, b, col, inten, rad) => {
    const steps = Math.ceil(Math.hypot(b[0] - a[0], b[1] - a[1]));
    for (let s = 0; s <= steps; s++) { const u = s / steps; stamp(a[0] + (b[0] - a[0]) * u, a[1] + (b[1] - a[1]) * u, col, inten, rad); }
  };
  const t = tesseract(size);
  // At toolbar sizes the inner cube + connectors turn to noise. Draw a clean
  // outer cube + glowing core only; render the full tesseract when there's
  // room for it to read.
  const tiny = size <= 24;
  if (!tiny) {
    t.outer.forEach((o, i) => line(o, t.inner[i], LINK, 0.4, brush * 0.8));
  }
  CUBE_EDGES.forEach(([i, j]) => line(t.outer[i], t.outer[j], VIOLET, tiny ? 0.85 : 0.62, brush * (tiny ? 0.7 : 1)));
  if (!tiny) {
    CUBE_EDGES.forEach(([i, j]) => line(t.inner[i], t.inner[j], FROST, 0.55, brush * 0.85));
  }
  // carve the black-hole void back to black at the centre
  const c = size / 2, hole = size * (tiny ? 0.16 : 0.11);
  for (let y = 0; y < H; y++) for (let x = 0; x < W; x++) {
    const d = Math.hypot(x - c, y - c);
    if (d < hole) { const f = 1 - d / hole; const i = (y * W + x) * 4; for (let ch = 0; ch < 3; ch++) buf[i + ch] = Math.round(buf[i + ch] * (1 - f)); }
  }
  stamp(c, c, CORE, 0.8, Math.max(0.9, size * 0.014));
  return buf;
}

// ---- emit -------------------------------------------------------------------
fs.mkdirSync(path.join(ROOT, 'web', 'public'), { recursive: true });
fs.writeFileSync(path.join(ROOT, 'web', 'public', 'logo.svg'), buildSVG());
console.log('web/public/logo.svg');
fs.mkdirSync(path.join(ROOT, 'extension', 'icons'), { recursive: true });
for (const size of [16, 48, 128]) {
  const png = encodePNG(size, size, raster(size));
  fs.writeFileSync(path.join(ROOT, 'extension', 'icons', `icon${size}.png`), png);
  console.log(`extension/icons/icon${size}.png (${png.length} bytes)`);
}
