'use strict';
// Erudit app-icon — the reference generator. Every dimension below is a FRACTION
// of the icon side, so this file reads the same as docs/ICON_BRANDBOOK.md and the
// icon reproduces at any resolution. Emits one variant/layer's SVG to stdout.
//
//   node build-icon.mjs --variant light|dark [--layer full|background|foreground] [--scheme] > icon.svg
//
// Layers (for Android adaptive icons):
//   full        the standalone master (rounded tile + mark) — iOS / PWA / favicon
//   background  wood + grain + light/shadow, full-bleed square (the OS applies the mask)
//   foreground  only «Э» + ✻, transparent, re-placed for the round mask (see FG_* below)
//
// Deps: opentype.js (npm i opentype.js). Font: ./Spectral-Bold.ttf (OFL, bundled).
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import opentype from 'opentype.js';

const DIR = dirname(fileURLToPath(import.meta.url));
const args = process.argv.slice(2);
const has = f => args.includes('--' + f);
const val = (f, d) => { const i = args.indexOf('--' + f); return i >= 0 ? args[i + 1] : d; };

// ---- the design, in fractions of the side ----
const S = 1024;                       // reference side (the icon is resolution-free)
const RADIUS   = 0.17;                // tile corner radius (full master)
const STAR_BULB = 0.22;               // spoke-end (and hub) radius, as fraction of star radius
const STAR_SPOKES = 6;
const GRAIN_COLS = 6, GRAIN_W = 1 / 32, GRAIN_SHIFT = 1 / 16, GRAIN_TILT = 4;
const SHEEN = 0.15;                   // white, transparent bottom-left -> this alpha top-right
const SHADE = 0.15;                   // black, this alpha bottom-left -> transparent top-right

// The mark, placed for the standalone master (bottom-right biased, full-bleed):
const MASTER = { lh: 0.6055, lc: [0.4814, 0.4922], sd: 0.1729, sc: [0.7881, 0.7881] };
// The mark, placed for the Android foreground layer (centred-ish inside the 61% safe
// zone, nudged right 8% / down 3% for optical balance under the round mask; smaller ✻).
// Every value is the earlier placement scaled 0.75 about the icon centre (0.5, 0.5): the
// whole mark is 25% smaller, its «Э»↔✻ arrangement unchanged, so the ✻ that used to graze
// the round mask now sits well inside the safe zone. Round-mask launchers (adaptive + the
// legacy round icon) get more breathing room; the full-bleed master is untouched.
const FOREGROUND = { lh: 0.4043, lc: [0.5176, 0.4963], sd: 0.0989, sc: [0.6970, 0.6700] };

const PALETTE = {
  light: { wood: '#e6b053', ink: '#5a3a12', grain: '#d8a54e' },
  dark:  { wood: '#4a3316', ink: '#f2d9a0', grain: '#432e14' },
};

// Layers:
//   full        rounded tile + master mark        (favicon.svg — browser rounds nothing)
//   flat        square tile + master mark          (apple-touch, PWA "any" — OS rounds)
//   background  square tile, no mark               (Android adaptive background)
//   foreground  mark only, transparent             (Android adaptive foreground)
//   maskable    square tile + foreground mark       (PWA maskable — safe-zone aware)
//   monochrome  foreground mark only, flat colour  (Android 13+ themed layer)
const LAYERS = ['full', 'flat', 'background', 'foreground', 'maskable', 'monochrome'];
const variant = val('variant', 'light');
const layer = val('layer', 'full');
const P = PALETTE[variant];
if (!P) { console.error(`unknown --variant ${variant} (light|dark)`); process.exit(1); }
if (!LAYERS.includes(layer)) { console.error(`unknown --layer ${layer} (${LAYERS.join('|')})`); process.exit(1); }

const usesForegroundPlacement = ['foreground', 'maskable', 'monochrome'].includes(layer);
const drawBg = ['full', 'flat', 'background', 'maskable'].includes(layer);
const drawMark = layer !== 'background';
const MARK = usesForegroundPlacement ? FOREGROUND : MASTER;
const inkColour = layer === 'monochrome' ? val('mono', '#ffffff') : P.ink;
const RX = layer === 'full' ? RADIUS * S : 0;   // only the standalone master keeps rounded corners
const font = opentype.parse(readFileSync(join(DIR, 'Spectral-Bold.ttf')).buffer);

// «Э» outline scaled so its ink bounding box is MARK.lh of the side, box centred on MARK.lc.
function letterSvg() {
  const g = font.charToGlyph('Э');
  const h0 = (() => { const b = g.getPath(0, 0, 1000).getBoundingBox(); return b.y2 - b.y1; })();
  const scale = (MARK.lh * S) / h0;
  const p = g.getPath(0, 0, 1000 * scale);
  const bb = p.getBoundingBox();
  const w = bb.x2 - bb.x1, h = bb.y2 - bb.y1;
  const tx = MARK.lc[0] * S - (bb.x1 + w / 2);
  const ty = MARK.lc[1] * S - (bb.y1 + h / 2);
  return { svg: `<path transform="translate(${tx.toFixed(2)} ${ty.toFixed(2)})" d="${p.toPathData(2)}" fill="${inkColour}"/>`,
           box: { x: bb.x1 + tx, y: bb.y1 + ty, w, h } };
}

