// AccentBlock.jsx — the floating "accent" cube that sits above the canvas
// at a 45° rotation around its vertical axis. Its dimensions are tied to the
// CANVAS volume (not to other blocks), so a 2×2×2 canvas gets a smaller
// accent than a 3×3×3 canvas. Always one accent at a time.
//
// Geometry (world units):
//   - centered on the cube top face: (W/2, D/2)
//   - floats `ACCENT_FLOAT` units above the cube top (z = H + ACCENT_FLOAT)
//   - cube edge length scales with canvas: roughly 0.85 × min(W, D)
//   - rotated 45° around z, so its faces present as diamond tips toward camera
//
// We compute the 8 corners with explicit (x, y, z) world coords (rotated)
// then project each via the same iso() helper used everywhere else, so the
// accent integrates with the rest of the scene's perspective.

const ACCENT_FLOAT = 1.0;      // gap between cube top (z=H) and accent bottom

// Edge length of the accent block in world units, given canvas dims.
function accentEdgeFor(W, D, H) {
  // Use the smaller of W/D so it never visually overflows the cube top.
  // 0.425 = half of the previous 0.85, so volume is 1/8 of before
  // (8 of these would fit in the old footprint).
  return 0.5 * Math.min(W, D);
}

// How much vertical room the accent + its float gap takes above z=H.
// Used by the canvas wireframe to extend its ceiling upward.
function accentSlotHeight(W, D, H) {
  // Edge × 0.5 since the rotated cube projects roughly to its half-edge
  // height above its center, plus the float gap, plus a little padding.
  // For a 2-canvas: 0.85*2*0.5 + 0.35 + 0.25 ≈ 1.45
  // For a 3-canvas: 0.85*3*0.5 + 0.35 + 0.25 ≈ 1.875
  // NOTE: this intentionally uses the OLD float gap (0.35) rather than
  // ACCENT_FLOAT, so lifting the accent doesn't push the canvas ceiling
  // up with it.
  const edge = accentEdgeFor(W, D, H);
  return edge + 0.35 + 0.25;
}

// Compute the 8 corner positions of the accent cube in world space.
// We render it with the SAME iso projection as every other block, but
// we rotate the WHOLE projected SVG group by a 2D angle (around the
// projected center) to get the diamond/tilted look the user asked for.
// (Rotating in 3D world-space around z would collapse the cube to a
// flat edge in our 22.5° iso, since +x and +y rotate onto the same
// screen-x ridge.)
function _accentCorners(W, D, H) {
  const edge = accentEdgeFor(W, D, H);
  // Bottom-back corner of the accent (world coords). Center accent over
  // canvas in (x,y) — its footprint runs from (cx, cy) to (cx+edge, cy+edge).
  // Then nudge along the screen-left diagonal (-x, +y) so the tilted
  // accent reads as visually centered over the canvas.
  const SHIFT = 0.15;
  const cx = W / 2 - edge / 2 - SHIFT;
  const cy = D / 2 - edge / 2 + SHIFT;
  const cz = H + ACCENT_FLOAT;

  // Project all 8 corners exactly like a regular block.
  const projected = {
    B00: iso(cx,        cy,        cz),
    B10: iso(cx + edge, cy,        cz),
    B11: iso(cx + edge, cy + edge, cz),
    B01: iso(cx,        cy + edge, cz),
    T00: iso(cx,        cy,        cz + edge),
    T10: iso(cx + edge, cy,        cz + edge),
    T11: iso(cx + edge, cy + edge, cz + edge),
    T01: iso(cx,        cy + edge, cz + edge),
  };

  // Center of the bottom face — this is the SVG-space pivot we'll rotate
  // the whole accent group around so the rotation feels grounded.
  const center = {
    x: (projected.B00.x + projected.B10.x + projected.B11.x + projected.B01.x) / 4,
    y: (projected.B00.y + projected.B10.y + projected.B11.y + projected.B01.y) / 4,
  };

  // World-space bottom corners (used by AccentShadow).
  const world = {
    B00: { x: cx,        y: cy,        z: cz },
    B10: { x: cx + edge, y: cy,        z: cz },
    B11: { x: cx + edge, y: cy + edge, z: cz },
    B01: { x: cx,        y: cy + edge, z: cz },
  };

  return { projected, center, edge, world };
}

