// Main App — auth gate, screen routing, settings, lifetime stats wiring.
// Globals: React, ReactDOM, VOCAB, CATEGORY_META,
// useStats, useAuth, AuthScreen, pickSession, pickMistakesSession, shuffle,
// TopNav, HomeScreen, PracticeScreen, ResultsScreen, ReviewScreen, MistakesScreen,
// StatsScreen, CategoriesScreen, SettingsScreen, SETTINGS_KEY.

const { useState: aUseState, useEffect: aUseEffect, useCallback: aUseCallback } = React;

const DEFAULT_SETTINGS = {
  categories: ["t1","t2","t3","t4","t5","wlf_wf","wlf_gr","irr_wf","mc_ru","mc_wlf",
    "tr_wf","tr_gr","tr_mc","irr2_wf","vp_gr","wb_wf","wb_gr","wb_mc",
    "ut_wf","ut_gr","ut_mc","modA_gr",
    "sfx_wf","wm5_wf","ru_wf","g5_gr","ru_gr","wm5_mc",
    "li_wf","li_gr","li_mc",
    "rc_wf","rc_gr","neg_wf","sg_wf","sg_gr","sg_mc","cp_mc","lc_mc",
    "hsg_gr","hsg_wf","hsg_mc",
    "oge_gr","oge_wf"],
  soundOn: false,
  dark: false,
  defaultLength: "10",   // "10" | "20" | "endless"
  autoAdvance: false,
  reduceMotion: false,
  haptics: true,
  examMode: "ege",       // "ege" | "oge" — OGE = grammar + word formation only
};

// Fixed production design tokens (Tweaks panel is a Claude Design dev tool, not for prod).
const TWEAK_DEFAULTS = {
  accent: "#13a292",
  density: "comfortable",
  showStatRail: true,
};

function loadSettings() {
  try {
    const raw = localStorage.getItem(SETTINGS_KEY);
    if (!raw) return DEFAULT_SETTINGS;
    const parsed = JSON.parse(raw);
    const merged = { ...DEFAULT_SETTINGS, ...parsed };
    const validCats = new Set(Object.keys(CATEGORY_META));
    merged.categories = (merged.categories || []).filter(c => validCats.has(c));
    if (merged.categories.length === 0) merged.categories = DEFAULT_SETTINGS.categories;
    // Enable newly-added exam categories (e.g. OGE) for existing users.
    if (validCats.has("oge_gr") && !merged.categories.includes("oge_gr")) merged.categories = [...merged.categories, "oge_gr"];
    if (validCats.has("oge_wf") && !merged.categories.includes("oge_wf")) merged.categories = [...merged.categories, "oge_wf"];
    return merged;
  } catch (e) { return DEFAULT_SETTINGS; }
}
function saveSettings(s) {
  try { localStorage.setItem(SETTINGS_KEY, JSON.stringify(s)); } catch (e) {}
}

// Guests (no synced account) can't join classrooms — prompt them to sign in.
function ClassGate({ onCreateAccount }) {
  return (
    <div className="class-shell">
      <EmptyState icon="○" title="Sign in to use classrooms"
        cta={<button className="cta-primary" onClick={onCreateAccount}><span>Sign in</span><span className="cta-arrow">→</span></button>}>
        You can practise freely as a guest, but joining a classroom and getting homework needs an account (free) so your teacher can sync your stats. Already have one? Sign in.
      </EmptyState>
    </div>
  );
}