// Teardrop-spoked asterisk ✻: each spoke tapers to a point at the centre and ends
// in a round bulb; a central disc equal to the bulb fuses them.
function starPath() {
  const cx = MARK.sc[0] * S, cy = MARK.sc[1] * S, Rout = MARK.sd * S / 2;
  const rb = Rout * STAR_BULB, R = Rout - rb;
  const L = Math.sqrt(R * R - rb * rb), theta = Math.asin(rb / R);
  const rot = (v, t) => [v[0] * Math.cos(t) - v[1] * Math.sin(t), v[0] * Math.sin(t) + v[1] * Math.cos(t)];
  let d = '';
  for (let k = 0; k < STAR_SPOKES; k++) {
    const a = -Math.PI / 2 + k * 2 * Math.PI / STAR_SPOKES;
    const u = [Math.cos(a), Math.sin(a)];
    const d1 = rot(u, theta), d2 = rot(u, -theta);
    const T1 = [cx + L * d1[0], cy + L * d1[1]], T2 = [cx + L * d2[0], cy + L * d2[1]];
    d += `M${cx.toFixed(2)} ${cy.toFixed(2)} L${T1[0].toFixed(2)} ${T1[1].toFixed(2)} A${rb.toFixed(2)} ${rb.toFixed(2)} 0 1 0 ${T2[0].toFixed(2)} ${T2[1].toFixed(2)} Z `;
  }
  d += `M${cx} ${cy} m${-rb} 0 a${rb} ${rb} 0 1 0 ${2 * rb} 0 a${rb} ${rb} 0 1 0 ${-2 * rb} 0 Z`;
  return `<path d="${d}" fill="${inkColour}"/>`;
}

function grain() {
  const colW = S / GRAIN_COLS, sw = GRAIN_W * S, shift = GRAIN_SHIFT * S;
  const yTop = -0.2 * S, yBot = 1.2 * S, cy = S / 2;
  let s = '';
  for (let i = 1; i <= GRAIN_COLS; i++) {
    const xr = i * colW - shift, x = xr - sw, cx = xr - sw / 2;
    s += `<rect x="${x.toFixed(2)}" y="${yTop.toFixed(2)}" width="${sw.toFixed(2)}" height="${(yBot - yTop).toFixed(2)}" fill="${P.grain}" transform="rotate(${GRAIN_TILT} ${cx.toFixed(2)} ${cy.toFixed(2)})"/>`;
  }
  return `<g clip-path="url(#tile)">${s}</g>`;
}

let body = '';
const L = letterSvg();
if (drawBg) {                           // tile + grain + light/shadow (the background)
  body += `<rect width="${S}" height="${S}" rx="${RX}" fill="${P.wood}"/>`
    + `<defs><clipPath id="tile"><rect width="${S}" height="${S}" rx="${RX}"/></clipPath>`
    +   `<linearGradient id="sheen" gradientUnits="userSpaceOnUse" x1="0" y1="${S}" x2="${S}" y2="0"><stop offset="0" stop-color="#fff" stop-opacity="0"/><stop offset="1" stop-color="#fff" stop-opacity="${SHEEN}"/></linearGradient>`
    +   `<linearGradient id="shade" gradientUnits="userSpaceOnUse" x1="0" y1="${S}" x2="${S}" y2="0"><stop offset="0" stop-color="#000" stop-opacity="${SHADE}"/><stop offset="1" stop-color="#000" stop-opacity="0"/></linearGradient>`
    + `</defs>`
    + grain()
    + `<rect width="${S}" height="${S}" fill="url(#sheen)" clip-path="url(#tile)"/>`
    + `<rect width="${S}" height="${S}" fill="url(#shade)" clip-path="url(#tile)"/>`;
}
if (drawMark) body += L.svg + starPath();   // the mark
if (has('scheme')) body += scheme(L);

process.stdout.write(`<svg xmlns="http://www.w3.org/2000/svg" width="${S}" height="${S}" viewBox="0 0 ${S} ${S}">${body}</svg>\n`);