// Visual rotation (degrees) applied to the accent SVG group around its
// projected center. This is a SCREEN-SPACE rotation — it doesn't change
// the iso projection, just twists the rendered cube on the page so we get
// a tilted/diamond reading without collapsing faces edge-on.
const ACCENT_TILT_DEG = 35;

// Render the accent block (the cube itself).
// `block` is { id, color, label, accent: true }.
function AccentBlock({ block, W, D, H, selected }) {
  const pal = PALETTE[block.color] || PALETTE.green;
  const { projected: P, edge, center } = _accentCorners(W, D, H);
  const labelScale = block.labelScale ?? 1;

  const poly = (a, b, c, d) => `${a.x},${a.y} ${b.x},${b.y} ${c.x},${c.y} ${d.x},${d.y}`;

  const topFace   = poly(P.T00, P.T10, P.T11, P.T01);
  const rightFace = poly(P.T10, P.B10, P.B11, P.T11); // x=max face
  const leftFace  = poly(P.T01, P.T11, P.B11, P.B01); // y=max face

  // Label on the LEFT (dark, y=max) face, sheared into perspective so it
  // looks painted onto the face. Same approach as Block.jsx — build an
  // affine matrix from the already-projected face corners, render text
  // in face-local pixel space, let the matrix carry it onto the face.
  // The whole accent <g> is then screen-rotated by ACCENT_TILT_DEG, so
  // the label tilts along with the cube (which is what we want).
  const labelEl = (() => {
    if (!block.label) return null;

    // Left face corners in face-local order: TL, TR, BL.
    // u runs along the face's horizontal edge (top, T01 → T11), so width = edge.
    // v runs along the vertical edge (T01 → B01), so height = edge.
    const TL = P.T01, TR = P.T11, BL = P.B01;
    const wPx = edge * UNIT;
    const hPx = edge * UNIT;
    const a = (TR.x - TL.x) / wPx;
    const b = (TR.y - TL.y) / wPx;
    const c = (BL.x - TL.x) / hPx;
    const d = (BL.y - TL.y) / hPx;
    const e = TL.x;
    const f = TL.y;
    const matrix = `matrix(${a},${b},${c},${d},${e},${f})`;

    // ---- Word wrap. Try 1 line, then 2, then 3. Pick the largest font
    // that fits both width and height with comfortable padding.
    const padX = wPx * 0.12;
    const usableW = Math.max(wPx - padX * 2, 12);
    const usableH = hPx * 0.78;
    const charWidth = 0.58;
    const FONT_CAP = 28;
    const MIN_FONT = 9;

    const wrapWords = (text, maxCharsPerLine, maxLines) => {
      const words = text.split(/\s+/).filter(Boolean);
      if (!words.length) return [text];
      const lines = [];
      let current = '';
      for (const word of words) {
        if (lines.length >= maxLines) break;
        if (!current) { current = word; continue; }
        const candidate = current + ' ' + word;
        if (candidate.length <= maxCharsPerLine) current = candidate;
        else { lines.push(current); current = word; }
      }
      if (current && lines.length < maxLines) lines.push(current);
      return lines;
    };

    const fitN = (n) => {
      // pick chars-per-line from rough average, wrap, refit twice
      let cpl = Math.max(3, Math.ceil(block.label.length / n));
      let lines = wrapWords(block.label, cpl, n);
      let longest = lines.reduce((m, l) => Math.max(m, l.length), 1);
      let fsW = usableW / longest / charWidth;
      let fsH = (usableH / n) / 1.1;
      let fs = Math.min(FONT_CAP, fsW, fsH);
      // re-wrap using cpl implied by chosen font
      const cpl2 = Math.max(3, Math.floor(usableW / (fs * charWidth)));
      if (cpl2 !== cpl) {
        lines = wrapWords(block.label, cpl2, n);
        longest = lines.reduce((m, l) => Math.max(m, l.length), 1);
        fsW = usableW / longest / charWidth;
        fs = Math.min(FONT_CAP, fsW, fsH);
      }
      return { lines, fs };
    };

    // One-line fit (no wrapping)
    const oneLineFs = Math.min(
      FONT_CAP,
      usableW / Math.max(block.label.length, 1) / charWidth,
      usableH / 1.1,
    );

    // Build wrap candidates so the user-set scale can re-pick a wrap
    // that supports the target font size (re-wrapping as size grows).
    const oneLineCand = { lines: [block.label], fs: oneLineFs };
    const twoCand = fitN(2);
    const threeCand = fitN(3);
    const candidates = [oneLineCand, twoCand, threeCand];

    // Default pick (labelScale == 1): existing priority — keep one line
    // when readable; otherwise the largest wrap that meets MIN_FONT.
    let lines, fontSize;
    if (oneLineFs >= MIN_FONT && !/\s/.test(block.label)) {
      lines = [block.label];
      fontSize = oneLineFs;
    } else if (oneLineFs >= 14) {
      lines = [block.label];
      fontSize = oneLineFs;
    } else {
      const ok = candidates.filter((c) => c.fs >= MIN_FONT);
      if (ok.length) {
        ok.sort((a, b) => b.fs - a.fs);
        lines = ok[0].lines;
        fontSize = ok[0].fs;
      } else {
        lines = twoCand.lines;
        fontSize = MIN_FONT;
      }
    }

    // User scale: target = baseline * labelScale. When growing, re-pick
    // the wrap whose fitted fs >= target (preferring fewer lines).
    const baselineFs = fontSize;
    const targetFs = baselineFs * labelScale;
    if (labelScale > 1) {
      const supported = candidates.find((c) => c.fs >= targetFs);
      const pick = supported || candidates.reduce((b, c) => (c.fs > b.fs ? c : b));
      lines = pick.lines;
      fontSize = Math.min(targetFs, pick.fs);
    } else {
      fontSize = targetFs;
    }

    const lineHeight = fontSize * 1.1;
    const totalH = lineHeight * lines.length;
    const firstBaselineY = hPx / 2 - totalH / 2 + fontSize * 0.85;
    const cx = wPx / 2;

    return (
      <g transform={matrix}>
        {lines.map((line, i) => (
          <text
            key={i}
            x={cx}
            y={firstBaselineY + i * lineHeight}
            textAnchor="middle"
            fontSize={fontSize}
            fontFamily="var(--font-sans)"
            fontWeight="700"
            fill="#ffffff"
            style={{ pointerEvents: 'none', userSelect: 'none' }}
          >
            {line}
          </text>
        ))}
      </g>
    );
  })();

  const topHighlight = `${P.T00.x},${P.T00.y} ${P.T10.x},${P.T10.y} ${P.T11.x},${P.T11.y} ${P.T01.x},${P.T01.y} ${P.T00.x},${P.T00.y}`;

  return (
    <g
      style={{ cursor: 'pointer' }}
      transform={`rotate(${ACCENT_TILT_DEG}, ${center.x}, ${center.y})`}
    >
      {selected && (
        <polygon
          points={topFace}
          fill="none"
          stroke="var(--teal-400)"
          strokeWidth="2"
          strokeDasharray="5 3"
          style={{ pointerEvents: 'none' }}
        />
      )}

      <polygon points={leftFace}  fill={pal.left} />
      <polygon points={rightFace} fill={pal.right} />
      <polygon points={topFace}   fill={pal.top} />

      <polyline
        points={topHighlight}
        fill="none"
        stroke="rgba(255,255,255,0.35)"
        strokeWidth="1.25"
        strokeLinejoin="round"
        style={{ pointerEvents: 'none' }}
      />

      {labelEl}
    </g>
  );
}

