// iso-engine.jsx
// Isometric projection + blocky primitives for the Doppio "toy city" hero.
// Everything is built from cuboids (3 polygons: top + two side faces) and a
// few simple shapes (circles, rhombi) so the scene reads crisp & Lego-ish,
// SimCity / Transport Tycoon style. All geometry is generated in grid units
// and projected to a fixed SVG viewBox; the stage scales the whole SVG, so
// the world behaves like one illustration.

// ── Projection (2:1 dimetric) ────────────────────────────────────────────
const TW = 42;        // half tile width  (x/y axis run)
const TH = 21;        // half tile height (= TW/2 → classic 2:1 iso)
const ZH = 27;        // pixels of screen-rise per 1 height unit

function iso(x, y, z = 0) {
  return { x: (x - y) * TW, y: (x + y) * TH - z * ZH };
}

// Brighten / darken a hex. amount in [-1,1]: -=darker, +=lighter.
function shade(hex, amount) {
  const n = parseInt(hex.replace('#', ''), 16);
  let r = (n >> 16) & 255, g = (n >> 8) & 255, b = n & 255;
  if (amount < 0) { const f = 1 + amount; r *= f; g *= f; b *= f; }
  else { r += (255 - r) * amount; g += (255 - g) * amount; b += (255 - b) * amount; }
  const h = (v) => Math.round(v).toString(16).padStart(2, '0');
  return '#' + h(r) + h(g) + h(b);
}

const pts = (arr) => arr.map((p) => `${p.x.toFixed(1)},${p.y.toFixed(1)}`).join(' ');

// ── Cuboid: a single block ─────────────────────────────────────────────────
// gx,gy = footprint origin (grid). w,d = footprint size. h = height (units).
// z = base elevation. Returns an SVG <g> with top + right + left faces.
function Cuboid({ gx, gy, w, d, h, color, z = 0, stroke = true, topColor, rightColor, leftColor, outline }) {
  const t = (X, Y) => iso(X, Y, z + h);
  const b = (X, Y) => iso(X, Y, z);
  const top   = [t(gx, gy), t(gx + w, gy), t(gx + w, gy + d), t(gx, gy + d)];
  const right = [t(gx + w, gy), t(gx + w, gy + d), b(gx + w, gy + d), b(gx + w, gy)];
  const left  = [t(gx, gy + d), t(gx + w, gy + d), b(gx + w, gy + d), b(gx, gy + d)];
  const cTop = topColor || color;
  const cR = rightColor || shade(color, -0.16);
  const cL = leftColor || shade(color, -0.32);
  const ol = outline || shade(color, -0.5);
  const sw = stroke ? 1 : 0;
  return (
    <g strokeLinejoin="round" strokeWidth={sw} stroke={stroke ? ol : 'none'}>
      <polygon points={pts(left)}  fill={cL} />
      <polygon points={pts(right)} fill={cR} />
      <polygon points={pts(top)}   fill={cTop} />
    </g>
  );
}

// Flat slab (a low cuboid used for ground plates / plazas / roads).
function Slab({ gx, gy, w, d, color, z = 0, h = 0.34, stroke = true, outline }) {
  return <Cuboid gx={gx} gy={gy} w={w} d={d} h={h} z={z} color={color} stroke={stroke} outline={outline} />;
}

// A little citizen — the "audience". Body block + round head, optional phone.
// They are the flipped version of Hero E's shadowed crowd: now tiny bright
// voters dotted around the plaza.
function Person({ gx, gy, color = '#E8552E', phone = false, z = 0 }) {
  const foot = iso(gx, gy, z);
  const bodyH = 0.62, headR = 6.4;
  const top = iso(gx, gy, z + bodyH);
  const skin = '#F4D9B8';
  return (
    <g>
      {/* soft contact shadow */}
      <ellipse cx={foot.x} cy={foot.y + 2} rx={11} ry={5} fill="rgba(43,38,32,0.16)" />
      {/* body as a slim cuboid */}
      <Cuboid gx={gx - 0.16} gy={gy - 0.16} w={0.32} d={0.32} h={bodyH} z={z} color={color} stroke />
      {/* head */}
      <circle cx={top.x} cy={top.y - headR + 1} r={headR} fill={skin} stroke={shade(skin, -0.3)} strokeWidth="1" />
      {phone && <rect x={top.x + 5} y={top.y - 3} width="5" height="8" rx="1.2" fill="#2B2620" stroke={shade(color, 0.2)} strokeWidth="0.8" transform={`rotate(18 ${top.x + 7} ${top.y + 1})`} />}
    </g>
  );
}