// Percent-based construction overlay for the brand book (full master only).
function scheme(L) {
  const pc = n => (100 * n / S).toFixed(1) + '%';
  const GC = '#0f172a', BX = '#d81b60', AC = '#0d9488';
  const halo = 'paint-order="stroke" stroke="#fff" stroke-width="4"';
  const cx = MARK.sc[0] * S, cy = MARK.sc[1] * S, sd = MARK.sd * S;
  let g = `<g font-family="'Helvetica Neue',Arial,sans-serif">`;
  for (let f = 1; f <= 3; f++) {
    const p = f * S / 4;
    g += `<line x1="${p}" y1="0" x2="${p}" y2="${S}" stroke="${GC}" stroke-opacity="0.18" stroke-width="1.2"/>`;
    g += `<line x1="0" y1="${p}" x2="${S}" y2="${p}" stroke="${GC}" stroke-opacity="0.18" stroke-width="1.2"/>`;
    g += `<text x="${p + 4}" y="20" font-size="17" font-weight="700" fill="${GC}" ${halo}>${f * 25}%</text>`;
    g += `<text x="4" y="${p - 4}" font-size="17" font-weight="700" fill="${GC}" ${halo}>${f * 25}%</text>`;
  }
  g += `<line x1="${0.14 * S}" y1="${0.86 * S}" x2="${0.86 * S}" y2="${0.14 * S}" stroke="${AC}" stroke-width="3" stroke-dasharray="3 8" stroke-linecap="round"/>`;
  g += `<circle cx="${0.86 * S}" cy="${0.14 * S}" r="6" fill="${AC}"/><circle cx="${0.14 * S}" cy="${0.86 * S}" r="6" fill="${AC}"/>`;
  g += `<text x="${0.86 * S - 6}" y="${0.14 * S - 12}" text-anchor="end" font-size="18" font-weight="700" fill="${AC}" ${halo}>light: white 0%→15%</text>`;
  g += `<text x="${0.14 * S + 10}" y="${0.86 * S + 26}" font-size="18" font-weight="700" fill="${AC}" ${halo}>shadow: black 15%→0%</text>`;
  const { x, y, w, h } = L.box;
  g += `<rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${w.toFixed(1)}" height="${h.toFixed(1)}" fill="none" stroke="${BX}" stroke-width="2.5"/>`;
  g += `<line x1="${MARK.lc[0] * S}" y1="${MARK.lc[1] * S - 16}" x2="${MARK.lc[0] * S}" y2="${MARK.lc[1] * S + 16}" stroke="${BX}" stroke-width="2.5"/><line x1="${MARK.lc[0] * S - 16}" y1="${MARK.lc[1] * S}" x2="${MARK.lc[0] * S + 16}" y2="${MARK.lc[1] * S}" stroke="${BX}" stroke-width="2.5"/>`;
  g += `<text x="${x.toFixed(1)}" y="${(y - 10).toFixed(1)}" font-size="19" font-weight="700" fill="${BX}" ${halo}>«Э» Spectral Bold · box H ${pc(h)} · W ${pc(w)}</text>`;
  g += `<text x="${x.toFixed(1)}" y="${(y + h + 24).toFixed(1)}" font-size="18" font-weight="700" fill="${BX}" ${halo}>box center ${pc(MARK.lc[0] * S)} / ${pc(MARK.lc[1] * S)}</text>`;
  g += `<rect x="${cx - sd / 2}" y="${cy - sd / 2}" width="${sd}" height="${sd}" fill="none" stroke="${BX}" stroke-width="2.5"/>`;
  g += `<line x1="${cx}" y1="${cy - 14}" x2="${cx}" y2="${cy + 14}" stroke="${BX}" stroke-width="2.5"/><line x1="${cx - 14}" y1="${cy}" x2="${cx + 14}" y2="${cy}" stroke="${BX}" stroke-width="2.5"/>`;
  g += `<text x="${(cx + sd / 2).toFixed(1)}" y="${(cy - sd / 2 - 10).toFixed(1)}" text-anchor="end" font-size="18" font-weight="700" fill="${BX}" ${halo}>✻ Ø ${pc(sd)} · center ${pc(cx)} / ${pc(cy)}</text>`;
  g += `<text x="${RX + 14}" y="${RX - 6}" font-size="17" font-weight="700" fill="${GC}" ${halo}>corner r = 17%</text>`;
  g += `<path d="M4 ${RX} A ${RX} ${RX} 0 0 1 ${RX} 4" fill="none" stroke="${GC}" stroke-width="2" stroke-dasharray="5 5"/>`;
  g += `<rect x="${0.03 * S}" y="${S - 96}" width="${0.94 * S}" height="74" rx="12" fill="#0f172a" fill-opacity="0.82"/>`;
  g += `<text x="${0.055 * S}" y="${S - 64}" font-size="18" font-weight="600" fill="#fff">Wood grain: 6 equal columns; a vertical stripe on each column's right edge,</text>`;
  g += `<text x="${0.055 * S}" y="${S - 40}" font-size="18" font-weight="600" fill="#fff">width 1/32 of side; the whole set shifted left 1/16; every stripe tilted 4°.</text>`;
  return g + `</g>`;
}