// Render the accent's drop shadow on the cube top (z=H).
// Lives in its own component so the parent can interleave it with the
// painter's draw order — the shadow goes BEHIND the accent and on top of
// the canvas top face.
// Multi-phase accent shadow.
//
// Instead of a single flat polygon at z=H, we project the accent's
// rectangular footprint cell-by-cell onto the surface immediately below
// each cell. The "surface" is the highest block-top within that integer
// (gx,gy) column — or NOTHING if no block occupies that column. This
// produces a shadow that:
//
//   • Conforms to whatever heights the blocks underneath happen to be.
//   • Steps cleanly when crossing between blocks of different heights.
//   • Disappears entirely over empty cells (no floor-shadow over the void).
//
// We accept the canvas's blocks list as a prop and build a column-top
// map keyed on integer (gx, gy). For each cell the accent footprint
// overlaps, we emit a clipped shadow quad at that column's top z,
// nudged toward +x/+y so it visibly fringes past the casting accent.
function AccentShadow({ W, D, H, blocks = [] }) {
  const { world } = _accentCorners(W, D, H);
  // Axis-aligned XY footprint of the accent at its float plane. (The
  // accent itself is rotated 45° in screen space, but its WORLD-space
  // footprint is still the axis-aligned rectangle from B00 to B11.)
  const fx0 = world.B00.x;
  const fy0 = world.B00.y;
  const fx1 = world.B11.x;
  const fy1 = world.B11.y;

  // Build per-column top z. For each integer (ix, iy) we record the
  // highest (gz + dz) of any block whose footprint covers that cell.
  // Cells with no block stay undefined and produce no shadow tile.
  const colTop = new Map();
  for (const b of blocks) {
    const dx = b.dx || 1, dy = b.dy || 1, dz = b.dz || 1;
    const top = b.gz + dz;
    for (let ix = b.gx; ix < b.gx + dx; ix++) {
      for (let iy = b.gy; iy < b.gy + dy; iy++) {
        const key = `${ix},${iy}`;
        const cur = colTop.get(key);
        if (cur === undefined || top > cur) colTop.set(key, top);
      }
    }
  }

  // No camera-direction offset — the shadow tiles overlap each cell
  // exactly, so the shadow stays clipped to the block tops it's cast on
  // and never bleeds past their silhouettes into empty space.
  const ox = 0, oy = 0;

  // Iterate the integer cells the footprint overlaps. For a typical
  // accent edge (≈0.85·min(W,D)) this is up to ~min(W,D)+1 cells in
  // each axis — cheap.
  const cellMinX = Math.floor(fx0);
  const cellMaxX = Math.ceil(fx1);
  const cellMinY = Math.floor(fy0);
  const cellMaxY = Math.ceil(fy1);

  const tiles = [];
  for (let ix = cellMinX; ix < cellMaxX; ix++) {
    for (let iy = cellMinY; iy < cellMaxY; iy++) {
      const top = colTop.get(`${ix},${iy}`);
      if (top === undefined) continue; // no block below this cell — skip
      // Intersect cell [ix, ix+1] × [iy, iy+1] with the accent footprint.
      const x0 = Math.max(fx0, ix);
      const x1 = Math.min(fx1, ix + 1);
      const y0 = Math.max(fy0, iy);
      const y1 = Math.min(fy1, iy + 1);
      if (x1 - x0 <= 1e-6 || y1 - y0 <= 1e-6) continue;

      const z = top;
      const p0 = iso(x0 + ox, y0 + oy, z);
      const p1 = iso(x1 + ox, y0 + oy, z);
      const p2 = iso(x1 + ox, y1 + oy, z);
      const p3 = iso(x0 + ox, y1 + oy, z);
      tiles.push({ key: `${ix},${iy}`, pts: `${p0.x},${p0.y} ${p1.x},${p1.y} ${p2.x},${p2.y} ${p3.x},${p3.y}`, z });
    }
  }

  // Sort tiles back-to-front so any tile-to-tile overdraw paints
  // higher tiles after lower ones (matters when neighbouring cells
  // have different heights).
  tiles.sort((a, b) => a.z - b.z);

  return (
    <g style={{ pointerEvents: 'none', filter: 'blur(2.5px)' }}>
      {tiles.map((t) => (
        <polygon
          key={t.key}
          points={t.pts}
          fill="rgba(0, 0, 0, 0.32)"
        />
      ))}
    </g>
  );
}

// Public helper — return just the world-space axis-aligned XY footprint
// of the accent (the rectangle from B00 to B11 at the float plane).
// Used by the App to compute per-cell accent-shadow tiles that can be
// interleaved into the painter's draw order.
function accentFootprint(W, D, H) {
  const { world } = _accentCorners(W, D, H);
  return {
    x0: world.B00.x,
    y0: world.B00.y,
    x1: world.B11.x,
    y1: world.B11.y,
  };
}

window.AccentBlock = AccentBlock;
window.AccentShadow = AccentShadow;
window.accentSlotHeight = accentSlotHeight;
window.accentFootprint = accentFootprint;
