import { useEffect, useRef, memo } from 'react';
import { Group, Text, TextPath, Rect } from 'react-konva';
import { measureTextMetrics } from '../../utils/textMetrics';
import { computeTextVisibleBbox } from '../../utils/textGeometry';
import { useFontsReady } from '../../hooks/useFontsReady';
import { LINE_HEIGHT_RATIO, MIN_FONT_SIZE, DEFAULT_BODY_FONT } from '../../constants/textMetrics';
import { unflipReportedXY } from '../../utils/flipOffset';

// Per-element Transformer rendering was moved to DesignCanvas's dedicated
// transformer layer (see DesignCanvas.jsx). This component now renders
// ONLY the text node + its out-of-bounds warning; selection chrome lives
// one layer above. Imports for Transformer / rotation snap / corner
// anchors / transformer visual style are dropped along with the JSX
// that consumed them.
//
// Outline-on-silhouette rendering
// ───────────────────────────────
// Konva's default render order for Text / TextPath is fill-then-stroke,
// which paints each glyph's stroke ON TOP of the fill. When characters
// overlap (script faces with tight kerning — Pacifico is the obvious
// example in our palette, but Caveat does it too), each character's
// stroke draws across the previous character's fill, leaving visible
// outline lines slicing through the interior of the word.
//
// Fix is render-order based: stroke first at 2× the user's configured
// width, fill on top. The fill then covers (a) the inner half of the
// stroke and (b) any portion of any glyph's stroke that's inside the
// union of glyph fills. Net visible outline = the outer silhouette of
// the whole word.
//
// Two routes implement this depending on whether the text is arc'd:
//
//   • Flat (arc = 0): one Konva.Text node with `fillAfterStrokeEnabled`.
//     Konva.Text strokes/fills a whole line in ONE strokeText/fillText
//     call, so the trick lands natively on a single node.
//
//   • Arc'd (arc ≠ 0): two TextPath nodes, stroke-only below + fill-only
//     above. Konva.TextPath draws glyphs ONE AT A TIME with per-glyph
//     transforms along the path — the trick can't span across glyphs
//     in one node, so we split the work into two nodes. The fill node's
//     fill operations cover (a) and (b) above for every glyph.
//
// Either way, the user's configured `strokeWidth` becomes the visible
// outer half of the stroke; the actual stroke passed to Konva is 2×
// that value (the inner half gets eaten by the fill).
//
// Live transforms must keep stroke + fill in lockstep, so both children
// live inside a Konva.Group that owns the element's transform attrs.
// The Transformer in DesignCanvas attaches to the Group via
// `findOne('.' + id)`; Group-level scale/rotation propagates to all
// children, so the stroke and fill stay aligned every frame.

/**
 * Konva Text wrapper.
 *
 * Mirrors ImageElement's flip / opacity handling. See ImageElement.jsx for
 * the rationale on offset-then-scale to keep the flip in-place. Text-resize
 * scales the font-size rather than width/height (text doesn't have an
 * intrinsic box; Konva computes one from the rendered glyphs).
 *
 * ── Arc ──────────────────────────────────────────────────────────────────
 * Text can be curved along an arc using Konva's `TextPath`. The `arc` prop
 * is a number in [-100, 100]:
 *
 *   arc = 0    → flat text (renders as <Text>)
 *   arc > 0    → upward arc (text reads on the top of the curve, rainbow-style)
 *   arc < 0    → downward arc (text reads on the bottom, smile-shape)
 *   |arc| = 100 → tightly curved (semicircle-like)
 *
 * Path geometry
 * ─────────────
 * The path is a quadratic Bézier (cheap, visually indistinguishable from a
 * true circular arc at typical curvature values). It starts at (0, baseline)
 * and ends at (W, baseline) with a control point at (W/2, controlY) pulling
 * the curve up or down.
 *
 * W is the *actual* rendered text width (via canvas `measureText`), not a
 * geometric estimate. This matters for two reasons:
 *
 *   1. If the path is shorter than the text, Konva silently clips trailing
 *      characters. A wide-glyph font like a serif overflows the
 *      `fontSize × length × 0.55` estimate that previous versions used.
 *
 *   2. If the path is longer than the text, the bulge peak (at W/2) sits
 *      past the visual midpoint of the text. A condensed face like
 *      Bebas Neue under-flows the geometric estimate, so the arc looked
 *      lopsided.
 *
 * We also pass `align="center"` to the TextPath so any small measurement
 * residual is absorbed evenly at both ends rather than only the right.
 */
