/* Advanced Grand Cup Auto Meme Generator - canvas-first share image studio */

const {
  useEffect: useEffect_mg,
  useMemo: useMemo_mg,
  useRef: useRef_mg,
  useState: useState_mg,
} = React;

const MEME_ASPECTS = {
  square: { label: "square raid", width: 1400, height: 1400 },
  story: { label: "story heist", width: 1080, height: 1920 },
  poster: { label: "poster file", width: 1200, height: 1600 },
  wide: { label: "x banner", width: 1600, height: 900 },
};

const MEME_BACKDROPS = {
  stadium: { label: "stadium night", src: "assets/hero-desktop.jpg", tint: ["#2a0f55", "#ff3aa3", "#46e5e5"] },
  print: { label: "printing room", src: "assets/scene-printing-room.png", tint: ["#1a0a3a", "#ff3aa3", "#ffd23a"] },
  evidence: { label: "evidence locker", src: "assets/scene-evidence-locker.png", tint: ["#07132f", "#46e5e5", "#ff3aa3"] },
  boot: { label: "golden boot", src: "assets/scene-golden-boot-chase.png", tint: ["#30123d", "#ffd23a", "#ff3aa3"] },
  turf: { label: "turf war", src: "assets/bg-section-map.png", tint: ["#0b173f", "#46e5e5", "#ff8a3a"] },
};

const MEME_CHARACTERS = [
  { id: "wheelchair", name: "touchdown uncle", src: "assets/char-wheelchair.png", role: "final whistle menace" },
  { id: "fan", name: "underdog fan", src: "assets/char-fan.png", role: "Curacao chaos battery" },
  { id: "bookie", name: "sideline bookie", src: "assets/char-bookie.png", role: "odds goblin accountant" },
  { id: "boss", name: "trophy boss", src: "assets/char-boss.png", role: "bracket cartel owner" },
  { id: "broker", name: "briefcase broker", src: "assets/char-broker.png", role: "contract whisperer" },
  { id: "coach", name: "veteran coach", src: "assets/char-coach.png", role: "halftime cigar prophet" },
  { id: "driver", name: "getaway driver", src: "assets/char-driver.png", role: "trophy in the trunk" },
  { id: "goldenboot", name: "golden boot thief", src: "assets/char-goldenboot.png", role: "goal bounty hunter" },
  { id: "janitor", name: "final boss janitor", src: "assets/char-janitor.png", role: "swept the group stage" },
  { id: "paperhands", name: "paper hands", src: "assets/char-paperhands.png", role: "sold before kickoff" },
  { id: "referee", name: "red-card ref", src: "assets/char-referee.png", role: "VAR raid captain" },
  { id: "refmom", name: "referee mom", src: "assets/char-refmom.png", role: "parking lot justice" },
  { id: "security", name: "stadium security", src: "assets/char-boss.png", role: "five-star lockdown" },
  { id: "stockholm", name: "diamond hands", src: "assets/char-stockholm.png", role: "panic-proof degen" },
  { id: "striker", name: "street striker", src: "assets/char-striker.png", role: "one candle finisher" },
  { id: "superagent", name: "luxury agent", src: "assets/char-superagent.png", role: "transfer window villain" },
  { id: "trailerpark", name: "left-back legend", src: "assets/char-trailerpark.png", role: "beer cooler specialist" },
  { id: "var", name: "VAR informant", src: "assets/char-var.png", role: "frame-by-frame snitch" },
  { id: "wag", name: "trophy WAG", src: "assets/char-wag.png", role: "Miami clout raid" },
  { id: "whale", name: "whale holder", src: "assets/char-whale.png", role: "buys the dip and the yacht" },
];

