// Activities part 2: WordSearch, Crossword, Puzzle (CreateJS)

const { useState: useStateB, useEffect: useEffectB, useRef: useRefB, useMemo: useMemoB, useCallback: useCallbackB } = React;

// =========================================================
// 5. SOPA DE NÚMEROS — find numeric results in a grid
// =========================================================
function ActWordSearch({ theme, difficulty, onCorrect, onMistake, onFinish, data }) {
  const cfg = { facil: { size: 8, count: 4 }, media: { size: 10, count: 5 }, dificil: { size: 12, count: 6 } }[difficulty] || { size: 10, count: 5 };

  const puzzle = useMemoB(() => {
    let targets = [];
    let isWords = false;
    let customTitle = "Encuentra las palabras en la sopa de letras.";

    if (Array.isArray(data?.palabras) && data.palabras.length > 0) {
      isWords = true;
      targets = data.palabras
        .map(w => typeof w === 'string' ? w : (w.palabra || w.word || w.concepto || ''))
        .map(w => String(w).toUpperCase().replace(/[^A-ZÁÉÍÓÚÑ]/g, ''))
        .filter(w => w.length > 1)
        .map(w => ({ expr: w, r: w, str: w }));
    } else if (Array.isArray(data?.conceptos) && data.conceptos.length > 0) {
      // Fallback for some AI formats
      isWords = true;
      targets = data.conceptos
        .map(w => String(w).toUpperCase().replace(/[^A-ZÁÉÍÓÚÑ]/g, ''))
        .filter(w => w.length > 1)
        .map(w => ({ expr: w, r: w, str: w }));
    }

    if (!isWords || targets.length === 0) {
      isWords = false;
      customTitle = "Encuentra los resultados de cada operación en la sopa.";
      while (targets.length < cfg.count) {
        const a = 2 + Math.floor(Math.random()*9);
        const b = 2 + Math.floor(Math.random()*9);
        const op = Math.random() < 0.5 ? "+" : "×";
        const r = op === "+" ? a + b : a * b;
        const str = String(r);
        if (str.length >= 2 && str.length <= 3 && !targets.some(t => t.r === r)) {
          targets.push({ expr: `${a}${op}${b}`, r, str });
        }
      }
    }

    const maxLen = Math.max(...targets.map(t => t.str.length), 0);
    const N = Math.max(cfg.size, maxLen + 2, 8);
    const grid = Array.from({length: N}, () => Array.from({length: N}, () => null));
    const placements = [];

    targets.sort((a, b) => b.str.length - a.str.length);
    const dirs = [[1,0],[0,1],[1,1],[-1,1],[1,-1],[0,-1],[-1,0]]; // more directions
    
    targets.forEach(t => {
      let placed = false;
      for (let tries = 0; tries < 200 && !placed; tries++) {
        const dir = dirs[Math.floor(Math.random()*dirs.length)];
        const len = t.str.length;
        const r0 = Math.floor(Math.random()*N);
        const c0 = Math.floor(Math.random()*N);
        const cells = [];
        let ok = true;
        for (let i = 0; i < len; i++) {
          const r = r0 + dir[1]*i, c = c0 + dir[0]*i;
          if (r<0||r>=N||c<0||c>=N) { ok=false; break; }
          if (grid[r][c] != null && grid[r][c] !== t.str[i]) { ok=false; break; }
          cells.push([r,c]);
        }
        if (ok) {
          cells.forEach(([r,c],i) => { grid[r][c] = t.str[i]; });
          placements.push({ ...t, cells });
          placed = true;
        }
      }
    });

    const alphabet = "ABCDEFGHIJKLMNÑOPQRSTUVWXYZ";
    for (let r=0;r<N;r++) for (let c=0;c<N;c++) {
      if (grid[r][c] == null) {
        grid[r][c] = isWords ? alphabet[Math.floor(Math.random() * alphabet.length)] : String(Math.floor(Math.random()*10));
      }
    }
    
    return { N, grid, placements, title: customTitle };
  }, [cfg.size, cfg.count, data]);

  const [path, setPath] = useStateB([]); // [[r,c], ...]
  const [found, setFound] = useStateB([]); // [{cells, r}]
  const [drag, setDrag] = useStateB(false);

  useEffectB(() => {
    if (found.length === puzzle.placements.length) {
      setTimeout(() => onFinish(found.length, puzzle.placements.length), 600);
    }
  }, [found.length]);

  const cellKey = (r,c) => `${r}-${c}`;
  const isInPath = (r,c) => path.some(([pr,pc]) => pr===r && pc===c);
  const isFound = (r,c) => found.some(f => f.cells.some(([pr,pc]) => pr===r && pc===c));

  const startPath = (r,c) => { setPath([[r,c]]); setDrag(true); window.SFX && window.SFX.pickup(); };
  const extendPath = (r,c) => {
    if (!drag) return;
    if (path.length === 0) return;
    if (isInPath(r,c)) return;
    // Constrain to straight line from start
    const [sr, sc] = path[0];
    const dr = r - sr, dc = c - sc;
    if (!(dr === 0 || dc === 0 || Math.abs(dr) === Math.abs(dc))) return;
    const len = Math.max(Math.abs(dr), Math.abs(dc));
    const stepR = len === 0 ? 0 : dr / len, stepC = len === 0 ? 0 : dc / len;
    const cells = [];
    for (let i = 0; i <= len; i++) cells.push([sr + stepR*i, sc + stepC*i]);
    setPath(cells);
  };
  const endPath = () => {
    setDrag(false);
    if (path.length < 2) { setPath([]); return; }
    const str = path.map(([r,c]) => puzzle.grid[r][c]).join("");
    const strRev = path.map(([r,c]) => puzzle.grid[r][c]).reverse().join("");
    const match = puzzle.placements.find(p =>
      !found.some(f => f.r === p.r) && (p.str === str || p.str === strRev)
    );
    if (match) {
      setFound(f => [...f, { cells: path, r: match.r, expr: match.expr }]);
      onCorrect();
    } else {
      onMistake();
    }
    setPath([]);
  };

  return (
    <div className="ma-ws">
      <div className="ma-instruction">{puzzle.title}</div>
      <div className="ma-ws-layout">
        <div className="ma-ws-grid"
          style={{gridTemplateColumns: `repeat(${puzzle.N}, 1fr)`}}
          onMouseLeave={endPath}
          onMouseUp={endPath}
          onTouchEnd={endPath}>
          {puzzle.grid.map((row, r) => row.map((ch, c) => {
            const inP = isInPath(r,c), fnd = isFound(r,c);
            return (
              <div key={cellKey(r,c)}
                className={`ma-ws-cell ${inP?"is-path":""} ${fnd?"is-found":""}`}
                onMouseDown={()=>startPath(r,c)}
                onMouseEnter={()=>extendPath(r,c)}
                onTouchStart={()=>startPath(r,c)}
                onTouchMove={(e)=>{
                  const t = e.touches[0];
                  const el = document.elementFromPoint(t.clientX, t.clientY);
                  if (el?.dataset?.ws) {
                    const [rr,cc] = el.dataset.ws.split("-").map(Number);
                    extendPath(rr,cc);
                  }
                }}
                data-ws={cellKey(r,c)}>
                {ch}
              </div>
            );
          }))}
        </div>
        <div className="ma-ws-list">
          <div className="ma-ws-list-title">Encuentra:</div>
          {puzzle.placements.map(p => {
            const done = found.some(f => f.r === p.r);
            return (
              <div key={p.r} className={`ma-ws-clue ${done?"is-done":""}`}>
                <span className="ma-ws-expr">{p.expr}</span>
                <span className="ma-ws-eq">=</span>
                <span className="ma-ws-r">{done ? p.r : "?"}</span>
              </div>
            );
          })}
        </div>
      </div>
    </div>
  );
}