// Picks teacher vs student view by relationship (owner / member), with a
// switch when the signed-in user is both.
function ClassRoot({ classData, auth, pushToast, onStartHomework, onCreateAccount, onOpenStudents }) {
  const { store, actions, dataState, reload } = classData;
  const [roleOverride, setRoleOverride] = aUseState(null);
  // Only genuine guests get the sign-in gate. Signed-in users whose data is
  // still loading or failed to load get loading/error — NOT "create account".
  if (dataState === "guest") return <ClassGate onCreateAccount={onCreateAccount} />;
  if (dataState === "error") {
    return (
      <div className="class-shell">
        <ErrorState title="Couldn't load your class" onRetry={reload}>
          We couldn't reach the classroom service just now. Check your connection and try again.
        </ErrorState>
      </div>
    );
  }
  if (!store.account) return <div className="class-shell"><SkeletonCards n={2} /></div>;
  const account = store.account;
  const owns = Object.values(store.classrooms).some(c => c.ownerHandle === account.handle);
  const isStudentish = (store.studentMemberships[account.handle] || []).length > 0 || studentPendingInvites(store, account.handle).length > 0;
  const both = owns && isStudentish;
  const role = roleOverride || ((account.accountType === "teacher" || owns) ? "teacher" : "student");
  return (
    <>
      {both && (
        <div className="class-roleswitch">
          <button className="text-link" onClick={() => setRoleOverride(role === "teacher" ? "student" : "teacher")}>
            Viewing as {role} · switch to {role === "teacher" ? "student" : "teacher"}
          </button>
        </div>
      )}
      {role === "teacher"
        ? <TeacherClass store={store} actions={actions} account={account} pushToast={pushToast} dataState={dataState} onOpenStudents={onOpenStudents} />
        : <StudentClass store={store} actions={actions} account={account} pushToast={pushToast} dataState={dataState} onStartHomework={onStartHomework} />}
    </>
  );
}