const MEME_TEMPLATES = [
  {
    id: "bbl-penalty",
    name: "BBL WAG penalty",
    angle: "the exact extravagant meme-card style",
    top: "BBL WAG BOUGHT THE PENALTY SPOT",
    bottom: "THE REF CHECKED THE RECEIPT AND POINTED.",
    bubble: "THE REF CHECKED THE RECEIPT AND POINTED.",
    sticker: "VIP VAR",
    character: "wag",
    sidekick: "refmom",
    backdrop: "stadium",
    aspect: "square",
    vibe: "vice",
  },
  {
    id: "janitor-boss",
    name: "janitor final boss",
    angle: "stadium janitor becomes the raid boss",
    top: "THE JANITOR IS THE FINAL BOSS",
    bottom: "HE SWEPT THE GROUP STAGE.",
    bubble: "HE SWEPT THE GROUP STAGE.",
    sticker: "FINAL BOSS",
    character: "janitor",
    sidekick: "none",
    backdrop: "turf",
    aspect: "square",
    vibe: "heat",
  },
  {
    id: "curacao-ambush",
    name: "Curacao ambush",
    angle: "underdog nation goes full crime boss",
    top: "WHEN CURACAO QUALIFIES",
    bottom: "THE GROUP CHAT TURNS INTO A SAFEHOUSE",
    bubble: "small island, big heat",
    sticker: "UNDERDOG HEIST",
    character: "fan",
    backdrop: "stadium",
    aspect: "square",
    vibe: "heat",
  },
  {
    id: "nov19-clock",
    name: "Leonida wait clock",
    angle: "open-world hype meets tournament panic",
    top: "GRAND CUP AUTO DROPS 5 JUNI",
    bottom: "MY BAG TRYING TO SURVIVE 104 MATCHES",
    bubble: "wake me at extra time",
    sticker: "HYPE CLOCK",
    character: "stockholm",
    backdrop: "evidence",
    aspect: "square",
    vibe: "vice",
  },
  {
    id: "var-snitch",
    name: "VAR snitch cam",
    angle: "every replay becomes a raid report",
    top: "VAR SAID NO GOAL",
    bottom: "I SAID SEND THE CONTRACT",
    bubble: "enhance the candle",
    sticker: "FRAME 104",
    character: "var",
    backdrop: "print",
    aspect: "square",
    vibe: "cyan",
  },
  {
    id: "paperhands",
    name: "paper hands court",
    angle: "sell low, buy back with sirens",
    top: "SOLD BEFORE KICKOFF",
    bottom: "REBOUGHT DURING STOPPAGE TIME",
    bubble: "it was risk management",
    sticker: "WITNESS",
    character: "paperhands",
    backdrop: "turf",
    aspect: "square",
    vibe: "gold",
  },
  {
    id: "whale-dip",
    name: "whale buys stadium",
    angle: "one wallet changes the whole match",
    top: "WHEN THE WHALE SAYS DIP",
    bottom: "AND BUYS THE WHOLE STADIUM",
    bubble: "put it on the briefcase",
    sticker: "LIQUIDITY RAID",
    character: "whale",
    backdrop: "boot",
    aspect: "square",
    vibe: "vice",
  },
  {
    id: "mascot-sidequest",
    name: "mascot side quest",
    angle: "three mascots, zero chill",
    top: "CLUTCH MAPLE ZAYU WALK IN",
    bottom: "THE MEME DESK CALLS A THREE STAR RAID",
    bubble: "not financial advice, just mascot lore",
    sticker: "SIDE QUEST",
    character: "bookie",
    backdrop: "stadium",
    aspect: "square",
    vibe: "heat",
  },
  {
    id: "goldenboot",
    name: "golden boot bounty",
    angle: "one striker, one green candle",
    top: "ONE GOAL FROM MY STRIKER",
    bottom: "ONE GREEN CANDLE FROM RETIREMENT",
    bubble: "shoot first, explain tokenomics later",
    sticker: "BOUNTY LIVE",
    character: "goldenboot",
    backdrop: "boot",
    aspect: "square",
    vibe: "gold",
  },
  {
    id: "final-heist",
    name: "104 mission board",
    angle: "Grand Cup as a city-wide mission tree",
    top: "104 MATCHES",
    bottom: "104 WAYS TO GET LIQUIDATED",
    bubble: "pick your crew wisely",
    sticker: "MISSION TREE",
    character: "boss",
    backdrop: "evidence",
    aspect: "square",
    vibe: "cyan",
  },
];

const VIBE_PALETTES = {
  heat: { a: "#ff2d55", b: "#ff3aa3", c: "#ffd23a" },
  vice: { a: "#ff3aa3", b: "#46e5e5", c: "#ffc7a0" },
  cyan: { a: "#46e5e5", b: "#b5f6f6", c: "#ff3aa3" },
  gold: { a: "#ffd23a", b: "#ff8a3a", c: "#46e5e5" },
};

function clampMeme(n, min, max) {
  return Math.max(min, Math.min(max, n));
}

function hashMeme(str) {
  let h = 2166136261;
  for (let i = 0; i < str.length; i += 1) {
    h ^= str.charCodeAt(i);
    h = Math.imul(h, 16777619);
  }
  return h >>> 0;
}

function makeMemeRng(seed) {
  let s = seed || 1;
  return () => {
    s ^= s << 13;
    s ^= s >>> 17;
    s ^= s << 5;
    return ((s >>> 0) % 10000) / 10000;
  };
}

function roundRectPath(ctx, x, y, w, h, r) {
  const rr = Math.min(r, w / 2, h / 2);
  if (ctx.roundRect) {
    ctx.roundRect(x, y, w, h, rr);
    return;
  }
  ctx.moveTo(x + rr, y);
  ctx.lineTo(x + w - rr, y);
  ctx.quadraticCurveTo(x + w, y, x + w, y + rr);
  ctx.lineTo(x + w, y + h - rr);
  ctx.quadraticCurveTo(x + w, y + h, x + w - rr, y + h);
  ctx.lineTo(x + rr, y + h);
  ctx.quadraticCurveTo(x, y + h, x, y + h - rr);
  ctx.lineTo(x, y + rr);
  ctx.quadraticCurveTo(x, y, x + rr, y);
}