// =========================================================
// 6. CRUCIGRAMA NUMÉRICO — fill numbers across/down
// =========================================================
function ActCrossword({ theme, difficulty, onCorrect, onMistake, onFinish, data }) {
  const layout = useMemoB(() => {
    let customPistas = [];
    if (Array.isArray(data?.pistas) && data.pistas.length) {
      customPistas = data.pistas
        .map((p) => ({
          pista: String(p?.pista ?? ''),
          respuesta: String(p?.respuesta ?? '').replace(/\s+/g, '').toUpperCase(),
        }))
        .filter((p) => p.pista && p.respuesta);
    }

    if (customPistas.length > 0) {
      const MAX_N = 16;
      const gridChars = Array.from({length: MAX_N}, () => Array.from({length: MAX_N}, () => null));
      const slots = [];
      const answers = {};
      const clues = {};
      let idCounter = 1;

      // Sort by length to place longest words first
      customPistas.sort((a,b) => b.respuesta.length - a.respuesta.length);

      customPistas.forEach((item, idx) => {
        const word = item.respuesta;
        let placed = false;
        
        if (idx === 0) {
          const r = Math.floor(MAX_N/2);
          const c = Math.floor((MAX_N - word.length)/2);
          for (let i=0; i<word.length; i++) gridChars[r][c+i] = word[i];
          const id = `${idCounter}H`;
          slots.push({ id, dir: "H", r, c, len: word.length });
          answers[id] = word;
          clues[id] = item.pista;
          idCounter++;
          placed = true;
        } else {
          for (let w=0; w<word.length && !placed; w++) {
            for (let r=0; r<MAX_N && !placed; r++) {
              for (let c=0; c<MAX_N && !placed; c++) {
                if (gridChars[r][c] === word[w]) {
                  // Try Horizontal
                  let canH = true;
                  const hc = c - w;
                  if (hc < 0 || hc + word.length > MAX_N) canH = false;
                  else {
                    for (let i=0; i<word.length; i++) {
                      if (i === w) continue;
                      if (gridChars[r][hc+i] != null || 
                          (r>0 && gridChars[r-1][hc+i] != null) || 
                          (r<MAX_N-1 && gridChars[r+1][hc+i] != null)) { canH = false; break; }
                    }
                    if (canH && hc>0 && gridChars[r][hc-1] != null) canH = false;
                    if (canH && hc+word.length<MAX_N && gridChars[r][hc+word.length] != null) canH = false;
                  }

                  if (canH) {
                    for (let i=0; i<word.length; i++) gridChars[r][hc+i] = word[i];
                    const id = `${idCounter}H`;
                    slots.push({ id, dir: "H", r, c: hc, len: word.length });
                    answers[id] = word;
                    clues[id] = item.pista;
                    idCounter++;
                    placed = true;
                    break;
                  }

                  // Try Vertical
                  let canV = true;
                  const vr = r - w;
                  if (vr < 0 || vr + word.length > MAX_N) canV = false;
                  else {
                    for (let i=0; i<word.length; i++) {
                      if (i === w) continue;
                      if (gridChars[vr+i][c] != null || 
                          (c>0 && gridChars[vr+i][c-1] != null) || 
                          (c<MAX_N-1 && gridChars[vr+i][c+1] != null)) { canV = false; break; }
                    }
                    if (canV && vr>0 && gridChars[vr-1][c] != null) canV = false;
                    if (canV && vr+word.length<MAX_N && gridChars[vr+word.length][c] != null) canV = false;
                  }

                  if (canV) {
                    for (let i=0; i<word.length; i++) gridChars[vr+i][c] = word[i];
                    const id = `${idCounter}V`;
                    slots.push({ id, dir: "V", r: vr, c, len: word.length });
                    answers[id] = word;
                    clues[id] = item.pista;
                    idCounter++;
                    placed = true;
                    break;
                  }
                }
              }
            }
          }

          if (!placed) {
            for (let tries=0; tries<100 && !placed; tries++) {
              const dir = Math.random() < 0.5 ? "H" : "V";
              const r = Math.floor(Math.random()*MAX_N);
              const c = Math.floor(Math.random()*MAX_N);
              let can = true;
              if (dir === "H") {
                if (c + word.length > MAX_N) can = false;
                else {
                  for (let i=0; i<word.length; i++) {
                    if (gridChars[r][c+i] != null || 
                        (r>0 && gridChars[r-1][c+i] != null) || 
                        (r<MAX_N-1 && gridChars[r+1][c+i] != null)) { can = false; break; }
                  }
                  if (can && c>0 && gridChars[r][c-1] != null) can = false;
                  if (can && c+word.length<MAX_N && gridChars[r][c+word.length] != null) can = false;
                }
              } else {
                if (r + word.length > MAX_N) can = false;
                else {
                  for (let i=0; i<word.length; i++) {
                    if (gridChars[r+i][c] != null || 
                        (c>0 && gridChars[r+i][c-1] != null) || 
                        (c<MAX_N-1 && gridChars[r+i][c+1] != null)) { can = false; break; }
                  }
                  if (can && r>0 && gridChars[r-1][c] != null) can = false;
                  if (can && r+word.length<MAX_N && gridChars[r+word.length][c] != null) can = false;
                }
              }

              if (can) {
                for (let i=0; i<word.length; i++) {
                  if (dir === "H") gridChars[r][c+i] = word[i];
                  else gridChars[r+i][c] = word[i];
                }
                const id = `${idCounter}${dir}`;
                slots.push({ id, dir, r, c, len: word.length });
                answers[id] = word;
                clues[id] = item.pista;
                idCounter++;
                placed = true;
              }
            }
          }
        }
      });

      let minR = MAX_N, maxR = 0, minC = MAX_N, maxC = 0;
      slots.forEach(s => {
        if (s.r < minR) minR = s.r;
        if (s.c < minC) minC = s.c;
        if (s.dir === "H") {
          if (s.r > maxR) maxR = s.r;
          if (s.c + s.len - 1 > maxC) maxC = s.c + s.len - 1;
        } else {
          if (s.r + s.len - 1 > maxR) maxR = s.r + s.len - 1;
          if (s.c > maxC) maxC = s.c;
        }
      });
      if (minR > maxR) { minR = 0; maxR = 0; minC = 0; maxC = 0; }
      
      const pad = 1;
      minR = Math.max(0, minR - pad);
      minC = Math.max(0, minC - pad);
      maxR = Math.min(MAX_N - 1, maxR + pad);
      maxC = Math.min(MAX_N - 1, maxC + pad);

      const finalN = Math.max(maxR - minR + 1, maxC - minC + 1, 5);
      const active = Array.from({length: finalN}, () => Array.from({length: finalN}, () => false));
      
      slots.forEach(s => {
        s.r -= minR;
        s.c -= minC;
        for (let i=0; i<s.len; i++) {
          const rr = s.dir === "H" ? s.r : s.r + i;
          const cc = s.dir === "H" ? s.c + i : s.c;
          active[rr][cc] = true;
        }
      });

      return { N: finalN, slots, answers, clues, active, title: "Resuelve el crucigrama leyendo las pistas." };
    }

    // Fallback math crossword
    const N = 5;
    const slots = [
      { id: "1H", dir: "H", r: 0, c: 0, len: 3 },
      { id: "2H", dir: "H", r: 2, c: 1, len: 4 },
      { id: "3H", dir: "H", r: 4, c: 0, len: 3 },
      { id: "1V", dir: "V", r: 0, c: 2, len: 5 },
      { id: "2V", dir: "V", r: 1, c: 4, len: 4 },
    ];
    function genNumber(len) {
      const min = Math.pow(10, len-1);
      const max = Math.pow(10, len) - 1;
      return Math.floor(Math.random()*(max-min+1))+min;
    }
    let answers = {};
    for (let attempt = 0; attempt < 200; attempt++) {
      answers = {};
      slots.forEach(s => answers[s.id] = String(genNumber(s.len)));
      let ok = true;
      slots.forEach(s => {
        for (let i = 0; i < s.len; i++) {
          const r = s.dir==="H" ? s.r : s.r+i;
          const c = s.dir==="H" ? s.c+i : s.c;
          slots.forEach(o => {
            if (o.id === s.id) return;
            for (let j = 0; j < o.len; j++) {
              const or = o.dir==="H" ? o.r : o.r+j;
              const oc = o.dir==="H" ? o.c+j : o.c;
              if (or === r && oc === c) {
                if (answers[s.id][i] !== answers[o.id][j]) ok = false;
              }
            }
          });
        }
      });
      if (ok) break;
    }
    if (Object.keys(answers).length < slots.length) {
      answers = { "1H": "123", "2H": "4567", "3H": "890", "1V": "32850", "2V": "5670" };
    }
    function makeClue(num) {
      const n = parseInt(num, 10);
      const f = 2 + Math.floor(Math.random()*8);
      if (n % f === 0) return `${n/f} × ${f}`;
      const s = Math.max(1, Math.floor(n/2));
      return `${s} + ${n - s}`;
    }
    const clues = {};
    slots.forEach(s => clues[s.id] = makeClue(answers[s.id]));

    const active = Array.from({length:N}, () => Array.from({length:N}, () => false));
    slots.forEach(s => {
      for (let i = 0; i < s.len; i++) {
        const r = s.dir==="H" ? s.r : s.r+i;
        const c = s.dir==="H" ? s.c+i : s.c;
        active[r][c] = true;
      }
    });
    return { N, slots, answers, clues, active, title: "Resuelve cada operación y escribe el resultado en el crucigrama." };
  }, [difficulty, data]);

  const [grid, setGrid] = useStateB(() =>
    Array.from({length: layout.N}, () => Array.from({length: layout.N}, () => ""))
  );
  const [checked, setChecked] = useStateB(false);

  const set = (r, c, v) => {
    if (v.length > 1) v = v.slice(-1);
    if (v && !/[A-Za-z0-9ÁÉÍÓÚáéíóúÑñ]/.test(v)) return;
    const next = grid.map(row => [...row]);
    next[r][c] = String(v).toUpperCase();
    setGrid(next);
  };

  const check = () => {
    setChecked(true);
    let allRight = true;
    layout.slots.forEach(s => {
      const got = [];
      for (let i = 0; i < s.len; i++) {
        const r = s.dir==="H" ? s.r : s.r+i;
        const c = s.dir==="H" ? s.c+i : s.c;
        got.push(grid[r][c]);
      }
      if (got.join("") !== layout.answers[s.id]) allRight = false;
    });
    if (allRight) { onCorrect(); setTimeout(() => onFinish(layout.slots.length, layout.slots.length), 500); }
    else { onMistake(); setTimeout(() => setChecked(false), 1200); }
  };

  const labels = {};
  let n = 1;
  layout.slots.forEach(s => {
    const k = `${s.r}-${s.c}`;
    if (!labels[k]) { labels[k] = n++; }
  });

  return (
    <div className="ma-cw">
      <div className="ma-instruction">{layout.title}</div>
      <div className="ma-cw-layout">
        <div className="ma-cw-grid" style={{gridTemplateColumns: `repeat(${layout.N}, 44px)`}}>
          {Array.from({length: layout.N}).map((_, r) => Array.from({length: layout.N}).map((_, c) => {
            if (!layout.active[r][c]) return <div key={`${r}-${c}`} className="ma-cw-block"/>;
            const isStart = layout.slots.some(s => s.r===r && s.c===c);
            const correct = checked && grid[r][c] === (() => {
              const sl = layout.slots.find(s => (s.dir==="H" && s.r===r && s.c<=c && s.c+s.len>c) || (s.dir==="V" && s.c===c && s.r<=r && s.r+s.len>r));
              if (sl) return layout.answers[sl.id][sl.dir==="H" ? c - sl.c : r - sl.r];
              return null;
            })();
            return (
              <div key={`${r}-${c}`} className={`ma-cw-cell ${checked?(correct?"is-right":"is-wrong"):""}`}>
                {isStart && <span className="ma-cw-num">{labels[`${r}-${c}`]}</span>}
                <input maxLength={1} value={grid[r][c]} onChange={e=>set(r,c,e.target.value)}/>
              </div>
            );
          }))}
        </div>
        <div className="ma-cw-clues">
          <div className="ma-cw-clues-title">Horizontales</div>
          {layout.slots.filter(s=>s.dir==="H").map(s => (
            <div key={s.id} className="ma-cw-clue">
              <strong>{labels[`${s.r}-${s.c}`]}.</strong> {layout.clues[s.id]}
            </div>
          ))}
          <div className="ma-cw-clues-title">Verticales</div>
          {layout.slots.filter(s=>s.dir==="V").map(s => (
            <div key={s.id} className="ma-cw-clue">
              <strong>{labels[`${s.r}-${s.c}`]}.</strong> {layout.clues[s.id]}
            </div>
          ))}
        </div>
      </div>
      <button className="ma-btn ma-btn-primary ma-btn-lg" onClick={check}>Comprobar ✓</button>
    </div>
  );
}

