/*
 * letter-or-not/game.jsx — "מה הכתב" (which script). #92.
 *
 * LETTER HOMES (ADR 0024): a lost glyph appears, big and centred, and
 * three little HOUSES sit at the bottom — one per script, each with a
 * roof, two googly eyes, and its representative letter on the door:
 *   עברית (א, blue) · אנגלית (e, red) · ערבית (ب, green)
 * The kid sends the stranger to the house whose family it looks like.
 * On a correct tap the letter DROPS INTO the house, the house bounces +
 * its eyes squint happy + the language-coloured sparks fly. Hebrew is the
 * curriculum; English + Arabic are contrast scripts so the Hebrew shapes
 * feel concrete by comparison ("this one is OUR kind of letter").
 *
 * 12 rounds: 6 Hebrew + 3 English + 3 Arabic, shuffled. Mastery is
 * recorded on Hebrew rounds only (the others aren't the kid's letters).
 * EndScreen gallery shows the Hebrew rounds. Pointerdown commits.
 * (dir name stays letter-or-not to keep URLs/bookmarks; the binary
 *  "is-it-a-letter" v1.64 game evolved into this 3-way per #92.)
 */

const TWEAK_DEFAULTS = window.TWEAK_DEFAULTS;

// R27: the deck covers EVERY letter of all three alphabets — Hebrew 22 +
// Latin 26 + Arabic 28 = 76 rounds — in non-alphabetical, script-mixed
// order. Progress is shown PER ALPHABET, chunked into groups of six (the
// per-script tracker below), so the kid/parent sees how far each alphabet
// has gone. (Previously the deck sampled only 6+3+3 of each.)

// FULL alphabets (user req R3): every letter is usable, not a curated
// subset. The deck samples from these; over plays all letters appear.
// English = the 26 Latin capitals A–Z.
const LATIN_GLYPHS  = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
// Arabic = all 28 letters (isolated forms), alphabetical (alif→yaa).
const ARABIC_GLYPHS = "ابتثجحخدذرزسشصضطظعغفقكلمنهوي".split("");
// (Hebrew is the full 22-letter bank from GAME_CONFIG.rounds.)

// Each script's identity: its representative letter + its language colour.
//   Hebrew  → א  (first letter of the alephbet)   · blue
//   English → E  (capital; matches the uppercase Latin prompts)   · red
//   Arabic  → ب (baa — unmistakably Arabic: dish + dot; chosen over the
//                literal first letter alef ا, a plain stroke a pre-reader
//                could read as Latin "l/1", self-defeating here) · green
// Hebrew=blue / Arabic=green are the colours those cultures own; English
// takes red (the third primary). The big PROMPT glyph stays ink so the kid
// judges by SHAPE — only the buttons carry the identity hue.
const SCRIPTS = [
  { id: "he", exemplar: "א", hue: "#2563c9", cls: "lon-choice--he", label: "עברית" },
  { id: "en", exemplar: "E", hue: "#d23f3f", cls: "lon-choice--en", label: "אנגלית" },
  { id: "ar", exemplar: "ب", hue: "#2f9d6e", cls: "lon-choice--ar", label: "ערבית" },
];
const LANG_HUE = Object.fromEntries(SCRIPTS.map((s) => [s.id, s.hue]));

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 FEEDBACK_HOLD_MS = _ms("feedbackMs", 700);

// Pressability / guidance (baseline — docs/game-baseline.md "Invite"):
// a piano-ripple lift on the three houses every 15s while idle, and a
// single hint (glow the correct house) after 30s of no engagement.
const INVITE_INTERVAL_MS = _ms("invite", 15000);
const INVITE_LIFT_PX = 9, INVITE_STAGGER_MS = 130, 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.
// Commit flight (baseline — the glyph flies from the card to its home).
const FLIGHT_MS = _ms("flightMs", 1000);
const REDUCED_MOTION = () =>
  typeof window !== "undefined" && window.matchMedia &&
  window.matchMedia("(prefers-reduced-motion: reduce)").matches;