function drawImageCover(ctx, img, x, y, w, h) {
  if (!img) return;
  const scale = Math.max(w / img.width, h / img.height);
  const sw = w / scale;
  const sh = h / scale;
  const sx = (img.width - sw) / 2;
  const sy = (img.height - sh) / 2;
  ctx.drawImage(img, sx, sy, sw, sh, x, y, w, h);
}

function getMemeLines(ctx, text, maxWidth) {
  const words = String(text || "").toUpperCase().trim().split(/\s+/).filter(Boolean);
  const lines = [];
  let line = "";
  words.forEach((word) => {
    const test = line ? `${line} ${word}` : word;
    if (ctx.measureText(test).width <= maxWidth || !line) {
      line = test;
    } else {
      lines.push(line);
      line = word;
    }
  });
  if (line) lines.push(line);
  return lines;
}

function drawMemeText(ctx, text, opts) {
  const {
    x,
    y,
    maxWidth,
    maxLines = 3,
    startSize,
    minSize,
    align = "center",
    fill = "#fff5fb",
    stroke = "#08030f",
    accent = "#ff3aa3",
  } = opts;
  const family = '"Anton", Impact, "Bowlby One SC", sans-serif';
  let size = startSize;
  let lines = [];
  while (size >= minSize) {
    ctx.font = `900 ${size}px ${family}`;
    lines = getMemeLines(ctx, text, maxWidth);
    if (lines.length <= maxLines) break;
    size -= 8;
  }
  lines = lines.slice(0, maxLines);
  const lineHeight = size * 0.82;
  ctx.save();
  ctx.textAlign = align;
  ctx.textBaseline = "middle";
  ctx.lineJoin = "round";
  lines.forEach((line, i) => {
    const yy = y + (i - (lines.length - 1) / 2) * lineHeight;
    ctx.shadowColor = "rgba(0,0,0,0.65)";
    ctx.shadowBlur = 16;
    ctx.shadowOffsetY = 8;
    ctx.lineWidth = Math.max(14, size * 0.13);
    ctx.strokeStyle = stroke;
    ctx.strokeText(line, x, yy);
    ctx.shadowBlur = 0;
    ctx.fillStyle = fill;
    ctx.fillText(line, x, yy);
    ctx.lineWidth = Math.max(3, size * 0.035);
    ctx.strokeStyle = accent;
    ctx.globalAlpha = 0.24;
    ctx.strokeText(line, x, yy);
    ctx.globalAlpha = 1;
  });
  ctx.restore();
}

function drawMemeLabel(ctx, text, x, y, opts = {}) {
  const fill = opts.fill || "#46e5e5";
  const bg = opts.bg || "rgba(8,3,15,0.72)";
  ctx.save();
  ctx.font = '800 34px "JetBrains Mono", monospace';
  ctx.textAlign = "center";
  ctx.textBaseline = "middle";
  const padX = 34;
  const w = ctx.measureText(text).width + padX * 2;
  const h = 66;
  ctx.beginPath();
  roundRectPath(ctx, x - w / 2, y - h / 2, w, h, 4);
  ctx.fillStyle = bg;
  ctx.fill();
  ctx.strokeStyle = fill;
  ctx.lineWidth = 3;
  ctx.stroke();
  ctx.fillStyle = fill;
  ctx.fillText(text, x, y + 1);
  ctx.restore();
}

function drawMemeBars(ctx, x, y, w, label, fill, value = 0.72) {
  ctx.save();
  ctx.fillStyle = "rgba(3,5,18,0.9)";
  ctx.strokeStyle = "#0ef0ff";
  ctx.lineWidth = 2;
  ctx.beginPath();
  roundRectPath(ctx, x, y, w, 44, 4);
  ctx.fill();
  ctx.stroke();
  ctx.font = '900 16px "Anton", Impact, sans-serif';
  ctx.fillStyle = "#fff8d1";
  ctx.textAlign = "left";
  ctx.textBaseline = "middle";
  ctx.fillText(label, x + 12, y + 15);
  ctx.fillStyle = "#091126";
  ctx.fillRect(x + 12, y + 28, w - 24, 7);
  ctx.fillStyle = fill;
  ctx.fillRect(x + 12, y + 28, (w - 24) * value, 7);
  ctx.restore();
}

