/*
 * game.jsx — Game 4 ("shadow-letter"): see a Hebrew letter as a big black
 * silhouette, pick the matching letter from a row of 3.
 *
 * Pure shape recognition — no words, no photos, just the letterform. The
 * kid maps the silhouette overhead to one of three letter buttons below.
 * Same 22-letter pool as the rest of the suite; reuses the Buttons row +
 * pointerdown commit + invitation + hint vocabulary from game 1.
 *
 * Why a silhouette?
 *   - The "shadow" reading is concrete and visual — a 4-5yo gets it
 *     without a single readable word on the screen.
 *   - The same glyph the kid sees on the buttons is now in the stage in
 *     a slightly different form (darker, bigger, no card chrome) — the
 *     bridge "same letter, different shading" IS the entire game.
 *   - No new SVG asset work; the silhouette is the Hebrew character
 *     rendered with color: var(--ink), which renders identically on
 *     every device that already loads our font stack.
 *
 * Per-round structure: { letter, correctPosition }. correctPosition is
 * randomized per round so the kid is forced to look at the silhouette
 * rather than learning a position habit. The two distractor letters are
 * sampled from the 22-letter pool.
 */

const TWEAK_DEFAULTS = window.TWEAK_DEFAULTS;

// Real-press timings.
const WRONG_REVEAL_MS = 900;
const CORRECT_HOLD_MS = 900;

// Invitation + hint timings — same URL-override hooks as the other games
// so tests can collapse the 30s chain into sub-second probes.
const _qs = new URLSearchParams(window.location.search);
const _ms = (key, def) => {
  const v = _qs.get(key);
  return v === null || v === "" ? def : Math.max(0, +v) || def;
};
const INVITE_INTERVAL_MS   = _ms("invite", 15000);
const INVITE_STAGGER_MS    = 130;
const INVITE_LIFT_PX       = 9;
const INVITE_LIFT_MS       = 360;
// Idle-cue timing + mapping are shared from game-shared.jsx (HINT_STEPS,
// HINT_CYCLE_MS, hintCueFor) — the one pattern, ADR 0028.

const SUCCESS_SPARK_ANGLES = [0, 45, 90, 135, 180, 225, 270, 315];

// Build the 3-letter row + a randomized correctPosition. The original
// rounds carry their pinned correctPosition (a cyclic pattern from
// game-config.js); here we override it to a fresh random slot per round
// so the kid can't learn "always middle" by position alone.
function buildRow(letter, pool) {
  const correctPosition = Math.floor(Math.random() * 3);
  const row = new Array(3).fill(null);
  row[correctPosition] = letter;
  const distractors = pickDistractors(pool, letter, 2);
  let di = 0;
  for (let i = 0; i < row.length; i++) if (row[i] === null) row[i] = distractors[di++];
  return { row, correctPosition };
}

function wrongIndicesFor(correctPosition) {
  return [0, 1, 2].filter((i) => i !== correctPosition);
}

// ── Shadow card — the big silhouette of the target letter ────────────────
// Rendered as a single character in --letter-font, sized to fill the card.
// `data-state` triggers the success pop + halo + 8 sparks when the kid taps
// the right answer (mirrors the slot-target dopamine burst from game 1).
const ShadowCard = React.forwardRef(function ShadowCard({ letter, status, shake }, ref) {
  return (
    <div className="stage">
      <div
        ref={ref}
        className="stage-card shadow-card"
        data-state={status === "correct" ? "correct" : "idle"}
        data-shake={shake ? "1" : "0"}
        aria-hidden="true"
      >
        <span className="shadow-glyph">{letter}</span>
        {status === "correct" && (
          <span className="success-burst" aria-hidden="true">
            {SUCCESS_SPARK_ANGLES.map((a) => (
              <span key={a} className="success-spark" style={{ "--angle": `${a}deg` }} />
            ))}
          </span>
        )}
      </div>
    </div>
  );
});

// ── Letter row ───────────────────────────────────────────────────────────
function Buttons({ row, letterNames, status, wrongIdx, hintFor, onPick, buttonRefs }) {
  return (
    <div className="buttons" role="group" aria-label="בחר את האות">
      {row.map((letter, i) => {
        const isWrong  = status === "wrong" && wrongIdx === i;
        const disabled = status === "correct";
        const hint = status === "idle" ? hintFor(i) : null;
        return (
          <button
            key={i + ":" + letter}
            ref={(el) => { buttonRefs.current[i] = el; }}
            type="button"
            className="btn"
            data-wrong={isWrong ? "1" : "0"}
            data-disabled={disabled ? "1" : "0"}
            data-hint={hint || "none"}
            aria-label={letterNames[letter] || letter}
            onPointerDown={(e) => { e.preventDefault(); onPick(i, letter); }}
          >
            <span className="glyph">{letter}</span>
            <span className="x-mark" aria-hidden="true">
              <svg viewBox="0 0 60 60" width="56" height="56">
                <path d="M16 16 L44 44 M44 16 L16 44" stroke="#fff" strokeWidth="7" strokeLinecap="round" fill="none" />
              </svg>
            </span>
            <span className="hint-cross" aria-hidden="true">
              <svg viewBox="0 0 60 60" preserveAspectRatio="none">
                <path d="M10 10 L50 50" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />
                <path d="M50 10 L10 50" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" />
              </svg>
            </span>
            <span className="hint-sparkles" aria-hidden="true">
              <span className="hint-sparkle" style={{ left: "10%", top: "14%" }} />
              <span className="hint-sparkle" style={{ left: "82%", top: "18%" }} />
              <span className="hint-sparkle" style={{ left: "18%", top: "80%" }} />
              <span className="hint-sparkle" style={{ left: "78%", top: "82%" }} />
            </span>
          </button>
        );
      })}
    </div>
  );
}