function App() {
  const auth = useAuth();

  const [screen, setScreen] = aUseState("home");
  const [livePin, setLivePin] = aUseState(() => {
    const m = typeof location !== "undefined" ? /[?&]live=(\d{4,8})/.exec(location.search) : null;
    return m ? m[1] : null;
  });
  const [settings, setSettings] = aUseState(() => loadSettings());
  const [queue, setQueue] = aUseState([]);
  const [endless, setEndless] = aUseState(false);
  const [verbRun, setVerbRun] = aUseState(null);   // Irregular-Verbs drill config
  const [summary, setSummary] = aUseState(null);
  const examMode = settings.examMode === "oge" ? "oge" : "ege";
  const { stats, recordAnswer, recordSession, resetStats } = useStats(auth.userKey, auth.syncTick, examMode);
  // Mistakes-pool size for the side-nav badge (same rule as MistakesScreen).
  const mistakesCount = React.useMemo(() => {
    let n = 0; const pi = stats.perItem || {};
    // Only count mistakes that are still in the bank (same filter the Mistakes
    // list uses) so the badge can never show more than the page can display.
    const ids = new Set((window.VOCAB || []).map(v => v.id));
    for (const id in pi) { if (ids.has(id) && window.isMistakeStat && window.isMistakeStat(pi[id])) n++; }
    return n;
  }, [stats.perItem]);

  // Classrooms — loaded once the user is signed in (so the nav knows whether
  // they're a teacher and the teacher pages have data), or when Class is opened.
  const [hwCtx, setHwCtx] = aUseState(null);          // { assignmentId, handle } while doing homework
  const [classEnabled, setClassEnabled] = aUseState(false);
  const classData = useClassroomData(auth, classEnabled);
  const [toastNode, pushToast] = useToast();
  aUseEffect(() => {
    const signedIn = auth.currentUser && auth.currentUser !== "guest";
    if (screen === "class" || signedIn) setClassEnabled(true);
  }, [screen, auth.currentUser]);

  // Role + teacher-nav counts (account loads with the classroom store).
  const account = classData.store.account;
  const isTeacher = !!(account && account.accountType === "teacher");
  const taskCount = Object.keys(classData.store.tasks || {}).length;
  const studentCount = React.useMemo(() => {
    const s = classData.store; const set = new Set();
    for (const id in (s.classrooms || {})) {
      const c = s.classrooms[id];
      if (c && account && c.ownerHandle === account.handle && Array.isArray(c.studentHandles)) {
        for (const h of c.studentHandles) set.add(h);
      }
    }
    return set.size;
  }, [classData.store, account]);
  // Teacher-only screens: bounce a loaded non-teacher back home (the nav never
  // shows these to students, but guard the stale-screen case anyway).
  aUseEffect(() => {
    if ((screen === "mytasks" || screen === "students") && account && !isTeacher) setScreen("home");
  }, [screen, account, isTeacher]);

  aUseEffect(() => { saveSettings(settings); }, [settings]);

  // PWA: mark standalone (installed) so the bottom nav can sit lower than in-browser.
  aUseEffect(() => {
    const mq = window.matchMedia("(display-mode: standalone)");
    const apply = () => {
      const standalone = mq.matches || window.navigator.standalone === true;
      document.documentElement.dataset.standalone = standalone ? "true" : "false";
    };
    apply();
    if (mq.addEventListener) mq.addEventListener("change", apply);
    return () => { if (mq.removeEventListener) mq.removeEventListener("change", apply); };
  }, []);

  // Deep-link: ?live=PIN (e.g. a scanned QR) drops you straight into the live
  // Join with the PIN filled. Guests are welcome — auto-continue as guest so
  // scanning "just works" without a sign-in wall.
  aUseEffect(() => {
    if (!livePin) return;
    if (!auth.decided) { if (auth.continueAsGuest) auth.continueAsGuest(); return; }
    setScreen("live");
    try { if (window.history && window.history.replaceState) window.history.replaceState(null, "", location.pathname); } catch (e) {}
  }, [livePin, auth.decided]);

  // Keep the haptics helper in sync with the setting (default on).
  aUseEffect(() => { if (window.setHapticsEnabled) window.setHapticsEnabled(settings.haptics !== false); }, [settings.haptics]);

  // Apply theme tokens + motion mode at the document root.
  aUseEffect(() => {
    const root = document.documentElement;
    root.dataset.theme = settings.dark ? "dark" : "light";
    root.dataset.density = TWEAK_DEFAULTS.density;
    root.dataset.rail = TWEAK_DEFAULTS.showStatRail ? "on" : "off";
    root.dataset.motion = settings.reduceMotion ? "reduced" : "normal";
    root.style.setProperty("--accent", TWEAK_DEFAULTS.accent);
  }, [settings.dark, settings.reduceMotion]);

  const start = aUseCallback((opts = {}) => {
    const length = opts.length ?? 10;
    let q;
    if (opts.itemIds && opts.itemIds.length) {
      // Practice a specific set of items (e.g. the Mistakes collection).
      const byId = new Map(VOCAB.map(v => [v.id, v]));
      q = shuffle(opts.itemIds.map(id => byId.get(id)).filter(Boolean));
      if (length !== Infinity) q = q.slice(0, length);
    } else if (opts.mistakesOnly) {
      // Practice items the user got wrong on their last attempt.
      q = pickMistakesSession({ stats, length });
    } else {
      // Optional family filter (Continue-your-streak card uses this).
      let cats = settings.categories;
      if (opts.familyFilter) {
        cats = Object.entries(CATEGORY_META)
          .filter(([id, m]) => m.family === opts.familyFilter && settings.categories.includes(id))
          .map(([id]) => id);
        if (cats.length === 0) {
          // user disabled all categories in that family — fall back to the full family pool
          cats = Object.entries(CATEGORY_META)
            .filter(([_, m]) => m.family === opts.familyFilter)
            .map(([id]) => id);
        }
      }
      // Trainer "EGE/OGE tasks" mixed practice: restrict to the chosen families
      // (e.g. word-formation + grammar + multiple-choice; never irregular).
      if (opts.families && opts.families.length) {
        const famSet = new Set(opts.families);
        cats = Object.entries(CATEGORY_META)
          .filter(([id, m]) => famSet.has(m.family) && settings.categories.includes(id))
          .map(([id]) => id);
        if (cats.length === 0) cats = Object.entries(CATEGORY_META).filter(([_, m]) => famSet.has(m.family)).map(([id]) => id);
      }
      // Restrict to the active exam's categories; if none are enabled, fall back
      // to the WHOLE exam (never leak the other exam's content into the session).
      const examCats = window.examCategories(settings.examMode === "oge" ? "oge" : "ege");
      const allowed = new Set(examCats);
      cats = cats.filter(c => allowed.has(c));
      if (cats.length === 0) cats = examCats;
      q = pickSession({ categories: cats, length });
    }
    if (q.length === 0) return; // nothing to practice
    setQueue(q);
    setEndless(!!opts.endless);
    setSummary(null);
    setScreen("practice");
  }, [settings, stats]);

  // Start an assigned homework: materialise its items from the same pools and
  // run them through the normal practice loop; finish() submits the rollup.
  const startHomework = aUseCallback((a) => {
    const k = a.kind;
    if (k.type === "drill" && k.drill === "verbs") {
      const r = buildVerbQueue(k.types && k.types.length ? k.types : ["complete", "spelling", "brackets"], k.len || "20");
      if (!r.queue.length || !classData.store.account) return;
      setHwCtx({ assignmentId: a.id, handle: classData.store.account.handle });
      setVerbRun({ ...r, returnTo: "class" }); setSummary(null); setScreen("trainerrun");
      return;
    }
    let items = [];
    if (k.type === "set") {
      // Scope a family "set" to the class's exam so an OGE class pulls OGE
      // passages (not EGE sentences of the same family), and vice-versa.
      const examCats = k.exam ? new Set(window.examCategories(k.exam === "oge" ? "oge" : "ege")) : null;
      const pool = VOCAB.filter(v => v.type === k.family && (!examCats || examCats.has(v.category)));
      items = shuffle(pool).slice(0, k.n);
    }
    else if (k.type === "test") items = shuffle(VOCAB.filter(v => v.category === k.categoryId)).slice(0, 20);
    else if (k.type === "task") {
      const t = classData.store.tasks[k.taskId];
      if (t) items = [{ id: t.id, type: t.type, sentence: t.sentence, base: t.base, answer: t.answer, alts: t.alts || [], choices: t.choices, category: "custom" }];
    }
    if (!items.length || !classData.store.account) return;
    setHwCtx({ assignmentId: a.id, handle: classData.store.account.handle });
    setQueue(items); setEndless(false); setSummary(null); setScreen("practice");
  }, [classData]);

  const finish = aUseCallback((s) => {
    // homework submission (auto-graded client-side) → rollup to the server.
    // The submit is durable (retries + offline queue), so we surface whether it
    // landed and never silently drop a completed homework.
    if (hwCtx) {
      const missed = (s.wrong || []).map(w => w.item && w.item.id).filter(Boolean);
      const ctx = hwCtx;
      setHwCtx(null);
      Promise.resolve(classData.actions.submitHomework(ctx.assignmentId, ctx.handle, {
        correct: s.correct, total: s.total, accuracy: s.total ? s.correct / s.total : 0,
        score: s.score, missed, timeSpentS: Math.round((s.durationMs || 0) / 1000),
      })).then((res) => {
        pushToast(res && res.ok === false
          ? "Saved — we’ll submit your homework automatically when you’re back online"
          : "Homework submitted ✓");
      }).catch(() => pushToast("Saved — we’ll submit your homework when you’re back online"));
    }
    const session = {
      ts: Date.now(),
      score: s.score, total: s.total, correct: s.correct,
      accuracy: s.total ? s.correct / s.total : 0,
      durationMs: s.durationMs, bestStreak: s.bestStreak,
      source: hwCtx ? "homework" : "self",      // tags the teacher's recent-sessions table
      questions: Array.isArray(s.questions) ? s.questions : [],  // [{id, ua, c}] for review
    };
    recordSession(session);
    setSummary(s);
    setScreen("results");
  }, [recordSession, hwCtx, classData, pushToast]);

  // Verb-drill completion: submit the homework (if this was an assignment) and
  // record a session. Stays in the run's own "done" screen (no ResultsScreen).
  const finishVerbRun = aUseCallback((s) => {
    if (hwCtx) {
      const ctx = hwCtx; setHwCtx(null);
      Promise.resolve(classData.actions.submitHomework(ctx.assignmentId, ctx.handle, {
        correct: s.correct, total: s.total, accuracy: s.total ? s.correct / s.total : 0,
        score: s.score, missed: [], timeSpentS: Math.round((s.durationMs || 0) / 1000),
      })).then((res) => pushToast(res && res.ok === false
        ? "Saved — we’ll submit your homework when you’re back online" : "Homework submitted ✓"))
        .catch(() => pushToast("Saved — we’ll submit your homework when you’re back online"));
    }
    recordSession({
      ts: Date.now(), score: s.score, total: s.total, correct: s.correct,
      accuracy: s.total ? s.correct / s.total : 0, durationMs: s.durationMs || 0, bestStreak: 0,
      source: hwCtx ? "homework" : "self", questions: [],
    });
  }, [hwCtx, classData, pushToast, recordSession]);

  const exitPractice = aUseCallback(() => {
    if (confirm("End this session? Your answers so far are saved to lifetime stats.")) {
      setScreen("home");
    }
  }, []);

  const handleSignOut = aUseCallback(() => {
    if (confirm("Sign out? Your stats stay saved with your account.")) {
      auth.signOut();
      setScreen("home");
    }
  }, [auth]);

  // Auth gate — show AuthScreen until the user signs in / creates / chooses guest.
  if (!auth.decided) {
    return (
      <div className="app-root" data-density={TWEAK_DEFAULTS.density}>
        <AuthScreen
          onSignIn={auth.signIn}
          onSignUp={auth.signUp}
          onGuest={auth.continueAsGuest}
          onExamType={(t) => setSettings(s => ({ ...s, examMode: t === "oge" ? "oge" : "ege" }))}
        />
      </div>
    );
  }

  const MAIN_SCREENS = ["home", "stats", "categories", "settings", "mistakes", "class", "mytasks", "students", "trainer"];
  const RUN_SCREENS = ["practice", "trainerrun"];   // tasks keep the desktop side-panel, but no bottom nav on mobile
  const shellMode = MAIN_SCREENS.includes(screen) ? "main" : RUN_SCREENS.includes(screen) ? "run" : "full";

  return (
    <div className="app-root" data-density={TWEAK_DEFAULTS.density} data-shell={shellMode}>
      {(shellMode === "main" || shellMode === "run") && (
        <SideNav active={screen === "trainerrun" ? "trainer" : screen} go={setScreen} currentUser={auth.currentUser} onSignOut={handleSignOut} mistakesCount={mistakesCount} isTeacher={isTeacher} taskCount={taskCount} studentCount={studentCount} />
      )}
      {shellMode === "main" && (
        <BottomNav active={screen} go={setScreen} isTeacher={isTeacher} />
      )}
      {screen === "home" && (
        <HomeScreen
          stats={stats}
          settings={settings}
          onStart={start}
          onOpenStats={() => setScreen("stats")}
          onOpenSettings={() => setScreen("settings")}
          onOpenCategories={() => setScreen("categories")}
          onOpenMistakes={() => setScreen("mistakes")}
          onOpenLive={() => setScreen("live")}
          onOpenStreak={() => setScreen("streak")}
        />
      )}

      {screen === "streak" && (
        <StreakScreen
          stats={stats}
          onBack={() => setScreen("home")}
          onPractice={() => start({ length: examMode === "oge" ? 5 : 10 })}
        />
      )}

      {screen === "trainer" && (
        <TrainerScreen
          stats={stats}
          settings={settings}
          examMode={examMode}
          onOpenMistakes={() => setScreen("mistakes")}
          onOpenLive={() => setScreen("live")}
          onStartMixed={(types, len) => { const map = { wf: "word-formation", gr: "grammar", mc: "multiple-choice" }; const families = (types || []).map((t) => map[t]).filter(Boolean); start({ length: len === "Endless" ? Infinity : Number(len), endless: len === "Endless", families }); }}
          onStartVerbs={(types, len) => { const r = buildVerbQueue(types, len); if (r.queue.length) { setVerbRun({ ...r, returnTo: "trainer" }); setScreen("trainerrun"); } }}
        />
      )}

      {screen === "trainerrun" && verbRun && (
        <TrainerRun config={verbRun} onExit={() => setScreen(verbRun.returnTo || "trainer")} onFinish={finishVerbRun} recordAnswer={recordAnswer} />
      )}

      {screen === "practice" && queue.length > 0 && (
        <PracticeScreen
          queue={queue}
          endless={endless}
          soundOn={settings.soundOn}
          autoAdvance={settings.autoAdvance}
          reduceMotion={settings.reduceMotion}
          onFinish={finish}
          onExit={exitPractice}
          recordAnswer={recordAnswer}
        />
      )}

      {screen === "results" && summary && (
        <ResultsScreen
          summary={summary}
          onAgain={() => start({ length: summary.total === Infinity ? 10 : summary.total })}
          onReview={() => setScreen("review")}
          onHome={() => setScreen("home")}
        />
      )}

      {screen === "review" && summary && (
        <ReviewScreen wrong={summary.wrong} onBack={() => setScreen("results")} />
      )}

      {screen === "mistakes" && (
        <MistakesScreen
          stats={stats}
          onStart={start}
          onStartDrills={(ids) => { const r = buildVerbQueueFromIds(ids); if (r.queue.length) { setVerbRun({ ...r, returnTo: "mistakes" }); setScreen("trainerrun"); } }}
          onJoinLive={() => setScreen("live")}
          onBack={() => setScreen("home")}
          examMode={examMode}
        />
      )}

      {screen === "stats" && (
        <StatsScreen stats={stats} onBack={() => setScreen("home")} onReset={resetStats} examMode={examMode} />
      )}

      {screen === "categories" && (
        <CategoriesScreen settings={settings} setSettings={setSettings} stats={stats} onBack={() => setScreen("home")} />
      )}

      {screen === "settings" && (
        <SettingsScreen settings={settings} setSettings={setSettings} onBack={() => setScreen("home")} onOpenTests={() => setScreen("categories")} currentUser={auth.currentUser} onSignOut={handleSignOut}
          isTeacher={isTeacher} onOpenMistakes={() => setScreen("mistakes")} onOpenStats={() => setScreen("stats")} />
      )}

      {screen === "mytasks" && isTeacher && (
        <TeacherTasksScreen store={classData.store} actions={classData.actions} pushToast={pushToast} dataState={classData.dataState} onBack={() => setScreen("home")} />
      )}

      {screen === "students" && isTeacher && (
        <MyStudentsScreen store={classData.store} actions={classData.actions} pushToast={pushToast} dataState={classData.dataState} onBack={() => setScreen("home")} />
      )}

      {screen === "class" && (
        <ClassRoot
          classData={classData}
          auth={auth}
          pushToast={pushToast}
          onStartHomework={startHomework}
          onCreateAccount={() => auth.signOut()}
          onOpenStudents={() => setScreen("students")}
        />
      )}

      {screen === "live" && <LiveScreen onExit={() => { setScreen("home"); setLivePin(null); }} initialPin={livePin} tasks={Object.values(classData.store.tasks || {})} canHostTasks={isTeacher} />}

      <InstallPrompt />
      {toastNode}
    </div>
  );
}

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