// Build the full deck: EVERY Hebrew (22, from the bank) + EVERY Latin (26)
// + EVERY Arabic (28) = 76 rounds. Each script is shuffled, then the three
// are concatenated and shuffled together → non-alphabetical, script-mixed.
// Each round = { glyph, script, item } where `item` is the matching
// GAME_CONFIG round for Hebrew (so the EndScreen gallery has the
// illustration), null for the contrast scripts.
function buildDeck(rounds) {
  const he = shuffle(rounds.slice())
    .map((r) => ({ glyph: r.letter, script: "he", item: r }));
  const en = shuffle(LATIN_GLYPHS.slice())
    .map((g) => ({ glyph: g, script: "en", item: null }));
  const ar = shuffle(ARABIC_GLYPHS.slice())
    .map((g) => ({ glyph: g, script: "ar", item: null }));
  return shuffle(he.concat(en, ar));
}

// Per-alphabet progress, chunked into groups of six (R27). One row per
// script (he/en/ar), each row = its exemplar tag + a dot per letter,
// visually grouped every sixth dot. Dots are `.progress-dot` inside the
// header's `.progress` so the shared-chrome contract (CI-2) still holds.
// `done` counts how many of that script's letters the kid has resolved so
// far; the current round's script marks its next dot "current".
function ScriptProgress({ deck, completed, currentScript }) {
  const GROUP = 6;
  const totals = { he: 0, en: 0, ar: 0 };
  deck.forEach((r) => { totals[r.script]++; });
  const done = { he: 0, en: 0, ar: 0 };
  deck.slice(0, completed).forEach((r) => { done[r.script]++; });
  return (
    <div className="lon-progress">
      {SCRIPTS.map((s) => (
        <div key={s.id} className="lon-prog-row" style={{ "--lang-hue": s.hue }}>
          <span className={`lon-prog-tag lon-glyph--${s.id}`} aria-hidden="true">{s.exemplar}</span>
          <span className="lon-prog-dots">
            {Array.from({ length: totals[s.id] }).map((_, i) => (
              <span
                key={i}
                className="progress-dot lon-pdot"
                data-state={i < done[s.id] ? "done" : (s.id === currentScript && i === done[s.id]) ? "current" : "todo"}
                data-group-end={i % GROUP === GROUP - 1 ? "1" : "0"}
              />
            ))}
          </span>
        </div>
      ))}
    </div>
  );
}

// The lost letter, big and centred. On a correct pick it FLIES to its home
// (`glyphRef` is the flight origin); the glyph hides once it has lifted off.
function GlyphCard({ glyph, script, status, glyphRef }) {
  const gone = status === "flying" || status === "correct";
  return (
    <div className="stage stage--lon">
      <div className="stage-card lon-card" data-status={status}
           style={{ "--lang-hue": LANG_HUE[script] }} aria-hidden="true">
        <span ref={glyphRef} className={`lon-glyph lon-glyph--${script}`}
              style={{ opacity: gone ? 0 : 1 }}>{glyph}</span>
      </div>
    </div>
  );
}