export const TextElement = memo(function TextElement({
  id: _id,
  x = 0,
  y = 0,
  text = '',
  fontSize = 24,
  fontFamily = DEFAULT_BODY_FONT,
  fill = '#0f172a',
  stroke = null,
  strokeWidth = 0,
  rotation = 0,
  opacity = 1,
  flipX = false,
  flipY = false,
  arc = 0,
  locked = false,
  isSelected: _isSelected,
  // Change 3 — see ImageElement.jsx for the rationale. For text we
  // use the visible-ink bbox computed below (the same one fed to
  // canvasDragBound for snap centering) rather than Konva.Text's
  // line-height bbox so the warning rect hugs the actual glyphs.
  isOutOfBounds = false,
  shiftHeld: _shiftHeld = false,
  onSelect,
  onUpdate,
  onCommit,
  // Change 7 — the on-canvas double-click handler that used to fire
  // this is gone. Text editing is now triggered exclusively via the
  // pencil affordance (desktop, rendered by TextEditAffordance) or the
  // mobile Edit-text toolbar button. We keep the prop here for the
  // affordance components to call through, but TextElement itself
  // doesn't fire it on double-click anymore.
  onStartEdit,
  dragBoundFunc,
  transformBoundFunc: _transformBoundFunc,
}) {
  // groupRef — the Konva.Group that wraps the text nodes is now the
  // "element" node from the rest of the system's perspective:
  //   • carries the element's `name` (= id) for findOne lookups,
  //   • carries the draggable + dragBoundFunc + event handlers,
  //   • carries the _pawSnapBbox* custom attrs the snap math reads,
  //   • is what the DesignCanvas Transformer attaches to.
  //
  // The Text / TextPath children inside it are positioned in Group-
  // local coordinates (no x/y/offset/scale/rotation of their own),
  // so Group-level transforms propagate uniformly. There's no longer
  // a separate `textRef` — nothing outside this component needs to
  // address the inner Text node specifically.
  const groupRef = useRef(null);

  // Subscribe to font-loading state. Calling the hook in the render body
  // means this component re-renders once `document.fonts.ready` resolves,
  // which causes `measureTextWidth` below to retry with the now-loaded
  // font and produce an accurate width. Without this, the first render
  // can use fallback-font metrics and the arc path / centering math is
  // off until the next unrelated update happens to come along.
  useFontsReady();

  // Real rendered text width plus glyph ascent/descent. Pixel-perfect for
  // the current font; falls back to coarse geometric estimates only when
  // DOM is unavailable. We still call measureTextMetrics here for the
  // whole-string width (used to size the path and Konva.Text bbox), but
  // the visible-ink envelope below comes from `computeTextVisibleBbox`
  // which does per-character measurement for arc'd text — see
  // textGeometry.js docblock for the rationale.
  const metrics = measureTextMetrics(text, fontSize, fontFamily);
  const measuredW = metrics.width;
  // Cap measured width at canvas size to keep approximate bounding box
  // sensible even with very long strings.
  const approxW = Math.max(fontSize, measuredW);
  const approxH = fontSize * LINE_HEIGHT_RATIO;

  // Always-center offset for rotation + flip-in-place. See the
  // matching docblock in ImageElement.jsx for the full rationale; the
  // short version: rotating around the local origin (= offset point)
  // gives center-pivot rotation when offset is at the bbox center,
  // which is what users expect from a rotate handle and avoids the
  // "swing on first touch" bug that the old top-left-pivot caused on
  // mobile. Flip-in-place falls out of the same center-aligned offset
  // for free.
  //
  // For text the "bbox" is the line-height envelope (approxW, approxH)
  // rather than the tighter visible-ink envelope — we want rotation to
  // pivot around the GEOMETRIC center of the text node, not the
  // visible-ink center, because the geometric center is what the user
  // sees the rotate handle attach to via the Transformer (which reads
  // getSelfRect, overridden below to the visible-ink bbox — close to
  // but not exactly identical to the line-height envelope; the small
  // difference is unnoticeable for rotation feel).
  const offsetX = approxW / 2;
  const offsetY = approxH / 2;
  const renderX = x + offsetX;
  const renderY = y + offsetY;

  // Arc path — only computed when a non-zero arc is requested. The path is
  // expressed in the text node's local coordinate system: it starts at
  // (0, baselineY) and ends at (approxW, baselineY), with a quadratic
  // control point pulling the curve up (arc > 0) or down (arc < 0).
  const arcAbs = Math.min(100, Math.abs(arc));
  const isArced = arcAbs > 0.5;
  const baselineY = approxH * 0.85;
  // bulgeMag scales with font size so larger text gets proportionally bigger
  // curvature.
  const bulgeMag = (arcAbs / 100) * approxH * 1.4;
  const controlY = arc > 0 ? baselineY - bulgeMag : baselineY + bulgeMag;
  // Tiny overshoot on the path (2%) plus center alignment so a couple of
  // pixels of measurement residual don't clip the last glyph.
  const pathW = approxW * 1.02;
  const arcPath = isArced
    ? `M 0 ${baselineY} Q ${pathW / 2} ${controlY} ${pathW} ${baselineY}`
    : null;

  // Visible-ink bbox in node-local coords (Change 23). Computed by
  // `computeTextVisibleBbox` in textGeometry.js — see that function's
  // docblock for the full geometry derivation. Critically, for arc'd
  // text it does PER-CHARACTER measurement along the curve: each
  // character's actual ascent/descent is applied at that character's
  // baseline-along-the-path, so a string like "Queen" (cap Q at the
  // arc endpoint, lowercase 'ee' at the peak) gets a tight bbox at
  // the peak rather than the cap-height envelope we used before.
  //
  // The same function feeds elementBounds.js's OOB check, so the
  // canvas's yellow OOB rect (rendered below), the Transformer rect,
  // the snap-bbox custom attrs, and the App-level out-of-bounds
  // warning all share one calculation. No drift between visual and
  // logical bounds.
  //
  // These values are in the text node's UN-OFFSET local coords —
  // origin at the top-left of the line-height envelope, so a typical
  // capital letter sits at roughly (0, ascent..approxH-descent). The
  // OOB Rect rendered below consumes them directly because the Rect
  // is positioned inside the Group at Group-local (0, 0) and
  // therefore shares the un-offset frame.
  //
  // For the dragBoundFunc on the Group, we need PIVOT-RELATIVE coords
  // (relative to the offset point, which IS the rotation pivot — see
  // the offsetX/Y docblock above). The conversion is just
  // `visibleBbox± - offset`, performed below where the snap attrs
  // are set on the Group.
  //
  // Fallback to the old line-height envelope only when the helper
  // returns null (empty text). Keeps existing positioning math stable
  // before any text has been typed.
  const _bbox = computeTextVisibleBbox({ text, fontSize, fontFamily, arc });
  const visibleBboxMinX = _bbox ? _bbox.minX : 0;
  const visibleBboxMaxX = _bbox ? _bbox.maxX : approxW;
  const visibleBboxMinY = _bbox ? _bbox.minY : 0;
  const visibleBboxMaxY = _bbox ? _bbox.maxY : approxH;

  // Pivot-relative versions of the visible bbox, for the dragBoundFunc
  // on the Group. canvasDragBound rotates corners around the local
  // origin (= offset point = bbox center). The visible-ink bbox above
  // is in un-offset coords; subtracting (offsetX, offsetY) translates
  // into the post-offset frame the rotation math expects.
  //
  // Without this translation, the drag clamp would treat the un-offset
  // top-left as the pivot — the same bug that ImageElement had before
  // its offset became unconditional. The OOB Rect below still consumes
  // the un-offset visibleBbox values because Rect inherits Group's
  // offset transform; the snap attrs are the only consumer that needs
  // pivot-relative coords.
  const snapBboxMinX = visibleBboxMinX - offsetX;
  const snapBboxMinY = visibleBboxMinY - offsetY;
  const snapBboxMaxX = visibleBboxMaxX - offsetX;
  const snapBboxMaxY = visibleBboxMaxY - offsetY;

  // Override the Konva node's getSelfRect with our visible-ink bbox.
  // Re-applied whenever the bbox-affecting attrs change so the
  // DesignCanvas-level Transformer (which lives in a separate layer and
  // attaches via `findOne('.' + id)`) sees the right rect when it calls
  // `tr.forceUpdate()` on element changes.
  //
  // The override now lives on the GROUP, not the inner text node.
  // Konva.Group.getSelfRect by default returns the union of its
  // children's getSelfRects — for us that would be either Konva.Text's
  // line-height box (loose at the top) or TextPath's baseline-padded
  // approximation. Overriding directly gives the Transformer (which
  // reads getSelfRect from the attached node — the Group) the
  // pixel-accurate visible-ink bbox that the rest of the system
  // (snap math, OOB check, App-level bounds warning) also uses.
  //
  // Three concerns the override addresses:
  //
  //   1. arc=0 ↔ arc≠0 toggles swap the children between <Text> and
  //      <TextPath>. The Group instance is unchanged — the override
  //      stays attached — but the children's bboxes change shape, so
  //      the override needs to reflect the new visibleBbox values.
  //      The deps array catches this.
  //
  //   2. Stroke vs no-stroke toggles change the Group's child set
  //      (one node vs two for arc'd text). Same Group instance, but
  //      its native getSelfRect would change — override still wins.
  //
  //   3. Konva's defaults for Text / TextPath bboxes are loose
  //      approximations of actual rendered glyph extent. Our visible-
  //      ink bbox (from computeTextVisibleBbox) is what the canvas
  //      drag-snap math centers against (via the _pawSnapBbox*
  //      custom attrs), so overriding here keeps the Transformer
  //      rectangle aligned with the snap target.
  useEffect(() => {
    const node = groupRef.current;
    if (!node) return;
    node.getSelfRect = function () {
      return {
        x: visibleBboxMinX,
        y: visibleBboxMinY,
        width: visibleBboxMaxX - visibleBboxMinX,
        height: visibleBboxMaxY - visibleBboxMinY,
      };
    };
  }, [
    arc,
    visibleBboxMinX,
    visibleBboxMinY,
    visibleBboxMaxX,
    visibleBboxMaxY,
  ]);

  // Outline state. The Konva strokeWidth we actually pass to the glyph
  // node(s) is DOUBLED because fillAfterStrokeEnabled (and the parallel
  // dual-TextPath path for arc'd text) makes only the OUTER half of
  // the stroke visible — the inner half gets covered by the fill. So
  // the user's strokeWidth setting (which they perceive as the visible
  // outline thickness) maps to 2× the Konva strokeWidth.
  const hasOutline = Boolean(stroke) && strokeWidth > 0;
  const konvaStrokeWidth = hasOutline ? strokeWidth * 2 : 0;

  // Group-level transform + interaction attrs. Applied to the
  // Konva.Group that wraps the text nodes; children render in Group-
  // local coords so Group-level scale/rotation/flip propagates uniformly
  // (this is what keeps the stroke and fill TextPath nodes locked
  // together during live transforms in the arc'd outlined case).
  const groupProps = {
    ref: groupRef,
    /* Konva `name` attr = element id. Used by DesignCanvas's findOne
     * lookups for marquee, single-Transformer, and multi-Transformer
     * attachment. See ImageElement.jsx for full rationale. */
    name: _id,
    x: renderX,
    y: renderY,
    offsetX,
    offsetY,
    scaleX: flipX ? -1 : 1,
    scaleY: flipY ? -1 : 1,
    rotation,
    opacity,
    draggable: !locked,
    dragBoundFunc,
    // Custom Konva attrs that DesignCanvas's canvasDragBound reads
    // directly via this.attrs.*, encoding the *visible-ink* bbox of
    // the rendered text in PIVOT-RELATIVE node-local coords (i.e.
    // relative to the offset point, which is the rotation pivot).
    // See the docblock in canvasDragBound for the corner-math
    // rationale, and the snapBbox* declarations above for why the
    // values are pre-shifted by (-offsetX, -offsetY) here. These
    // live on the Group (the draggable node) rather than the inner
    // text node because Group is what Konva calls `this` in
    // dragBoundFunc.
    _pawSnapBboxMinX: snapBboxMinX,
    _pawSnapBboxMinY: snapBboxMinY,
    _pawSnapBboxMaxX: snapBboxMaxX,
    _pawSnapBboxMaxY: snapBboxMaxY,
    onClick: (e) => { e.cancelBubble = true; onSelect?.(); },
    onTap:   (e) => { e.cancelBubble = true; onSelect?.(); },
    // Bug 6 fix — select on press, not just on click-up. Konva fires
    // `click` on the mouseup completing a press-release pair WITHOUT
    // intervening drag motion; a press that becomes a drag (the user
    // grabs an element and immediately starts moving) never produces
    // a click event, so the element wasn't getting selected and the
    // DesignCanvas-level Transformer (which attaches to the selected
    // node) never mounted handles around it. Selecting on mousedown
    // / touchstart ensures the selection state is updated BEFORE
    // Konva's drag machinery starts, so the Transformer is attached
    // by the next render and its handles appear immediately around
    // the dragging element.
    //
    // Selecting an already-selected element is a no-op at the
    // useDesignEditor level, so the click + mousedown pair on a
    // simple click doesn't double-commit anything.
    onMouseDown: (e) => { e.cancelBubble = true; onSelect?.(); },
    onTouchStart: (e) => { e.cancelBubble = true; onSelect?.(); },
    onDragEnd: (e) => {
      // e.target is the Group (the draggable node). Same shape as
      // before — unflipReportedXY just needs an object with x()/y()
      // accessors, which Group has.
      const { x: reportedX, y: reportedY } = unflipReportedXY(e.target, approxW, approxH, flipX, flipY);
      onUpdate({ x: reportedX, y: reportedY });
      onCommit?.();
    },
    onTransformEnd: () => {
      // The Transformer attaches to the Group, so transform fires on
      // the Group. We bake the live scale into a new fontSize and
      // reset the Group's scale to ±1 (preserving flip state); the
      // children inherit the reset on re-render.
      const node = groupRef.current;
      if (!node) return;
      const sx = node.scaleX();
      // Read fontSize from the prop (not from the node) — Group has
      // no `fontSize` attr; that lives on the inner Text/TextPath.
      // The prop is the same value react-konva pushed down to the
      // child, so this is equivalent to the old node.fontSize() read.
      const newFontSize = Math.max(MIN_FONT_SIZE, Math.round(fontSize * Math.abs(sx)));
      node.scaleX(flipX ? -1 : 1);
      node.scaleY(flipY ? -1 : 1);
      // Anchor-preserving commit math.
      //
      // During the transform Konva scales the Group via scaleX/Y while
      // keeping the un-grabbed corner of the Transformer rect anchored
      // in stage coords. With center offset (offsetX = approxW/2,
      // offsetY = approxH/2), the rendered line-height envelope
      // top-left lands at:
      //
      //     node.x() - offsetX * scaleX,  node.y() - offsetY * scaleY
      //
      // That's the position we want element.x/y to reproduce on the
      // very next render. Since we're about to commit a new fontSize
      // (which will produce new approxW/H of approximately
      // approxW*sx / approxH*sx on re-render under linear-in-fontSize
      // scaling), we need element.x/y to equal that scaled-offset
      // top-left. The naive `unflipReportedXY(node, approxW, approxH,
      // ...)` call used the OLD (unscaled) offsets and produced an
      // element.x/y that didn't match the visual position the user
      // saw at release — the line-height envelope would visibly jump
      // by approxW*(sx-1)/2 toward the bottom-right after the user
      // lets go. Multiplying by Math.abs(sx) here gives the
      // scaled-offset top-left, which the next render reconstructs
      // exactly (re-renders the node at that line-height envelope
      // origin with the new larger fontSize and matching new offset).
      //
      // Image elements get this right naturally because their
      // onTransformEnd computes `newWidth = node.width() *
      // Math.abs(sx)` first and feeds THAT into unflipReportedXY —
      // so the helper's W/2 subtraction is already the scaled half-
      // width. Text doesn't have a node.width() it controls (Konva
      // measures the glyphs internally and approxW is the React-
      // render-time measurement), so the equivalent here is to scale
      // approxW/H by sx at the call site.
      //
      // Using sx for both axes — the Transformer has keepRatio set
      // in DesignCanvas, so sy ≈ sx every frame, and the post-resize
      // fontSize is single-axis (a single scalar that drives both
      // measured width AND line-height-derived height). Decoupling
      // here would just introduce sub-pixel disagreement between
      // axes for no benefit.
      const scaledApproxW = approxW * Math.abs(sx);
      const scaledApproxH = approxH * Math.abs(sx);
      const { x: reportedX, y: reportedY } = unflipReportedXY(node, scaledApproxW, scaledApproxH, flipX, flipY);
      onUpdate({
        x: reportedX,
        y: reportedY,
        fontSize: newFontSize,
        rotation: node.rotation(),
      });
      onCommit?.();
    },
  };

  // Glyph-render attrs that go on the inner Text / TextPath children.
  // Position attrs are NOT here — those live on the Group above so its
  // transform applies uniformly. Children render at Group-local (0, 0).
  const sharedGlyphProps = {
    fontSize,
    fontFamily,
  };

  return (
    <Group {...groupProps}>
      {/* Out-of-bounds warning background (Change 3). Sits BEHIND the
          text within the Group, in Group-local coords — the Group's
          own transform applies so the highlight tracks the text
          through every transform. Position is the visible-ink bbox
          origin (which may be non-zero for text with baseline offset
          or arc bulge extending above y=0). */}
      {isOutOfBounds && (
        <Rect
          x={visibleBboxMinX}
          y={visibleBboxMinY}
          width={visibleBboxMaxX - visibleBboxMinX}
          height={visibleBboxMaxY - visibleBboxMinY}
          fill="#fde68a"
          stroke="#f59e0b"
          strokeWidth={1.5}
          opacity={0.65}
          listening={false}
          perfectDrawEnabled={false}
        />
      )}

      {/* Stroke-only layer for ARC'D outlined text. Renders first so
          it's visually behind the fill layer. listening=false so the
          inflated stroke hit area doesn't shadow the fill node's
          (rectangular bbox) hit area — clicks still register on the
          fill text and bubble up to the Group.

          Flat outlined text uses fillAfterStrokeEnabled on a single
          Text node instead (see below), since Konva.Text strokes/fills
          a whole line in one shot — no second node needed. */}
      {isArced && hasOutline && (
        <TextPath
          {...sharedGlyphProps}
          data={arcPath}
          text={text}
          align="center"
          fill={undefined}
          stroke={stroke}
          strokeWidth={konvaStrokeWidth}
          listening={false}
        />
      )}

      {/* Fill layer (always rendered). For flat text this is the only
          glyph node; outline (when present) rides along on this same
          node via fillAfterStrokeEnabled. For arc'd text this is the
          top layer; the stroke-only TextPath above provides the
          outline silhouette. */}
      {isArced ? (
        <TextPath
          {...sharedGlyphProps}
          data={arcPath}
          text={text}
          align="center"
          fill={fill}
          // No stroke on the fill layer — the outline (when present)
          // is drawn by the separate stroke-only TextPath above this
          // one, and this layer's job is to cover the inner half of
          // that stroke + any of it that's inside the union of glyph
          // fills. Default stroke = undefined; strokeWidth omitted.
        />
      ) : (
        <Text
          {...sharedGlyphProps}
          text={text}
          fill={fill}
          stroke={hasOutline ? stroke : undefined}
          strokeWidth={konvaStrokeWidth}
          // fillAfterStrokeEnabled reverses Konva.Text's default fill-
          // then-stroke render order. Combined with a 2× strokeWidth
          // (so the visible OUTER half matches the user's configured
          // strokeWidth), the fill covers (a) the inner half of the
          // stroke and (b) any portion of the stroke that's inside
          // the union of all glyph fills in this line. Net visible
          // outline = the outer silhouette of the whole word, not
          // individual glyphs. Solves the overlap-outlines-cutting-
          // through-glyphs problem for script fonts like Pacifico and
          // Caveat. No-op when fill or stroke is absent.
          fillAfterStrokeEnabled
          // Explicit width + height + wrap="none". See the long
          // docblock at the top of the file for why these three props
          // are interdependent — in summary, width+height fix the
          // snap-to-center bbox getter (which otherwise reads ~0
          // before textArr populates), and wrap="none" prevents
          // sub-pixel measurement disagreements between us and Konva
          // from triggering a word-wrap after a resize.
          width={approxW}
          height={approxH}
          wrap="none"
        />
      )}
      {/* Per-element Transformer removed (bug 8 fix). Selection chrome
          now lives in DesignCanvas's dedicated transformer layer,
          which renders ABOVE the elements layer so resize / rotate
          handles always draw on top of every element regardless of
          z-order. */}
    </Group>
  );
});