function drawMemeStars(ctx, x, y, size, count = 5, accent = "#ffd23a") {
  ctx.save();
  ctx.font = `900 ${size}px "Arial", sans-serif`;
  ctx.textAlign = "right";
  ctx.textBaseline = "middle";
  ctx.lineWidth = Math.max(2, size * 0.08);
  ctx.strokeStyle = "#08030f";
  ctx.fillStyle = accent;
  const stars = "★★★★★".slice(0, count);
  ctx.strokeText(stars, x, y);
  ctx.fillText(stars, x, y);
  ctx.restore();
}

function AdvancedMemeGenerator() {
  const canvasRef = useRef_mg(null);
  const fileRef = useRef_mg(null);
  const imageCache = useRef_mg({});
  const [templateId, setTemplateId] = useState_mg(MEME_TEMPLATES[0].id);
  const activeTemplate = useMemo_mg(
    () => MEME_TEMPLATES.find((t) => t.id === templateId) || MEME_TEMPLATES[0],
    [templateId]
  );
  const [topText, setTopText] = useState_mg(activeTemplate.top);
  const [bottomText, setBottomText] = useState_mg(activeTemplate.bottom);
  const [bubbleText, setBubbleText] = useState_mg(activeTemplate.bubble);
  const [stickerText, setStickerText] = useState_mg(activeTemplate.sticker);
  const [characterId, setCharacterId] = useState_mg(activeTemplate.character);
  const [sidekickId, setSidekickId] = useState_mg(activeTemplate.sidekick || "none");
  const [backdropId, setBackdropId] = useState_mg(activeTemplate.backdrop);
  const [aspectId, setAspectId] = useState_mg(activeTemplate.aspect);
  const [vibeId, setVibeId] = useState_mg(activeTemplate.vibe);
  const [charScale, setCharScale] = useState_mg(64);
  const [charX, setCharX] = useState_mg(70);
  const [charY, setCharY] = useState_mg(58);
  const [charRotate, setCharRotate] = useState_mg(-3);
  const [flip, setFlip] = useState_mg(false);
  const [uploadedBg, setUploadedBg] = useState_mg("");
  const [status, setStatus] = useState_mg("ready to forge");
  const [remixSeed, setRemixSeed] = useState_mg(104);

  const aspect = MEME_ASPECTS[aspectId] || MEME_ASPECTS.square;
  const palette = VIBE_PALETTES[vibeId] || VIBE_PALETTES.vice;
  const character = MEME_CHARACTERS.find((c) => c.id === characterId) || MEME_CHARACTERS[0];
  const sidekick = sidekickId === "none" ? null : MEME_CHARACTERS.find((c) => c.id === sidekickId);
  const backdrop = MEME_BACKDROPS[backdropId] || MEME_BACKDROPS.stadium;

  const loadImage = (src) => {
    if (!src) return Promise.resolve(null);
    const cached = imageCache.current[src];
    if (cached && cached.complete) return Promise.resolve(cached);
    if (cached && cached.promise) return cached.promise;
    const img = new Image();
    img.crossOrigin = "anonymous";
    img.decoding = "async";
    const promise = new Promise((resolve) => {
      img.onload = () => resolve(img);
      img.onerror = () => resolve(null);
    });
    imageCache.current[src] = img;
    img.promise = promise;
    img.src = src;
    return promise;
  };

  const drawCanvas = async () => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext("2d");
    const w = aspect.width;
    const h = aspect.height;
    canvas.width = w;
    canvas.height = h;
    const seed = hashMeme(`${topText}|${bottomText}|${bubbleText}|${characterId}|${sidekickId}|${remixSeed}`);
    const rng = makeMemeRng(seed);

    const bgImg = await loadImage(uploadedBg || backdrop.src);
    const charImg = await loadImage(character.src);
    const sidekickImg = sidekick ? await loadImage(sidekick.src) : null;

    const bgGradient = ctx.createLinearGradient(0, 0, w, h);
    bgGradient.addColorStop(0, backdrop.tint[0]);
    bgGradient.addColorStop(0.54, "#10051f");
    bgGradient.addColorStop(1, palette.a);
    ctx.fillStyle = bgGradient;
    ctx.fillRect(0, 0, w, h);

    if (bgImg) {
      ctx.save();
      ctx.filter = "blur(14px) saturate(1.2) brightness(0.82)";
      drawImageCover(ctx, bgImg, -54, -54, w + 108, h + 108);
      ctx.restore();
    }

    const outerWash = ctx.createRadialGradient(w * 0.62, h * 0.12, 80, w * 0.52, h * 0.48, Math.max(w, h));
    outerWash.addColorStop(0, `${palette.c}44`);
    outerWash.addColorStop(0.46, "rgba(255,58,163,0.2)");
    outerWash.addColorStop(1, "rgba(4,2,12,0.62)");
    ctx.fillStyle = outerWash;
    ctx.fillRect(0, 0, w, h);

    drawMemeText(ctx, topText, {
      x: 64,
      y: aspectId === "story" ? 150 : 122,
      maxWidth: w * 0.78,
      startSize: aspectId === "wide" ? 76 : 90,
      minSize: 42,
      maxLines: 2,
      align: "left",
      fill: "#fff8ef",
      stroke: "#070913",
      accent: palette.b,
    });

    const stripX = 64;
    const stripY = aspectId === "wide" ? 222 : 352;
    const stripW = w - 128;
    const stripH = 62;
    ctx.save();
    ctx.beginPath();
    roundRectPath(ctx, stripX, stripY, stripW, stripH, 8);
    ctx.fillStyle = "rgba(7,9,19,0.92)";
    ctx.fill();
    ctx.strokeStyle = palette.c;
    ctx.lineWidth = 4;
    ctx.stroke();
    ctx.font = `900 ${aspectId === "wide" ? 22 : 28}px "Anton", Impact, sans-serif`;
    ctx.textAlign = "left";
    ctx.textBaseline = "middle";
    ctx.fillStyle = palette.c;
    getMemeLines(ctx, bottomText, stripW - 52).slice(0, 1).forEach((line) => {
      ctx.fillText(line, stripX + 30, stripY + stripH / 2 + 1);
    });
    ctx.restore();

    const cardX = Math.round(w * 0.03);
    const cardY = Math.round(h * (aspectId === "wide" ? 0.36 : 0.34));
    const cardW = Math.round(w * 0.94);
    const cardH = Math.round(h * (aspectId === "wide" ? 0.46 : 0.53));
    const sideW = Math.round(cardW * 0.34);
    const laneX = cardX + cardW - sideW;

    ctx.save();
    ctx.fillStyle = "#090b18";
    ctx.fillRect(cardX, cardY, cardW, cardH);
    if (bgImg) {
      drawImageCover(ctx, bgImg, cardX, cardY, cardW - sideW * 0.58, cardH);
    }
    const cardScrim = ctx.createLinearGradient(cardX, cardY, cardX + cardW, cardY + cardH);
    cardScrim.addColorStop(0, "rgba(255,45,85,0.18)");
    cardScrim.addColorStop(0.5, "rgba(8,3,15,0.04)");
    cardScrim.addColorStop(1, "rgba(8,3,15,0.4)");
    ctx.fillStyle = cardScrim;
    ctx.fillRect(cardX, cardY, cardW, cardH);

    ctx.beginPath();
    ctx.moveTo(laneX - sideW * 0.22, cardY);
    ctx.lineTo(laneX + sideW * 0.08, cardY);
    ctx.lineTo(laneX + sideW * 0.18, cardY + cardH);
    ctx.lineTo(laneX - sideW * 0.02, cardY + cardH);
    ctx.closePath();
    ctx.fillStyle = palette.a;
    ctx.fill();
    ctx.strokeStyle = palette.b;
    ctx.lineWidth = 3;
    ctx.stroke();

    ctx.fillStyle = palette.b;
    ctx.fillRect(laneX + sideW * 0.08, cardY, sideW * 0.92, cardH);
    ctx.restore();

    drawMemeText(ctx, topText, {
      x: cardX + 28,
      y: cardY + (aspectId === "wide" ? 72 : 122),
      maxWidth: cardW * 0.46,
      startSize: aspectId === "wide" ? 56 : 76,
      minSize: 34,
      maxLines: 3,
      align: "left",
      fill: "#fff8ef",
      stroke: "#050711",
      accent: palette.b,
    });

    ctx.save();
    const miniStripW = cardW * 0.38;
    const miniStripH = 36;
    ctx.beginPath();
    roundRectPath(ctx, cardX + 28, cardY + (aspectId === "wide" ? 138 : 226), miniStripW, miniStripH, 4);
    ctx.fillStyle = "rgba(4,6,14,0.94)";
    ctx.fill();
    ctx.strokeStyle = palette.c;
    ctx.lineWidth = 3;
    ctx.stroke();
    ctx.font = `900 ${aspectId === "wide" ? 14 : 19}px "Anton", Impact, sans-serif`;
    ctx.fillStyle = palette.c;
    ctx.textBaseline = "middle";
    ctx.textAlign = "left";
    getMemeLines(ctx, bubbleText || bottomText, miniStripW - 24).slice(0, 1).forEach((line) => {
      ctx.fillText(line, cardX + 42, cardY + (aspectId === "wide" ? 156 : 244));
    });
    ctx.restore();

    drawMemeBars(ctx, cardX + 30, cardY + cardH - 122, 205, "ACTIVE MISSION", palette.a, 0.66 + rng() * 0.22);
    drawMemeBars(ctx, cardX + 30, cardY + cardH - 70, 240, "GRAND CUP", palette.a, 0.54 + rng() * 0.34);
    drawMemeStars(ctx, cardX + cardW - 22, cardY + 38, 30, 5, palette.c);

    const drawCharacter = (img, xRatio, yRatio, scaleRatio, rotate, mirror, glow) => {
      if (!img) return;
      const drawW = cardW * scaleRatio;
      const drawH = drawW * (img.height / img.width);
      const cx = cardX + cardW * xRatio;
      const cy = cardY + cardH * yRatio;
      ctx.save();
      ctx.translate(cx, cy);
      ctx.rotate((rotate * Math.PI) / 180);
      ctx.scale(mirror ? -1 : 1, 1);
      ctx.shadowColor = "rgba(0,0,0,0.78)";
      ctx.shadowBlur = 28;
      ctx.shadowOffsetX = 18;
      ctx.shadowOffsetY = 28;
      ctx.drawImage(img, -drawW / 2, -drawH / 2, drawW, drawH);
      ctx.globalAlpha = 0.42;
      ctx.shadowColor = glow;
      ctx.shadowBlur = 22;
      ctx.shadowOffsetX = 0;
      ctx.shadowOffsetY = 0;
      ctx.drawImage(img, -drawW / 2, -drawH / 2, drawW, drawH);
      ctx.restore();
    };

    if (sidekickImg) {
      drawCharacter(sidekickImg, 0.84, 0.63, clampMeme(charScale / 180, 0.28, 0.46), charRotate * -0.4, !flip, palette.a);
    }
    drawCharacter(charImg, charX / 100, charY / 100, clampMeme(charScale / 130, 0.38, 0.7), charRotate, flip, palette.b);

    ctx.save();
    ctx.beginPath();
    roundRectPath(ctx, cardX + cardW - 196, cardY + cardH - 166, 168, 42, 4);
    ctx.fillStyle = "rgba(8,3,15,0.82)";
    ctx.fill();
    ctx.strokeStyle = palette.c;
    ctx.lineWidth = 2;
    ctx.stroke();
    ctx.font = '900 18px "JetBrains Mono", monospace';
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillStyle = palette.c;
    ctx.fillText(stickerText.toUpperCase().slice(0, 16), cardX + cardW - 112, cardY + cardH - 145);
    ctx.restore();

    ctx.save();
    ctx.beginPath();
    roundRectPath(ctx, cardX + cardW - 170, cardY + cardH - 110, 142, 82, 8);
    ctx.fillStyle = "rgba(8,3,15,0.58)";
    ctx.fill();
    ctx.strokeStyle = palette.c;
    ctx.lineWidth = 3;
    ctx.stroke();
    ctx.font = '900 36px "Anton", Impact, sans-serif';
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.fillStyle = "#fff8ef";
    ctx.strokeStyle = "#050711";
    ctx.lineWidth = 7;
    ctx.strokeText("$GCA", cardX + cardW - 99, cardY + cardH - 66);
    ctx.fillText("$GCA", cardX + cardW - 99, cardY + cardH - 66);
    ctx.restore();

    ctx.save();
    ctx.strokeStyle = "rgba(255,255,255,0.82)";
    ctx.lineWidth = 2;
    ctx.strokeRect(cardX, cardY, cardW, cardH);
    ctx.strokeStyle = palette.c;
    ctx.lineWidth = 4;
    ctx.beginPath();
    ctx.moveTo(cardX, cardY);
    ctx.lineTo(cardX + cardW, cardY);
    ctx.stroke();
    ctx.restore();

    drawMemeLabel(ctx, "$GCA", w * 0.15, h - 88, { fill: palette.b });
    drawMemeLabel(ctx, "GRAND CUP", w * 0.35, h - 88, { fill: "#fff8ef", bg: "rgba(8,3,15,0.58)" });
    drawMemeStars(ctx, w - 78, h - 88, 38, 5, palette.c);
  };

  useEffect_mg(() => {
    let cancelled = false;
    const redraw = async () => {
      if (document.fonts && document.fonts.ready) {
        await document.fonts.ready.catch(() => {});
      }
      if (!cancelled) await drawCanvas();
    };
    redraw();
    return () => { cancelled = true; };
  }, [
    topText,
    bottomText,
    bubbleText,
    stickerText,
    characterId,
    sidekickId,
    backdropId,
    aspectId,
    vibeId,
    charScale,
    charX,
    charY,
    charRotate,
    flip,
    uploadedBg,
    remixSeed,
  ]);

  useEffect_mg(() => {
    window.__GCA_MEME_TEST_EXPORT = () => new Promise((resolve) => {
      const canvas = canvasRef.current;
      if (!canvas) return resolve(null);
      canvas.toBlob((blob) => resolve(blob), "image/png");
    });
    return () => {
      delete window.__GCA_MEME_TEST_EXPORT;
    };
  }, []);

  const applyTemplate = (tpl) => {
    const apply = () => {
      setTemplateId(tpl.id);
      setTopText(tpl.top);
      setBottomText(tpl.bottom);
      setBubbleText(tpl.bubble);
      setStickerText(tpl.sticker);
      setCharacterId(tpl.character);
      setSidekickId(tpl.sidekick || "none");
      setBackdropId(tpl.backdrop);
      setAspectId(tpl.aspect);
      setVibeId(tpl.vibe);
      setCharX(tpl.character === "var" || tpl.character === "paperhands" ? 64 : 70);
      setCharY(tpl.character === "goldenboot" ? 54 : 58);
      setCharRotate(tpl.character === "paperhands" ? 5 : -3);
      setStatus(`${tpl.name} loaded`);
    };
    if (window.withViewTransition) window.withViewTransition(apply, "meme-gen");
    else apply();
  };

  const randomize = () => {
    const rng = makeMemeRng(Date.now() % 100000);
    const tpl = MEME_TEMPLATES[Math.floor(rng() * MEME_TEMPLATES.length)];
    const chr = MEME_CHARACTERS[Math.floor(rng() * MEME_CHARACTERS.length)];
    const buddy = MEME_CHARACTERS[Math.floor(rng() * MEME_CHARACTERS.length)];
    applyTemplate(tpl);
    setCharacterId(chr.id);
    setSidekickId(rng() > 0.54 && buddy.id !== chr.id ? buddy.id : "none");
    setCharScale(52 + Math.round(rng() * 28));
    setCharX(42 + Math.round(rng() * 42));
    setCharY(48 + Math.round(rng() * 22));
    setCharRotate(-10 + Math.round(rng() * 20));
    setFlip(rng() > 0.5);
    setRemixSeed(Math.round(rng() * 9999));
    setStatus("chaos remix armed");
  };

  const exportPng = () => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    canvas.toBlob((blob) => {
      if (!blob) {
        setStatus("export blocked");
        return;
      }
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = `gca-meme-${templateId}-${Date.now()}.png`;
      document.body.appendChild(a);
      a.click();
      a.remove();
      setTimeout(() => URL.revokeObjectURL(url), 1200);
      setStatus(`png exported ${(blob.size / 1024).toFixed(0)}kb`);
    }, "image/png");
  };

  const copyMeme = async () => {
    const canvas = canvasRef.current;
    if (!canvas || !navigator.clipboard || !window.ClipboardItem) {
      setStatus("clipboard unavailable");
      return;
    }
    canvas.toBlob(async (blob) => {
      if (!blob) return;
      try {
        await navigator.clipboard.write([new ClipboardItem({ "image/png": blob })]);
        setStatus("copied to clipboard");
      } catch (err) {
        setStatus("clipboard blocked");
      }
    }, "image/png");
  };

  const handleUpload = (event) => {
    const file = event.target.files && event.target.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      setUploadedBg(String(reader.result || ""));
      setStatus("custom background loaded");
    };
    reader.readAsDataURL(file);
  };

  return (
    <section className="section meme-section" id="meme-generator-section" data-screen-label="02 Meme Generator">
      <window.SectionStage
        variant="duo"
        shape="wedge"
        accent="cyan"
        num="02"
        eyebrow="meme lab - share forge"
        title={<><span className="ac">meme</span><br/>generator.</>}
        sub="Pick a raid template, choose a transparent character, write your own bait, then export a high-res Grand Cup Auto meme."
        cta={{ label: "open meme lab", onClick: () => { const el = document.querySelector(".meme-gen"); if (el) window.scrollTo({ top: el.offsetTop - 30, behavior: "smooth" }); } }}
        bg="assets/scene-dossier-wall.png"
        charLeft="assets/char-var.png"
        charRight="assets/char-wheelchair.png"
        charPop={18}
        height={520}
      />

      <div className="meme-gen" aria-label="Advanced Grand Cup Auto meme generator">
        <div className="meme-panel meme-panel-library">
          <div className="meme-panel-head">
            <span className="k">01</span>
            <div>
              <h3>raid templates</h3>
              <p>Fast meme premises tuned to Grand Cup Auto, crypto raid energy, and tournament chaos.</p>
            </div>
          </div>
          <div className="meme-template-grid">
            {MEME_TEMPLATES.map((tpl) => (
              <button
                key={tpl.id}
                className={`meme-template-card ${tpl.id === templateId ? "on" : ""}`}
                onClick={() => applyTemplate(tpl)}
              >
                <span className="meme-template-name">{tpl.name}</span>
                <span className="meme-template-angle">{tpl.angle}</span>
              </button>
            ))}
          </div>

          <div className="meme-panel-head small">
            <span className="k">02</span>
            <div>
              <h3>transparent crew</h3>
              <p>Existing alpha PNG characters, staged as stickers inside the canvas.</p>
            </div>
          </div>
          <div className="meme-character-grid">
            {MEME_CHARACTERS.map((item) => (
              <button
                key={item.id}
                className={`meme-character-card ${item.id === characterId ? "on" : ""}`}
                onClick={() => setCharacterId(item.id)}
                title={item.role}
              >
                <img src={item.src} alt="" loading="lazy" />
                <span>{item.name}</span>
              </button>
            ))}
          </div>
        </div>

        <div className="meme-preview-panel">
          <div className="meme-preview-top">
            <div>
              <span className="meme-live-dot" />
              <span className="mono">CANVAS LIVE RENDER</span>
            </div>
            <span className="mono">{aspect.width}x{aspect.height}</span>
          </div>
          <div className="meme-canvas-shell">
            <canvas
              id="meme-canvas"
              ref={canvasRef}
              width={aspect.width}
              height={aspect.height}
              aria-label="Generated Grand Cup Auto meme preview"
            />
          </div>
          <div className="meme-actions">
            <button className="btn-sunset" onClick={randomize}>random raid</button>
            <button className="btn-ghost" onClick={copyMeme}>copy png</button>
            <button className="btn-ghost" data-testid="meme-export" onClick={exportPng}>download png</button>
          </div>
          <div className="meme-status mono">{status} / seed {remixSeed}</div>
        </div>

        <div className="meme-panel meme-panel-controls">
          <div className="meme-panel-head">
            <span className="k">03</span>
            <div>
              <h3>custom copy</h3>
              <p>Write the joke like a mission card: setup headline, yellow punchline strip, mini caption, and evidence stamp.</p>
            </div>
          </div>

          <label className="meme-field">
            <span>top text</span>
            <textarea name="memeTopText" value={topText} onChange={(e) => setTopText(e.target.value)} />
          </label>
          <label className="meme-field">
            <span>bottom text</span>
            <textarea name="memeBottomText" value={bottomText} onChange={(e) => setBottomText(e.target.value)} />
          </label>
          <label className="meme-field two">
            <span>mini caption</span>
            <input value={bubbleText} onChange={(e) => setBubbleText(e.target.value)} />
          </label>
          <label className="meme-field two">
            <span>stamp</span>
            <input value={stickerText} onChange={(e) => setStickerText(e.target.value)} />
          </label>

          <div className="meme-chip-group" role="group" aria-label="Meme format">
            {Object.entries(MEME_ASPECTS).map(([id, item]) => (
              <button key={id} className={aspectId === id ? "on" : ""} onClick={() => setAspectId(id)}>
                {item.label}
              </button>
            ))}
          </div>
          <div className="meme-chip-group" role="group" aria-label="Backdrop">
            {Object.entries(MEME_BACKDROPS).map(([id, item]) => (
              <button key={id} className={backdropId === id && !uploadedBg ? "on" : ""} onClick={() => { setBackdropId(id); setUploadedBg(""); }}>
                {item.label}
              </button>
            ))}
          </div>
          <div className="meme-chip-group" role="group" aria-label="Meme vibe">
            {Object.keys(VIBE_PALETTES).map((id) => (
              <button key={id} className={vibeId === id ? "on" : ""} onClick={() => setVibeId(id)}>
                {id}
              </button>
            ))}
          </div>

          <label className="meme-select-field">
            <span>sidekick sticker</span>
            <select value={sidekickId} onChange={(e) => setSidekickId(e.target.value)}>
              <option value="none">no sidekick</option>
              {MEME_CHARACTERS.map((item) => (
                <option key={item.id} value={item.id}>{item.name}</option>
              ))}
            </select>
          </label>

          <div className="meme-sliders">
            <label><span>size</span><input type="range" min="36" max="92" value={charScale} onChange={(e) => setCharScale(Number(e.target.value))} /></label>
            <label><span>x</span><input type="range" min="18" max="88" value={charX} onChange={(e) => setCharX(Number(e.target.value))} /></label>
            <label><span>y</span><input type="range" min="30" max="78" value={charY} onChange={(e) => setCharY(Number(e.target.value))} /></label>
            <label><span>tilt</span><input type="range" min="-18" max="18" value={charRotate} onChange={(e) => setCharRotate(Number(e.target.value))} /></label>
          </div>

          <div className="meme-actions mini">
            <button className={`btn-ghost ${flip ? "on" : ""}`} onClick={() => setFlip(!flip)}>flip character</button>
            <button className="btn-ghost" onClick={() => fileRef.current && fileRef.current.click()}>upload bg</button>
            <button className="btn-ghost" onClick={() => { setUploadedBg(""); setStatus("stock backdrop restored"); }}>clear bg</button>
          </div>
          <input
            ref={fileRef}
            className="meme-file"
            type="file"
            accept="image/*"
            onChange={handleUpload}
          />
        </div>
      </div>
    </section>
  );
}

window.AdvancedMemeGenerator = AdvancedMemeGenerator;