// =========================================================
// 7. ROMPECABEZAS (CreateJS) — slide puzzle
// =========================================================
function ActPuzzle({ theme, difficulty, onCorrect, onMistake, onFinish, data }) {
  const N = { facil: 3, media: 3, dificil: 4 }[difficulty] || 3;
  const canvasRef = useRefB(null);
  const stageRef = useRefB(null);
  const [solved, setSolved] = useStateB(false);
  const tilesRef = useRefB([]);
  const orderRef = useRefB([]);
  const SIZE = 360;
  const T = SIZE / N;

  useEffectB(() => {
    if (!window.createjs || !canvasRef.current) return;
    const stage = new createjs.Stage(canvasRef.current);
    stage.enableMouseOver(20);
    stageRef.current = stage;

    const colors = [theme.primary, theme.secondary, theme.accent, theme.success, theme.star];

    const tiles = [];
    for (let i = 0; i < N*N; i++) {
      const tile = new createjs.Container();
      const r = Math.floor(i/N), c = i%N;
      const bg = new createjs.Shape();
      const col = colors[(r*7+c*3)%colors.length];
      bg.graphics.beginFill(col).drawRoundRect(2,2,T-4,T-4,8);
      
      const txtStr = (i === N*N - 1) ? "" : String(i + 1);
      const txt = new createjs.Text(txtStr, `bold ${T*0.5}px sans-serif`, "#ffffff");
      txt.textAlign = "center"; txt.textBaseline = "middle";
      txt.x = T/2; txt.y = T/2;
      
      const border = new createjs.Shape();
      border.graphics.setStrokeStyle(3).beginStroke("rgba(0,0,0,0.25)").drawRoundRect(2,2,T-4,T-4,8);
      
      tile.addChild(bg, txt, border);
      tile.regX = 0; tile.regY = 0;
      tile.targetIndex = i;
      tile.on("click", () => tryMove(tile));
      tiles.push(tile);
    }
    
    tiles[N*N-1].alpha = 0;
    tiles[N*N-1].mouseEnabled = false;

    const order = tiles.slice();
    // shuffle by valid moves to ensure solvability
    for (let s = 0; s < 100; s++) {
      const empty = order.findIndex(t => t.alpha === 0);
      const er = Math.floor(empty/N), ec = empty%N;
      const cands = [];
      [[-1,0],[1,0],[0,-1],[0,1]].forEach(([dr,dc]) => {
        const nr = er+dr, nc = ec+dc;
        if (nr>=0 && nr<N && nc>=0 && nc<N) cands.push(nr*N+nc);
      });
      const pick = cands[Math.floor(Math.random()*cands.length)];
      [order[empty], order[pick]] = [order[pick], order[empty]];
    }
    orderRef.current = order;

    function layout() {
      orderRef.current.forEach((t, idx) => {
        const r = Math.floor(idx/N), c = idx%N;
        createjs.Tween.get(t, { override: true }).to({ x: c*T, y: r*T }, 180, createjs.Ease.quadOut);
      });
      stage.addChild(...orderRef.current);
      stage.update();
    }

    function checkWin() {
      const ord = orderRef.current;
      const won = ord.every((t, i) => t.targetIndex === i);
      if (won) {
        setSolved(true);
        ord[N*N-1].alpha = 1;
        ord[N*N-1].mouseEnabled = false;
        stage.update();
        onCorrect();
        setTimeout(() => onFinish(N*N, N*N), 800);
      }
    }

    function tryMove(tile) {
      if (solved) return;
      const ord = orderRef.current;
      const idx = ord.indexOf(tile);
      const empty = ord.findIndex(t => t.alpha === 0);
      const ir = Math.floor(idx/N), ic = idx%N;
      const er = Math.floor(empty/N), ec = empty%N;
      if (Math.abs(ir-er)+Math.abs(ic-ec) === 1) {
        [ord[idx], ord[empty]] = [ord[empty], ord[idx]];
        layout();
        window.SFX && window.SFX.pickup();
        checkWin();
      }
    }

    function giveHint() {
      if (solved) return;
      const ord = orderRef.current;
      for (let i = 0; i < N*N - 1; i++) {
        if (ord[i].targetIndex !== i) {
          const correctIdx = ord.findIndex(t => t.targetIndex === i);
          [ord[i], ord[correctIdx]] = [ord[correctIdx], ord[i]];
          layout();
          window.SFX && window.SFX.pickup();
          checkWin();
          break;
        }
      }
    }

    tilesRef.current = { tiles, layout, tryMove, giveHint };
    layout();
    
    createjs.Ticker.framerate = 30;
    createjs.Ticker.addEventListener("tick", stage);
    
    return () => {
      createjs.Ticker.removeEventListener("tick", stage);
      stage.removeAllChildren();
    };
  }, [N, theme, solved]); // added solved so state syncs if needed

  return (
    <div className="ma-puzzle">
      <div className="ma-instruction">Ordena los números del 1 al {N*N - 1} deslizando las piezas.</div>
      <canvas ref={canvasRef} width={SIZE} height={SIZE} className="ma-puzzle-canvas"/>
      <div className="ma-puzzle-hint">Toca una pieza junto al hueco para moverla.</div>
      {!solved && (
        <button className="ma-btn ma-btn-primary" style={{marginTop: 15}} onClick={() => {
          if (tilesRef.current && tilesRef.current.giveHint) tilesRef.current.giveHint();
        }}>💡 Pista (Acomodar una pieza)</button>
      )}
    </div>
  );
}

Object.assign(window, { ActWordSearch, ActCrossword, ActPuzzle });