// Three houses — one per script. The matching house, on a correct tap,
// shows the stranger DROPPING IN (lon-arrived) and fires the celebration.
function HouseRow({ status, wrongScript, correctScript, glyph, glyphScript, hintMap, houseRefs, onPick }) {
  const disabled = status !== "idle";
  return (
    <div className="lon-choices lon-houses" role="group" aria-label="לאיזה בית שייכת האות?">
      {SCRIPTS.map((s, idx) => {
        const arrived = status === "correct" && s.id === correctScript;
        return (
          <button
            key={s.id}
            ref={(el) => { if (houseRefs) houseRefs.current[idx] = el; }}
            type="button"
            className={`lon-choice lon-house ${s.cls}`}
            style={{ "--lang-hue": s.hue }}
            data-wrong={wrongScript === s.id ? "1" : "0"}
            data-arrived={arrived ? "1" : "0"}
            data-hint={hintMap[s.id] || undefined}
            aria-label={s.label}
            disabled={disabled}
            onPointerDown={(e) => { e.preventDefault(); if (!disabled) onPick(s.id); }}
          >
            <span className="lon-roof" aria-hidden="true">
              <span className="lon-eye" /><span className="lon-eye" />
            </span>
            <span className="lon-door">
              <span className={`lon-exemplar lon-glyph--${s.id}`} aria-hidden="true">{s.exemplar}</span>
              {arrived && (
                <span className={`lon-arrived lon-glyph--${glyphScript}`} aria-hidden="true">{glyph}</span>
              )}
              {/* canonical correct-burst, language-hued (docs/animation-language.md) */}
              <CorrectBurst on={arrived} hue={LANG_HUE[glyphScript]} />
            </span>
            {/* canonical wrong-mark (white X); the red wash + shake are below */}
            <WrongMark on={wrongScript === s.id} />
          </button>
        );
      })}
    </div>
  );
}

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

  const { rounds } = window.GAME_CONFIG;
  const deck = React.useMemo(() => buildDeck(rounds), [rounds]);
  const TOTAL = deck.length; // all letters: 22 he + 26 en + 28 ar = 76

  const saved = (typeof window !== "undefined" && window.PROGRESS) ? window.PROGRESS.load("letter-or-not") : null;
  const initialRound = saved && saved.roundIdx < TOTAL ? saved.roundIdx : 0;

  const [roundIdx, setRoundIdx] = React.useState(initialRound);
  const [status, setStatus]     = React.useState("idle"); // idle | correct | wrong
  const [wrongScript, setWrongScript] = React.useState(null); // which button shook
  const [done, setDone]         = React.useState(false);
  const [firstTries, setFirstTries] = React.useState(saved ? saved.firstTries : []);
  const [engaged, setEngaged]   = React.useState(false); // kid touched this round
  const [inviteCycle, setInviteCycle] = React.useState(0);
  const [flying, setFlying]     = React.useState(null); // {glyph,script,fromRect,toRect}
  const roundCleanRef = React.useRef(true);
  const houseRefs = React.useRef([]);
  const glyphRef  = React.useRef(null);

  const round = deck[roundIdx];

  // Accelerated, repeating hint cycle (docs/game-baseline.md). 3 houses: fade
  // the two wrong (level 1/2 @5s/@10s), glitter the correct (level 3 @15s).
  const hintLevel = useHintCycle(!done && !engaged && status === "idle", HINT_STEPS, HINT_CYCLE_MS);
  // Unified idle cue (ADR 0028) via the shared helper. 3 houses →
  // fewOptions: t+5 first wrong house, t+10 both wrong, t+15 glitter correct.
  const hintMap = React.useMemo(() => {
    const wrong = SCRIPTS.map((s) => s.id).filter((id) => id !== round.script);
    const m = {};
    SCRIPTS.forEach((s) => {
      const cue = hintCueFor(hintLevel, {
        correct: s.id === round.script,
        firstWrong: s.id === wrong[0],
        fewOptions: true,
      });
      if (cue) m[s.id] = cue;
    });
    return m;
  }, [hintLevel, round.script]);
  React.useEffect(() => { if (hintLevel > 0) roundCleanRef.current = false; }, [hintLevel]);

  const advance = React.useCallback(() => {
    setWrongScript(null);
    setEngaged(false);
    setFlying(null);
    if (roundIdx + 1 >= TOTAL) { setDone(true); return; }
    setRoundIdx(roundIdx + 1);
    setStatus("idle");
    roundCleanRef.current = true;
  }, [roundIdx]);

  const restart = () => {
    setDone(false);
    setRoundIdx(0);
    setStatus("idle");
    setWrongScript(null);
    setEngaged(false);
    setFlying(null);
    setFirstTries([]);
    roundCleanRef.current = true;
  };

  // ── Invite (baseline piano-ripple): cue at round mount + every 15s while
  //    idle; suppressed once the kid engages or the hint fires. ──────────
  React.useEffect(() => {
    if (done || 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, engaged, hintLevel]);

  React.useEffect(() => {
    if (inviteCycle === 0) return;
    // CSS @media can't reach Element.animate() — opt out of motion here.
    if (window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
    for (let i = 0; i < houseRefs.current.length; i++) {
      const el = houseRefs.current[i];
      if (!el) continue;
      el.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(() => { if (done && window.SOUNDS) window.SOUNDS.play("end"); }, [done]);

  React.useEffect(() => {
    if (!window.PROGRESS) return;
    if (done) { window.PROGRESS.clear("letter-or-not"); return; }
    window.PROGRESS.save("letter-or-not", { roundIdx, firstTries, lastBreak: 0 });
  }, [roundIdx, firstTries, done]);

  const onPick = (scriptId) => {
    if (status !== "idle") return;
    setEngaged(true);          // stops the invite ripple + hint cycle this round
    const correct = scriptId === round.script;
    if (correct) {
      if (window.SOUNDS) window.SOUNDS.play("correct");
      const wasClean = roundCleanRef.current;
      // Only Hebrew rounds count toward MASTERY — the contrast scripts
      // (English / Arabic) aren't letters the kid is learning.
      if (round.script === "he" && window.MASTERY) window.MASTERY.recordRound(round.glyph, wasClean);
      setFirstTries((prev) => [...prev, wasClean]);
      // Baseline COMMIT: the glyph flies from the card to its home's door,
      // then the house celebrates (status → "correct"). Skip the flight
      // under reduced motion.
      const idx = SCRIPTS.findIndex((s) => s.id === round.script);
      const cardEl = glyphRef.current;
      const doorEl = houseRefs.current[idx] && houseRefs.current[idx].querySelector(".lon-door");
      if (cardEl && doorEl && !REDUCED_MOTION()) {
        setFlying({
          glyph: round.glyph, script: round.script,
          fromRect: cardEl.getBoundingClientRect(),
          toRect: doorEl.getBoundingClientRect(),
        });
        setStatus("flying");
        window.setTimeout(() => {
          setFlying(null);
          setStatus("correct");
          window.setTimeout(advance, FEEDBACK_HOLD_MS);
        }, FLIGHT_MS);
      } else {
        setStatus("correct");
        window.setTimeout(advance, FEEDBACK_HOLD_MS);
      }
    } else {
      if (window.SOUNDS) window.SOUNDS.play("wrong");
      setStatus("wrong");
      setWrongScript(scriptId);           // shake + red the tapped button
      roundCleanRef.current = false;
      window.setTimeout(() => { setStatus("idle"); setWrongScript(null); }, FEEDBACK_HOLD_MS);
    }
  };

  // Summary (sanctioned divergence — docs/game-baseline.md): a Hebrew-only
  // illustration gallery makes no sense for a script-sorting game. Instead
  // show a PER-SCRIPT gallery: the glyphs the kid sorted into each home,
  // grouped by language and tinted to its colour.
  const playedByScript = React.useMemo(() => {
    const by = { he: [], en: [], ar: [] };
    deck.slice(0, firstTries.length).forEach((r) => by[r.script].push(r.glyph));
    return by;
  }, [deck, firstTries.length]);

  return (
    <div className="app app--lon">
      {/* progress: 1 dot = 1 letter, shown PER ALPHABET in groups of six
          (he 22 / en 26 / ar 28 = 76 total). The custom tracker lives in
          the Header's `.progress` slot. */}
      <Header index={roundIdx} total={TOTAL}
              progressSlot={<ScriptProgress deck={deck} completed={roundIdx} currentScript={round.script} />} />
      <GlyphCard glyph={round.glyph} script={round.script} status={status} glyphRef={glyphRef} />
      <HouseRow
        status={status}
        wrongScript={wrongScript}
        correctScript={round.script}
        glyph={round.glyph}
        glyphScript={round.script}
        hintMap={hintMap}
        houseRefs={houseRefs}
        onPick={onPick}
      />

      {flying && (
        <FlyingLetter
          letter={flying.glyph}
          fromRect={flying.fromRect}
          toRect={flying.toRect}
          hue={LANG_HUE[flying.script]}
          fontClass={`lon-glyph--${flying.script}`}
        />
      )}

      {/* baseline success marker for single-answer games (game-contract CI-15):
          the big green ✓ fires as the letter lands home (status → correct). */}
      <CorrectOverlay on={status === "correct"} />

      {done && (
        <EndScreen onRestart={restart}>
          <div className="lon-summary">
            {SCRIPTS.map((s) => (
              <div key={s.id} className="lon-summary-col" style={{ "--lang-hue": s.hue }}>
                <span className={`lon-summary-tag lon-glyph--${s.id}`} aria-hidden="true">{s.exemplar}</span>
                <div className="lon-summary-glyphs">
                  {playedByScript[s.id].map((g, i) => (
                    <span key={i} className={`lon-summary-glyph lon-glyph--${s.id}`} aria-hidden="true">{g}</span>
                  ))}
                </div>
              </div>
            ))}
          </div>
        </EndScreen>
      )}

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

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