/*
 * config/config.jsx — "בחירת תמונות" — the photo-set configuration tile.
 *
 * Roadmap Phase 0 (see docs/roadmap-child-photo-authoring.md). The child
 * (or parent) builds a custom photo set: exactly ONE illustration per
 * Hebrew letter, chosen from the gallery of everything in the collection.
 *
 * Rules:
 *   - Gallery is grouped by THEME, and within a theme sorted by the
 *     Hebrew alphabet (GAME_CONFIG.letterPool order).
 *   - Tap a photo to choose it for its letter.
 *   - Once a letter has a chosen photo, the OTHER photos of that same
 *     letter gray out (the letter is settled). Tap the chosen one again
 *     to free the letter and re-pick.
 *   - The kid can CONTINUE only once all 22 letters are covered — or tap
 *     "השלם אוטומטית" to let the program fill the remaining letters.
 *
 * Persistence: localStorage only for now (Phase 2 makes it survive across
 * devices). This file is fully self-contained: it READS shared/collection.js,
 * shared/game-config.js and shared/sounds.js but writes nothing outside
 * config/, so it cannot affect the rest of the suite or its tests.
 */

const SELECTION_KEY = "aleph-bet.photo-selection.v1";

// collection.js stores paths relative to its sibling images/ dir; the live
// asset lives under assets/. Mirror theme-loader.js's resolution.
function resolveFile(f) {
  return f && f.startsWith("images/") ? "assets/" + f : f;
}

const LETTERS = window.GAME_CONFIG.letterPool;            // 22, in order
const LETTER_NAMES = window.GAME_CONFIG.letterNames;
const LETTER_INDEX = LETTERS.reduce((m, l, i) => { m[l] = i; return m; }, {});

// Flatten the collection into display photos, grouped by theme. Within a
// theme, sort by the canonical alphabet order.
function buildThemes() {
  return (window.COLLECTION.sets || []).map((set) => ({
    id: set.id,
    name: set.name_he || set.id,
    desc: set.desc_he || "",
    photos: (set.items || [])
      .filter((it) => LETTER_INDEX[it.letter] !== undefined)
      .map((it) => ({
        key: set.id + ":" + it.file + ":" + it.letter,
        letter: it.letter,
        word: it.item,
        theme: set.id,
        file: resolveFile(it.file),
      }))
      .sort((a, b) => LETTER_INDEX[a.letter] - LETTER_INDEX[b.letter]),
  }));
}

function readSelection() {
  try {
    const raw = window.localStorage.getItem(SELECTION_KEY);
    const parsed = raw ? JSON.parse(raw) : {};
    // Guard against valid-but-wrong JSON (null / number / array) so callers
    // can safely index by letter.
    return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
  } catch (_) { return {}; }
}
function writeSelection(sel) {
  try { window.localStorage.setItem(SELECTION_KEY, JSON.stringify(sel)); } catch (_) {}
}

function sound(name) { if (window.SOUNDS) window.SOUNDS.play(name); }

// ── A single gallery photo ───────────────────────────────────────────────
// Tap to choose. Selection rides on the browser's native `click`, which is
// the one reliable "this was a tap, not a scroll" signal inside a scroll
// container — it fires for a stationary touch tap AND keyboard Enter/Space,
// and never blocks scrolling.
//
// History (see ADR 0017): an earlier long-press-to-enlarge feature wired up
// pointerdown/move/up + a hold timer and tried to hand-roll tap-vs-scroll
// detection. On iOS Safari that detection lost: to reach a tile below the
// first screen you must scroll, and Safari then resolves the trailing touch
// as a scroll — firing `pointercancel` (and suppressing `click`) instead of
// `pointerup`. So below-fold tiles only ever fired the long-press timer
// (started on pointerdown, before the cancel), never the tap. Removing the
// long-press machinery hands tap detection back to the platform, where it
// works. No pointer handlers, no preventDefault.
function PhotoTile({ photo, chosen, grayed, onTap }) {
  return (
    <button
      type="button"
      className="cfg-tile"
      data-chosen={chosen ? "1" : "0"}
      data-grayed={grayed ? "1" : "0"}
      aria-pressed={chosen}
      aria-label={`${photo.word} — אות ${LETTER_NAMES[photo.letter] || photo.letter}`}
      onClick={() => onTap(photo)}
    >
      <span className="cfg-tile__art">
        <img src={photo.file} alt="" aria-hidden="true" />
      </span>
      <span className="cfg-tile__letter" aria-hidden="true">{photo.letter}</span>
      {chosen && (
        <span className="cfg-tile__check" aria-hidden="true">
          <svg viewBox="0 0 24 24"><path d="M5 13l4 4L19 7" fill="none" stroke="#fff" strokeWidth="3.2" strokeLinecap="round" strokeLinejoin="round" /></svg>
        </span>
      )}
    </button>
  );
}