// ── App ──────────────────────────────────────────────────────────────────
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  useApplyTweaks(t);

  const { rounds, letterPool, letterNames } = window.GAME_CONFIG;

  // Resume from where the kid stopped last time. Scoped (theme, n) by the
  // shared PROGRESS module — "shadow" gets its own slot independent from
  // letter / photo / pairs so progress in one game doesn't push them
  // forward in another.
  const saved = (typeof window !== "undefined" && window.PROGRESS) ? window.PROGRESS.load("shadow") : null;
  const initialRound = saved ? saved.roundIdx : 0;
  const initialFirstTries = saved ? saved.firstTries : [];
  const initialLastBreak = saved ? Math.max(saved.lastBreak, Math.floor(saved.roundIdx / 5)) : 0;

  const [roundIdx, setRoundIdx] = React.useState(initialRound);
  const initialBuild            = React.useMemo(() => buildRow(rounds[initialRound].letter, letterPool), []);
  const [row, setRow]                       = React.useState(initialBuild.row);
  const [correctPosition, setCorrectPosition] = React.useState(initialBuild.correctPosition);
  const [status, setStatus]     = React.useState("idle");
  const [wrongIdx, setWrongIdx] = React.useState(-1);
  const [done, setDone]         = React.useState(false);
  const [lastBreak, setLastBreak] = React.useState(initialLastBreak);

  const [engaged, setEngaged]         = React.useState(false);
  const [inviteCycle, setInviteCycle] = React.useState(0);

  const [firstTries, setFirstTries] = React.useState(initialFirstTries);
  const roundCleanRef = React.useRef(true);

  const buttonRefs = React.useRef({});

  const breakAt = (roundIdx > 0 && roundIdx % 5 === 0) ? Math.floor(roundIdx / 5) : 0;
  const showBreak = breakAt > 0 && lastBreak < breakAt && !done && status === "idle";
  const batchRounds = showBreak ? rounds.slice(roundIdx - 5, roundIdx) : [];

  // Accelerated, repeating hint cycle (docs/game-baseline.md): fade wrong1/2
  // (@5s/@10s) then glitter the correct (@15s), repeating every 20s.
  const hintLevel = useHintCycle(
    !done && !showBreak && !engaged && status === "idle",
    HINT_STEPS, HINT_CYCLE_MS
  );

  const round = rounds[roundIdx];

  const advance = React.useCallback(() => {
    if (roundIdx + 1 >= rounds.length) { setDone(true); return; }
    const next = roundIdx + 1;
    const built = buildRow(rounds[next].letter, letterPool);
    setRoundIdx(next);
    setRow(built.row);
    setCorrectPosition(built.correctPosition);
    setStatus("idle");
    setWrongIdx(-1);
    setEngaged(false);
    setInviteCycle(0);
    roundCleanRef.current = true;
  }, [roundIdx, rounds, letterPool]);

  const restart = () => {
    const built = buildRow(rounds[0].letter, letterPool);
    setDone(false);
    setRoundIdx(0);
    setRow(built.row);
    setCorrectPosition(built.correctPosition);
    setStatus("idle");
    setWrongIdx(-1);
    setLastBreak(0);
    setEngaged(false);
    setInviteCycle(0);
    setFirstTries([]);
    roundCleanRef.current = true;
  };

  // ── INVITATION ripple on the buttons — same vocabulary as game 1. Plays
  //    on mount + every 15s while idle; suppressed once the kid engages or
  //    a hint takes over.
  React.useEffect(() => {
    if (done || showBreak || engaged || hintLevel > 0) return;
    const t0 = window.setTimeout(() => setInviteCycle((c) => c + 1), 300);
    const tid = window.setInterval(() => setInviteCycle((c) => c + 1), INVITE_INTERVAL_MS);
    return () => { clearTimeout(t0); clearInterval(tid); };
  }, [roundIdx, done, showBreak, engaged, hintLevel]);

  React.useEffect(() => {
    if (inviteCycle === 0) return;
    if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    for (let i = 0; i < 3; i++) {
      const btn = buttonRefs.current[i];
      if (!btn) continue;
      btn.animate(
        [
          { transform: "translateY(0)" },
          { transform: `translateY(-${INVITE_LIFT_PX}px)` },
          { transform: "translateY(0)" },
        ],
        {
          duration: INVITE_LIFT_MS,
          delay: i * INVITE_STAGGER_MS,
          easing: "cubic-bezier(.2,.9,.3,1.1)",
        }
      );
    }
  }, [inviteCycle]);

  // HINTS now come from the shared useHintCycle (above) — accelerated +
  // repeating per docs/game-baseline.md.

  React.useEffect(() => {
    document.documentElement.setAttribute("data-hint-level", String(hintLevel));
    document.documentElement.setAttribute("data-engaged", String(engaged));
  }, [hintLevel, engaged]);

  // Unified idle cue (ADR 0028) via the shared helper. 3 buttons →
  // fewOptions: t+5 first-wrong, t+10 all-wrong, t+15 glitter correct.
  const hintFor = React.useCallback((i) => {
    const [w1] = wrongIndicesFor(correctPosition);
    return hintCueFor(hintLevel, {
      correct: i === correctPosition,
      firstWrong: i === w1,
      fewOptions: true,
    });
  }, [hintLevel, correctPosition]);

  React.useEffect(() => {
    if (hintLevel > 0) roundCleanRef.current = false;
  }, [hintLevel]);

  React.useEffect(() => { if (showBreak && window.SOUNDS) window.SOUNDS.play("break"); }, [showBreak]);
  React.useEffect(() => { if (done && window.SOUNDS) window.SOUNDS.play("end"); }, [done]);

  React.useEffect(() => {
    if (!window.PROGRESS) return;
    if (done) { window.PROGRESS.clear("shadow"); return; }
    window.PROGRESS.save("shadow", { roundIdx, firstTries, lastBreak });
  }, [roundIdx, firstTries, lastBreak, done]);

  const onPick = (i, letter) => {
    if (!engaged) setEngaged(true);
    if (engaged && status !== "idle") return;
    if (window.SOUNDS) window.SOUNDS.play("tap");

    if (letter === round.letter) {
      if (window.SOUNDS) window.SOUNDS.play("correct");
      const wasClean = roundCleanRef.current;
      setFirstTries((prev) => [...prev, wasClean]);
      if (window.MASTERY) window.MASTERY.recordRound(round.letter, wasClean);
      setStatus("correct");
      window.setTimeout(advance, CORRECT_HOLD_MS);
    } else {
      if (window.SOUNDS) window.SOUNDS.play("wrong");
      roundCleanRef.current = false;
      setStatus("wrong");
      setWrongIdx(i);
      window.setTimeout(() => { setStatus("idle"); setWrongIdx(-1); }, WRONG_REVEAL_MS);
    }
  };

  return (
    <div className="app">
      {/* progress: 1 dot = 1 letter (default 22; truncated by ?n=) */}
      <Header index={roundIdx} total={rounds.length} />
      <ShadowCard
        key={`shadow-${roundIdx}`}
        letter={round.letter}
        status={status}
        shake={status === "wrong"}
      />
      <Buttons
        key={`row-${roundIdx}`}
        row={row}
        letterNames={letterNames}
        status={status}
        wrongIdx={wrongIdx}
        hintFor={hintFor}
        buttonRefs={buttonRefs}
        onPick={onPick}
      />

      {/* #170: the big green ✓ — baseline success marker for single-answer
          games (game-contract CI-15). The slot-pop + halo + spark burst on
          the silhouette card still play under it. (shadow's commit is
          reveal-in-place — no separate destination slot to fly to.) */}
      <CorrectOverlay on={status === "correct"} />

      {showBreak && (
        <BreakScreen
          batchRounds={batchRounds}
          batchFirstTries={firstTries.slice(roundIdx - 5, roundIdx)}
          completed={roundIdx}
          total={rounds.length}
          onContinue={() => { setEngaged(false); setLastBreak(breakAt); }}
          onStop={() => {
            window.location.href = document.baseURI;
          }}
        />
      )}
      {done && (
        <EndScreen
          onRestart={restart}
          playedRounds={rounds.slice(0, firstTries.length)}
          playedFirstTries={firstTries}
        />
      )}

      <TweaksPanel title="Tweaks">
        <SharedTweakControls t={t} setTweak={setTweak}>
          <TweakSection label="כפתורים" />
          <TweakRadio
            label="צורה"
            value={t.buttonShape}
            options={[
              { value: "squircle", label: "Squircle" },
              { value: "circle",   label: "עיגול" },
              { value: "cushion",  label: "כרית" },
            ]}
            onChange={(v) => setTweak("buttonShape", v)}
          />

          <TweakSection label="פעולות" />
          <TweakButton label="שחק מההתחלה" onClick={restart} />
        </SharedTweakControls>
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