// Chunky toy tree — trunk block + a stacked round-ish canopy (two circles).
function Tree({ gx, gy, color = '#2BA46A', z = 0 }) {
  const foot = iso(gx, gy, z);
  const top = iso(gx, gy, z + 0.5);
  return (
    <g>
      <ellipse cx={foot.x} cy={foot.y + 2} rx={12} ry={5.5} fill="rgba(43,38,32,0.16)" />
      <Cuboid gx={gx - 0.1} gy={gy - 0.1} w={0.2} d={0.2} h={0.5} z={z} color="#9A6B3E" stroke />
      <circle cx={top.x} cy={top.y - 16} r={14} fill={shade(color, 0.06)} stroke={shade(color, -0.4)} strokeWidth="1" />
      <circle cx={top.x - 7} cy={top.y - 9} r={9} fill={color} stroke={shade(color, -0.4)} strokeWidth="1" />
      <circle cx={top.x + 7} cy={top.y - 10} r={8} fill={shade(color, -0.1)} stroke={shade(color, -0.4)} strokeWidth="1" />
    </g>
  );
}

// A small street lamp / signpost with a glowing top.
function Lamp({ gx, gy, accent = '#F2B23D', z = 0 }) {
  const base = iso(gx, gy, z);
  const top = iso(gx, gy, z + 1.5);
  return (
    <g>
      <Cuboid gx={gx - 0.05} gy={gy - 0.05} w={0.1} d={0.1} h={1.5} z={z} color="#5B5550" stroke />
      <circle cx={top.x} cy={top.y - 4} r={5} fill={accent} stroke={shade(accent, -0.4)} strokeWidth="1" />
      <circle cx={top.x} cy={top.y - 4} r={9} fill={accent} opacity="0.18" />
    </g>
  );
}

// Floppy disk lying flat (a tiny iso prop, nods to the 90s-tech references).
function Floppy({ gx, gy, color = '#2E7DD6', z = 0 }) {
  const a = iso(gx, gy, z), b = iso(gx + 0.7, gy, z), c = iso(gx + 0.7, gy + 0.7, z), d = iso(gx, gy + 0.7, z);
  const m = iso(gx + 0.35, gy + 0.18, z);
  return (
    <g stroke={shade(color, -0.45)} strokeWidth="1" strokeLinejoin="round">
      <polygon points={pts([a, b, c, d])} fill={color} />
      <rect x={m.x - 7} y={m.y - 3} width="14" height="9" rx="1" fill="#EDE6D2" stroke="none" transform={`skewX(-26)`} opacity="0" />
    </g>
  );
}

// Blocky "minecraft/lego" person — seated audience member seen from behind:
// torso + wider shoulders + a hair-wrapped head. The extra shoulder block
// gives each figure a readable silhouette instead of a plain domino.
function BlockPerson({ gx, gy, z = 0, shirt = '#E8552E', hair = '#3A2A22', skin = '#F4D9B8', s = 1 }) {
  const torsoW = 0.30 * s, torsoD = 0.28 * s, torsoH = 0.30 * s;
  const shW = 0.48 * s, shD = 0.34 * s, shH = 0.22 * s;
  const headS = 0.30 * s, headH = 0.32 * s;
  const foot = iso(gx, gy, z);
  const hairR = shade(hair, -0.16), hairL = shade(hair, -0.32);
  const shadeShirt = shade(shirt, -0.05);
  return (
    <g>
      <ellipse cx={foot.x} cy={foot.y + 1.5} rx={11 * s} ry={4.8 * s} fill="rgba(43,38,32,0.18)" />
      {/* lower torso */}
      <Cuboid gx={gx - torsoW / 2} gy={gy - torsoD / 2} w={torsoW} d={torsoD} h={torsoH} z={z} color={shadeShirt} />
      {/* shoulders — wider block reads as upper body */}
      <Cuboid gx={gx - shW / 2} gy={gy - shD / 2} w={shW} d={shD} h={shH} z={z + torsoH} color={shirt} />
      {/* head, hair-wrapped (we see the back of the head) */}
      <Cuboid gx={gx - headS / 2} gy={gy - headS / 2} w={headS} d={headS} h={headH} z={z + torsoH + shH}
        color={hair} topColor={hair} rightColor={hairR} leftColor={hairL} />
    </g>
  );
}

Object.assign(window, { iso, shade, pts, Cuboid, Slab, Person, Tree, Lamp, Floppy, BlockPerson, ISO_TW: TW, ISO_TH: TH, ISO_ZH: ZH });