// ── Coverage strip: 22 letters, lit as they get a photo ───────────────────
function Coverage({ selection }) {
  const done = LETTERS.filter((l) => selection[l]).length;
  return (
    <div className="cfg-coverage" aria-label={`נבחרו ${done} מתוך ${LETTERS.length}`}>
      <div className="cfg-coverage__count">{done} / {LETTERS.length}</div>
      <div className="cfg-coverage__dots" aria-hidden="true">
        {LETTERS.map((l) => (
          <span key={l} className="cfg-dot" data-on={selection[l] ? "1" : "0"}>{l}</span>
        ))}
      </div>
    </div>
  );
}

function App() {
  const [themes] = React.useState(buildThemes);
  const [selection, setSelection] = React.useState(readSelection);
  const [done, setDone] = React.useState(false);

  React.useEffect(() => { writeSelection(selection); }, [selection]);

  const doneCount = LETTERS.filter((l) => selection[l]).length;
  const allCovered = doneCount === LETTERS.length;

  const choose = (photo) => {
    setSelection((prev) => {
      const cur = prev[photo.letter];
      const next = { ...prev };
      if (cur && cur.theme === photo.theme && cur.file === photo.file) {
        delete next[photo.letter];           // tap the chosen one = free the letter
        sound("tap");
      } else {
        next[photo.letter] = { theme: photo.theme, file: photo.file, word: photo.word };
        sound("correct");
      }
      return next;
    });
  };

  const onTap = (photo) => {
    const cur = selection[photo.letter];
    const isChosen = cur && cur.theme === photo.theme && cur.file === photo.file;
    // A settled letter is locked to its winner: only the winner is tappable
    // (to free the letter). Alternatives are grayed + inert.
    if (cur && !isChosen) return;
    choose(photo);
  };

  // Fill every empty letter with a RANDOM photo, preferring the "team" sets
  // (alex / animals / david / giborim) over the general `default` bank — so a
  // fresh auto-fill starts from the themed collections, not the plain default,
  // and looks different each time. Fall back to `default` only for letters no
  // team covers (ו, ר, and any the teams skip). See issue #115.
  const autoFill = () => {
    setSelection((prev) => {
      const next = { ...prev };
      for (const l of LETTERS) {
        if (next[l]) continue;
        const all = [];
        for (const t of themes) {
          for (const p of t.photos) if (p.letter === l) all.push(p);
        }
        if (!all.length) continue;
        const team = all.filter((p) => p.theme !== "default");
        const pool = team.length ? team : all;
        const hit = pool[Math.floor(Math.random() * pool.length)];
        next[l] = { theme: hit.theme, file: hit.file, word: hit.word };
      }
      return next;
    });
    sound("break");
  };

  // Apply a whole category at once: for every letter the category covers,
  // OVERRIDE the current choice with that category's photo. Letters the
  // category doesn't cover keep their existing selection. The category bar is
  // always on screen so a category can be (re)chosen at any time. (issue #115)
  const applyCategory = (themeId) => {
    const t = themes.find((x) => x.id === themeId);
    if (!t) return;
    setSelection((prev) => {
      const next = { ...prev };
      for (const l of LETTERS) {
        const hit = t.photos.find((p) => p.letter === l);
        if (hit) next[l] = { theme: hit.theme, file: hit.file, word: hit.word };
      }
      return next;
    });
    sound("correct");
  };

  const reset = () => { setSelection({}); sound("tap"); };

  const onContinue = () => { setDone(true); sound("end"); };

  if (done) {
    return (
      <div className="app app--config cfg-done">
        <div className="cfg-done__check" aria-hidden="true">
          <svg viewBox="0 0 24 24"><path d="M5 13l4 4L19 7" fill="none" stroke="#fff" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" /></svg>
        </div>
        <h1 className="cfg-title">התמונות שלך מוכנות!</h1>
        <div className="cfg-done__actions">
          {/* play with my set — big primary play triangle */}
          <a className="cfg-iconbtn cfg-iconbtn--primary cfg-iconbtn--lg" href="games/initial/?theme=mine" aria-label="שחק עם התמונות שלי">
            <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5v14l11-7z" fill="currentColor" /></svg>
          </a>
          {/* edit — pencil */}
          <button type="button" className="cfg-iconbtn" onClick={() => setDone(false)} aria-label="עריכה">
            <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20h4L18 10l-4-4L4 16z" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinejoin="round" /></svg>
          </button>
          {/* home */}
          <a className="cfg-iconbtn" href="index.html" aria-label="חזרה לעמוד הבית">
            <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 11l8-7 8 7M6 9.5V20h12V9.5" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" /></svg>
          </a>
        </div>
      </div>
    );
  }

  return (
    <div className="app app--config">
      <div className="header header--bare">
        {/* The אותיות brand mark — the same single artifact the games' Crown
            uses; this tool doesn't load game-shared.jsx so it points at it directly. */}
        <a className="crown" href="index.html" aria-label="חזרה לעמוד הבית"><img className="crown-mark" src="assets/brand/mark.svg" alt="" aria-hidden="true" /></a>
        <div className="cfg-head-actions">
          {/* auto-fill — magic wand */}
          <button type="button" className="cfg-iconbtn" onClick={autoFill} aria-label="השלם אוטומטית">
            <svg viewBox="0 0 24 24" aria-hidden="true">
              <path d="M4 20L15 9" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" />
              <path d="M17 3l.8 2.2L20 6l-2.2.8L17 9l-.8-2.2L14 6l2.2-.8zM7.5 3l.5 1.4L9.4 5l-1.4.5L7.5 7 7 5.5 5.6 5 7 4.4z" fill="currentColor" />
            </svg>
          </button>
          {/* reset — circular arrow */}
          <button type="button" className="cfg-iconbtn" onClick={reset} aria-label="איפוס">
            <svg viewBox="0 0 24 24" aria-hidden="true">
              <path d="M19 12a7 7 0 1 1-2.05-4.95" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" />
              <path d="M18 3v4.2h-4.2" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" />
            </svg>
          </button>
        </div>
      </div>

      <div className="cfg-stickytop">
        <Coverage selection={selection} />
        {/* Category cards — always visible. Tap one to apply that whole
            category, overriding existing choices for the letters it covers. */}
        <div className="cfg-cats" role="group" aria-label="בחירת קטגוריה">
          {themes.map((t) => (
            <button
              key={t.id}
              type="button"
              className="cfg-cat"
              onClick={() => applyCategory(t.id)}
            >
              {t.name}
            </button>
          ))}
        </div>
      </div>

      <div className="cfg-themes">
        {themes.map((t) => (
          <section key={t.id} className="cfg-theme">
            <h2 className="cfg-theme__name">{t.name}</h2>
            <div className="cfg-grid">
              {t.photos.map((p) => {
                const cur = selection[p.letter];
                const chosen = !!cur && cur.theme === p.theme && cur.file === p.file;
                const grayed = !!cur && !chosen;
                return (
                  <PhotoTile
                    key={p.key}
                    photo={p}
                    chosen={chosen}
                    grayed={grayed}
                    onTap={onTap}
                  />
                );
              })}
            </div>
          </section>
        ))}
      </div>

      <div className="cfg-footer">
        <button
          type="button"
          className="cfg-btn cfg-btn--primary cfg-continue"
          disabled={!allCovered}
          onClick={() => { if (allCovered) onContinue(); }}
          aria-label="המשך"
        >
          {/* RTL "next" = left-pointing chevron, graphic-only (see CLAUDE.md
              continuation-button convention). The coverage strip already
              shows how many letters remain — no text needed here. */}
          <svg viewBox="0 0 24 24" aria-hidden="true">
            <path d="M15 5l-7 7 7 7" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </button>
      </div>
    </div>
  );
}

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