// Wordra gameplay screen — playable swipe-to-spell engine
// Renders 5x5 grid + timer + score + combo + floating-score animations

const { useState, useEffect, useRef, useCallback, useMemo } = React;

function WordraGameplay({ seed = 1, lang = "en", duration = 60, onEnd, mode = "solo", onExit, inventory: initialInventory, onInventoryChange }) {
  const E = window.WordraEngine;
  const [seedState, setSeedState] = useState(seed);
  useEffect(() => { setSeedState(seed); }, [seed]);
  const board = useMemo(() => E.generateBoard(seedState, lang), [seedState, lang]);

  const [path, setPath] = useState([]); // indices currently selected
  const [score, setScore] = useState(0);
  const [timeLeft, setTimeLeft] = useState(duration);
  const [combo, setCombo] = useState(1);
  const [comboTimer, setComboTimer] = useState(0);
  const [feedback, setFeedback] = useState(null);
  const [floaters, setFloaters] = useState([]);
  const [shake, setShake] = useState(0);
  const [particles, setParticles] = useState([]);
  const [acceptedWords, setAcceptedWords] = useState([]);
  const [running, setRunning] = useState(false);
  const [paused, setPaused] = useState(false);
  const [counting, setCounting] = useState(true);
  const [soundOn, setSoundOn] = useState(true);
  const [hapticsOn, setHapticsOn] = useState(true);
  const [inv, setInv] = useState(() => initialInventory || (window.loadInventory ? window.loadInventory() : { shuffle: 2, time: 1, hint: 3, lock: 1 }));
  const [itemsUsed, setItemsUsed] = useState({ shuffle: 0, time: 0, hint: 0, lock: 0 });
  const [hintIdx, setHintIdx] = useState(null);
  const [comboLocked, setComboLocked] = useState(false);
  const comboLockUntilRef = useRef(0);
  const [itemFeedback, setItemFeedback] = useState(null);
  // Item FX state — drives dramatic Candy Crush-style animations
  const [itemFx, setItemFx] = useState(null); // { id, phase, ... }
  const [shuffleAnim, setShuffleAnim] = useState(null); // { phase: 'out'|'in', from: [letters], to: [letters] }
  const [hintBeam, setHintBeam] = useState(null); // { fromX, fromY, toX, toY, idx }
  const [timeBurst, setTimeBurst] = useState(0); // animation key
  const [lockChains, setLockChains] = useState(false);
  const [itemSparks, setItemSparks] = useState([]);
  // newWordsThisRound is bumped every time WordraVocab.record reports isNew.
  // It's also piped into onEnd so the Result screen can show "X NEW WORDS".
  const [newWordsThisRound, setNewWordsThisRound] = useState(0);
  const lastWordTimeRef = useRef(Date.now());
  const gridRef = useRef(null);
  const cellRectsRef = useRef([]);

  const t = (en, th) => (lang === "th" ? th : en);

  // Recalc cell rects for hit-testing during pointer move
  const recomputeRects = useCallback(() => {
    if (!gridRef.current) return;
    const cells = gridRef.current.querySelectorAll("[data-cell]");
    cellRectsRef.current = [...cells].map(el => {
      const r = el.getBoundingClientRect();
      return { idx: parseInt(el.dataset.cell, 10), cx: r.left + r.width / 2, cy: r.top + r.height / 2, r: r.width / 2 };
    });
  }, []);

  useEffect(() => {
    recomputeRects();
    const onResize = () => recomputeRects();
    window.addEventListener("resize", onResize);
    return () => window.removeEventListener("resize", onResize);
  }, [recomputeRects]);

  // Lazy service handles — looked up at call time so the gameplay screen
  // doesn't crash if a service module is still loading.
  const ana = () => window.WordraAnalytics;
  const snd = () => window.WordraSound;
  const hap = () => window.WordraHaptics;

  // Mute services when the user toggles inside the Pause overlay. We don't
  // route through the app-level settings here because the original gameplay
  // owns soundOn/hapticsOn state — keep that contract, just plumb it through.
  useEffect(() => { snd() && snd().setEnabled(soundOn); }, [soundOn]);
  useEffect(() => { hap() && hap().setEnabled(hapticsOn); }, [hapticsOn]);

  // ≤10s low-time tick — pulse audio/haptic once per second.
  useEffect(() => {
    if (running && !paused && timeLeft > 0 && timeLeft <= 10) {
      try { snd() && snd().timeLow(); hap() && hap().timeLow(); } catch (_) {}
    }
  }, [timeLeft, running, paused]);

  // Game timer
  useEffect(() => {
    if (!running || paused) return;
    if (timeLeft <= 0) {
      setRunning(false);
      try {
        ana() && ana().track("game_end", {
          mode, score,
          word_count: acceptedWords.length,
          longest: (acceptedWords.slice().sort((a,b) => b.length - a.length)[0] || "").length,
          items_used: Object.values(itemsUsed || {}).reduce((a,b) => a + b, 0),
        });
        snd() && snd().gameEnd();
        hap() && hap().gameEnd();
      } catch (_) {}
      setTimeout(() => onEnd && onEnd({ score, words: acceptedWords, itemsUsed, inv, newWords: newWordsThisRound }), 600);
      return;
    }
    const id = setTimeout(() => setTimeLeft(s => s - 1), 1000);
    return () => clearTimeout(id);
  }, [timeLeft, running]);

  // game_start: fire once when the timer begins running
  useEffect(() => {
    if (running) {
      try { ana() && ana().track("game_start", { mode, seed: seedState, lang }); } catch (_) {}
    }
  }, [running]);

  // Combo decay - 2s window
  useEffect(() => {
    if (!running || paused) return;
    const id = setInterval(() => {
      // honor combo lock
      if (comboLockUntilRef.current > Date.now()) {
        setComboTimer(1);
        setComboLocked(true);
        return;
      } else if (comboLocked) {
        setComboLocked(false);
        lastWordTimeRef.current = Date.now();
      }
      const elapsed = (Date.now() - lastWordTimeRef.current) / 2000;
      const remain = Math.max(0, 1 - elapsed);
      setComboTimer(remain);
      if (combo > 1 && elapsed >= 1) {
        setCombo(1);
      }
    }, 60);
    return () => clearInterval(id);
  }, [combo, running, comboLocked]);

  // Item handlers ---------------------------------------------------------
  const flashItem = (id) => {
    const meta = (window.WORDRA_ITEMS_BY_ID || {})[id];
    if (!meta) return;
    setItemFeedback({ icon: meta.icon, text: lang === "th" ? meta.nameTh : meta.nameEn });
    setTimeout(() => setItemFeedback(null), 1100);
  };

  const useItem = (id) => {
    if (!running) return;
    if (!inv[id] || inv[id] <= 0) return;
    const next = { ...inv, [id]: inv[id] - 1 };
    setInv(next);
    setItemsUsed(u => ({ ...u, [id]: (u[id] || 0) + 1 }));
    if (onInventoryChange) onInventoryChange(next);
    try {
      snd() && snd().item(id);
      hap() && hap().tap();
      ana() && ana().track("item_use", { item: id, mode });
    } catch (_) {}

    // emit sparks from item button position toward grid center
    const emitSparks = (color, count = 18) => {
      const sparks = [];
      for (let i = 0; i < count; i++) {
        sparks.push({
          id: Math.random().toString(36).slice(2),
          angle: Math.random() * Math.PI * 2,
          dist: 80 + Math.random() * 140,
          color,
          delay: Math.random() * 80,
        });
      }
      setItemSparks(sparks);
      setTimeout(() => setItemSparks([]), 1000);
    };

    if (id === "shuffle") {
      // Phase 1: cells spin & scatter out (350ms)
      // Phase 2: new board appears, cells drop in with bounce (450ms)
      flashItem("shuffle");
      setItemFx({ id: "shuffle", phase: "out", t: Date.now() });
      emitSparks("#22D3EE", 24);
      setPath([]);
      setHintIdx(null);
      setTimeout(() => {
        setSeedState(s => (s * 9301 + 49297) % 233280 + Math.floor(Math.random() * 1e6));
        setItemFx({ id: "shuffle", phase: "in", t: Date.now() });
      }, 350);
      setTimeout(() => setItemFx(null), 850);
    } else if (id === "time") {
      flashItem("time");
      setTimeBurst(b => b + 1);
      setItemFx({ id: "time", phase: "burst", t: Date.now() });
      emitSparks("#22C55E", 20);
      setTimeLeft(t => t + 10);
      // big golden +10s floater at top center
      const fid = Math.random().toString(36).slice(2);
      setFloaters(fs => [...fs, { id: fid, x: 160, y: 40, text: "+10s", combo: 5 }]);
      setTimeout(() => setFloaters(fs => fs.filter(f => f.id !== fid)), 1100);
      setTimeout(() => setItemFx(null), 800);
    } else if (id === "hint") {
      flashItem("hint");
      const pick = Math.floor(Math.random() * 25);
      setHintIdx(pick);
      setItemFx({ id: "hint", phase: "beam", target: pick, t: Date.now() });
      emitSparks("#FFD84D", 12);
      setTimeout(() => setItemFx(null), 600);
      setTimeout(() => setHintIdx(prev => (prev === pick ? null : prev)), 3000);
    } else if (id === "lock") {
      flashItem("lock");
      comboLockUntilRef.current = Date.now() + 5000;
      setComboLocked(true);
      setLockChains(true);
      setItemFx({ id: "lock", phase: "ignite", t: Date.now() });
      emitSparks("#FF8A3D", 22);
      setTimeout(() => setItemFx(null), 800);
      setTimeout(() => setLockChains(false), 5000);
    }
  };

  // pointer handlers ---------------------------------------------------------
  const startPath = (idx, ev) => {
    if (!running) return;
    setPath([idx]);
    recomputeRects();
  };

  const extendPath = (clientX, clientY) => {
    if (!running) return;
    if (path.length === 0) return;
    const rects = cellRectsRef.current;
    for (const c of rects) {
      const dx = clientX - c.cx, dy = clientY - c.cy;
      const dist = Math.hypot(dx, dy);
      if (dist < c.r * 0.75) {
        setPath(prev => {
          if (prev[prev.length - 1] === c.idx) return prev;
          // backtrack: if c.idx is the second-to-last, pop
          if (prev.length >= 2 && prev[prev.length - 2] === c.idx) {
            return prev.slice(0, -1);
          }
          if (prev.includes(c.idx)) return prev;
          // must be adjacent to last
          const last = prev[prev.length - 1];
          const ax = last % 5, ay = Math.floor(last / 5);
          const bx = c.idx % 5, by = Math.floor(c.idx / 5);
          if (Math.abs(ax - bx) > 1 || Math.abs(ay - by) > 1) return prev;
          return [...prev, c.idx];
        });
        return;
      }
    }
  };

  const endPath = () => {
    if (!running) {
      setPath([]);
      return;
    }
    if (path.length === 0) return;
    const word = E.pathToWord(path, board);
    const valid = E.isValidWord(word, lang);

    if (valid && !acceptedWords.includes(word.toLowerCase())) {
      const now = Date.now();
      const delta = now - lastWordTimeRef.current;
      lastWordTimeRef.current = now;
      const newCombo = delta < 2000 ? Math.min(5, combo + 1) : 1;
      const gained = E.scoreWord(word, newCombo);
      setCombo(newCombo);
      setScore(s => s + gained);
      setAcceptedWords(w => [word.toLowerCase(), ...w].slice(0, 12));
      setFeedback({ kind: "good", text: `+${gained}` });

      // Flash-card vocab tracking — record before computing isNew so the
      // celebration triggers on the FIRST-ever capture, not on subsequent rounds.
      let vocabIsNew = false;
      try {
        const v = window.WordraVocab;
        if (v) {
          const res = v.record(word, { lang, combo: newCombo, gained });
          vocabIsNew = !!res.isNew;
          setNewWordsThisRound(n => vocabIsNew ? n + 1 : n);
        }
      } catch (_) {}

      // Analytics + audio + haptic feedback on each accepted word.
      try {
        ana() && ana().track("word_accept", {
          length: word.length, combo: newCombo, gained, mode, new_word: vocabIsNew,
        });
        if (newCombo > combo && newCombo >= 3) {
          ana() && ana().track("combo_reached", { combo: newCombo, mode });
        }
        if (newCombo >= 5) {
          snd() && snd().comboPeak();
          hap() && hap().combo(5);
        } else if (newCombo > combo && newCombo >= 2) {
          snd() && snd().comboUp(newCombo);
          hap() && hap().combo(newCombo);
        } else {
          snd() && snd().wordAccept(word.length, newCombo);
          hap() && hap().wordAccept();
        }
      } catch (_) {}

      // floating score at last cell position
      const lastIdx = path[path.length - 1];
      const lastCell = cellRectsRef.current.find(r => r.idx === lastIdx);
      if (lastCell && gridRef.current) {
        const grect = gridRef.current.getBoundingClientRect();
        const fx = lastCell.cx - grect.left;
        const fy = lastCell.cy - grect.top;
        const fid = Math.random().toString(36).slice(2);
        setFloaters(fs => [...fs, { id: fid, x: fx, y: fy, text: `+${gained}`, combo: newCombo }]);
        setTimeout(() => setFloaters(fs => fs.filter(f => f.id !== fid)), 1100);

        // First-ever capture of this word — drop a celebratory NEW WORD pill
        // 32px above the score, slightly delayed so it doesn't visually collide.
        if (vocabIsNew) {
          const nid = Math.random().toString(36).slice(2);
          setFloaters(fs => [...fs, { id: nid, x: fx, y: fy - 32, text: "✨ NEW WORD", combo: 5, kind: "new" }]);
          setTimeout(() => setFloaters(fs => fs.filter(f => f.id !== nid)), 1400);
        }
      }

      // burst effects on combo milestones
      if (newCombo >= 3) {
        const ps = [];
        for (let i = 0; i < (newCombo >= 5 ? 24 : 14); i++) {
          ps.push({
            id: Math.random().toString(36).slice(2),
            angle: Math.random() * Math.PI * 2,
            dist: 60 + Math.random() * 80,
            life: 1,
          });
        }
        setParticles(ps);
        setTimeout(() => setParticles([]), 700);
      }
      if (newCombo >= 5) {
        setShake(1);
        setTimeout(() => setShake(0), 300);
      }
    } else {
      // invalid feedback (only when the path was long enough to be a real attempt)
      setFeedback({ kind: valid ? "dup" : "bad", text: valid ? t("Already used", "ใช้ไปแล้ว") : t("Not a word", "ไม่ใช่คำ") });
      if (path.length >= 3) {
        try { snd() && snd().reject(); hap() && hap().reject(); } catch (_) {}
      }
    }
    setTimeout(() => setFeedback(null), 900);
    setPath([]);
  };

  const onPointerDown = (e) => {
    e.preventDefault();
    const target = e.target.closest("[data-cell]");
    if (!target) return;
    const idx = parseInt(target.dataset.cell, 10);
    target.setPointerCapture && target.setPointerCapture(e.pointerId);
    startPath(idx, e);
  };
  const onPointerMove = (e) => {
    if (path.length === 0) return;
    extendPath(e.clientX, e.clientY);
  };
  const onPointerUp = () => endPath();

  // current word string
  const currentWord = path.map(i => board[i]).join("");
  const previewValid = currentWord.length >= 3 && E.isValidWord(currentWord, lang);

  // SVG line path through selected cells
  const linePoints = useMemo(() => {
    if (path.length < 2 || !gridRef.current) return "";
    const grect = gridRef.current.getBoundingClientRect();
    return path.map(idx => {
      const c = cellRectsRef.current.find(r => r.idx === idx);
      if (!c) return "";
      return `${c.cx - grect.left},${c.cy - grect.top}`;
    }).filter(Boolean).join(" ");
  }, [path]);

  const lowTime = timeLeft <= 10;

  const comboColor = combo >= 5 ? "var(--gold)" : combo >= 3 ? "#FF8A3D" : combo >= 2 ? "#FFD84D" : "#3a3a44";

  return (
    <div className="wordra-gameplay" style={{
      transform: shake ? `translate(${(Math.random() - 0.5) * 8}px, ${(Math.random() - 0.5) * 8}px)` : "none",
      transition: shake ? "none" : "transform 60ms",
    }}>
      {/* TOP BAR */}
      <div className="wg-topbar">
        <button className="wg-back" onClick={() => setPaused(true)} aria-label="Pause">
          <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/></svg>
        </button>
        <div className={"wg-timer " + (lowTime ? "low" : "") + (timeBurst ? " bonus" : "")} key={"timer-" + timeBurst}>
          <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><circle cx="12" cy="13" r="8"/><path d="M12 9v4l2 2"/><path d="M9 2h6"/></svg>
          <span>{String(Math.floor(timeLeft / 60)).padStart(2, "0")}:{String(timeLeft % 60).padStart(2, "0")}</span>
        </div>
        <div className="wg-score">
          <span className="wg-score-label">{t("SCORE", "คะแนน")}</span>
          <span className="wg-score-num">{score.toLocaleString()}</span>
        </div>
      </div>

      {/* CURRENT WORD PREVIEW */}
      <div className="wg-wordbar">
        {currentWord ? (
          <div className={"wg-preview " + (previewValid ? "valid" : currentWord.length >= 3 ? "maybe" : "")}>
            {currentWord.split("").map((ch, i) => (
              <span key={i} className="wg-prev-ch">{ch}</span>
            ))}
            {previewValid && (
              <span className="wg-prev-tick">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
              </span>
            )}
          </div>
        ) : (
          <div className="wg-hint">{t("Drag to spell ≥ 3 letters", "ลากเพื่อสะกดคำ ≥ 3 ตัว")}</div>
        )}
      </div>

      {/* GRID */}
      <div
        className="wg-grid"
        ref={gridRef}
        onPointerDown={onPointerDown}
        onPointerMove={onPointerMove}
        onPointerUp={onPointerUp}
        onPointerCancel={onPointerUp}
        onPointerLeave={() => path.length && endPath()}
      >
        {/* connection line */}
        {linePoints && (
          <svg className="wg-line" preserveAspectRatio="none">
            <polyline points={linePoints} fill="none" stroke={previewValid ? "var(--gold)" : "var(--purple)"} strokeWidth="6" strokeLinecap="round" strokeLinejoin="round" opacity="0.9"/>
          </svg>
        )}
        {board.map((letter, i) => {
          const sel = path.includes(i);
          const order = path.indexOf(i);
          const isLast = order === path.length - 1 && order !== -1;
          const isHint = hintIdx === i;
          const fxClass = itemFx && itemFx.id === "shuffle"
            ? (itemFx.phase === "out" ? "fx-shuffle-out" : "fx-shuffle-in")
            : (itemFx && itemFx.id === "hint" && itemFx.target === i ? "fx-hint-target" : "");
          const cellDelay = (itemFx && itemFx.id === "shuffle") ? `${(i % 5 + Math.floor(i / 5)) * 18}ms` : "0ms";
          return (
            <div
              key={i}
              data-cell={i}
              className={"wg-cell " + (sel ? "sel " : "") + (isLast ? "last " : "") + (previewValid && sel ? "valid " : "") + (isHint ? "hint " : "") + fxClass}
              style={{ animationDelay: cellDelay }}
            >
              <span className="wg-letter">{letter}</span>
              {sel && <span className="wg-order">{order + 1}</span>}
            </div>
          );
        })}

        {/* item sparks — emitted on use */}
        {itemSparks.map(s => (
          <span key={s.id} className="wg-item-spark" style={{
            "--a": s.angle + "rad",
            "--d": s.dist + "px",
            "--c": s.color,
            animationDelay: s.delay + "ms",
          }}/>
        ))}

        {/* shuffle vortex */}
        {itemFx && itemFx.id === "shuffle" && (
          <div className={"wg-vortex " + itemFx.phase}>
            <span className="wg-vortex-ring r1"/>
            <span className="wg-vortex-ring r2"/>
            <span className="wg-vortex-ring r3"/>
          </div>
        )}

        {/* time burst — golden expanding ring */}
        {itemFx && itemFx.id === "time" && (
          <div className="wg-time-burst">
            <span className="wg-time-ring"/>
            <span className="wg-time-ring delay"/>
            <span className="wg-time-clock">⏱</span>
          </div>
        )}

        {/* hint beam — light radiating from target cell */}
        {itemFx && itemFx.id === "hint" && (
          <div className="wg-hint-fx">
            <span className="wg-hint-spotlight" style={{
              "--gx": (itemFx.target % 5),
              "--gy": Math.floor(itemFx.target / 5),
            }}/>
          </div>
        )}

        {/* lock — flame ring around grid */}
        {itemFx && itemFx.id === "lock" && (
          <div className="wg-lock-ignite">
            <span className="wg-lock-flame"/>
          </div>
        )}

        {/* particles */}
        {particles.map(p => (
          <span key={p.id} className="wg-particle" style={{
            "--a": p.angle + "rad",
            "--d": p.dist + "px",
          }}/>
        ))}

        {/* floating scores */}
        {floaters.map(f => (
          <span key={f.id} className={"wg-floater " + (f.kind === "new" ? "new-word" : "")} style={{
            left: f.x + "px",
            top: f.y + "px",
            color: f.kind === "new" ? "var(--gold)" : f.combo >= 5 ? "var(--gold)" : f.combo >= 3 ? "#FF8A3D" : "#FFFFFF"
          }}>
            {f.text}
            {f.kind !== "new" && f.combo >= 2 && <small> ×{f.combo}</small>}
          </span>
        ))}
      </div>

      {/* COMBO BAR */}
      <div className={"wg-combo " + (lockChains ? "lock-chains" : "")}>
        <div className="wg-combo-label">
          <span className={"wg-combo-mult " + (combo >= 5 ? "peak" : combo >= 3 ? "hot" : "") + (comboLocked ? " locked" : "")}>
            {comboLocked ? t("LOCKED", "ล็อก") : combo >= 5 ? t("PEAK", "พีค") : combo >= 3 ? t("HOT", "ฮอต") : combo >= 2 ? t("COMBO", "คอมโบ") : t("READY", "พร้อม")}
          </span>
          <span className="wg-combo-x">×{combo}</span>
        </div>
        <div className="wg-combo-track">
          <div className={"wg-combo-fill " + (comboLocked ? "locked" : "")} style={{
            width: (comboTimer * 100) + "%",
            background: comboLocked ? "var(--purple)" : comboColor,
            boxShadow: combo >= 3 || comboLocked ? `0 0 16px ${comboLocked ? "var(--purple)" : comboColor}` : "none"
          }}/>
        </div>
        {lockChains && (
          <span className="wg-lock-chain-fx">
            <span className="wg-lock-spark s1"/>
            <span className="wg-lock-spark s2"/>
            <span className="wg-lock-spark s3"/>
          </span>
        )}
      </div>

      {/* ITEMS HUD */}
      <div className="wg-items">
        {(window.WORDRA_ITEMS || []).map(it => {
          const count = inv[it.id] || 0;
          const disabled = count <= 0 || !running;
          const firing = itemFx && itemFx.id === it.id;
          return (
            <button
              key={it.id}
              className={"wg-item " + (disabled ? "disabled " : "") + (firing ? "firing " : "")}
              onPointerDown={(e) => { e.stopPropagation(); }}
              onClick={(e) => { e.preventDefault(); e.stopPropagation(); useItem(it.id); }}
              style={{ "--item-color": it.color }}
              disabled={disabled}
              title={lang === "th" ? it.descTh : it.descEn}
            >
              <span className="wg-item-icon">{it.icon}</span>
              <span className="wg-item-name">{lang === "th" ? it.nameTh : it.nameEn}</span>
              <span className="wg-item-count">{count}</span>
              {firing && <span className="wg-item-ripple"/>}
            </button>
          );
        })}
      </div>

      {/* item flash — big Candy-Crush-style banner */}
      {itemFeedback && (
        <div className={"wg-item-flash flash-" + (itemFx ? itemFx.id : "")}>
          <span className="wg-item-flash-icon">{itemFeedback.icon}</span>
          <span className="wg-item-flash-text">{itemFeedback.text}</span>
          <span className="wg-item-flash-rays"/>
        </div>
      )}

      {/* pause overlay */}
      {paused && window.WordraPause && (
        <window.WordraPause
          lang={lang}
          soundOn={soundOn}
          hapticsOn={hapticsOn}
          onToggleSound={() => setSoundOn(v => !v)}
          onToggleHaptics={() => setHapticsOn(v => !v)}
          onResume={() => setPaused(false)}
          onRestart={() => { setPaused(false); setScore(0); setTimeLeft(duration); setAcceptedWords([]); setCombo(1); setSeedState(s => s + 1); setCounting(true); setRunning(false); }}
          onExit={() => { setPaused(false); onExit && onExit(); }}
        />
      )}

      {/* countdown */}
      {counting && window.WordraCountdown && (
        <window.WordraCountdown lang={lang} onDone={() => { setCounting(false); setRunning(true); lastWordTimeRef.current = Date.now(); }}/>
      )}

      {/* feedback toast */}
      {feedback && (
        <div className={"wg-toast " + feedback.kind}>{feedback.text}</div>
      )}

      {/* recent words */}
      <div className="wg-recent">
        {acceptedWords.slice(0, 6).map((w, i) => (
          <span key={i} className="wg-chip" style={{ opacity: 1 - i * 0.12 }}>{w}</span>
        ))}
      </div>
    </div>
  );
}

window.WordraGameplay = WordraGameplay;
