/* final-exam.jsx
 *
 * Final Exam Mode — a one-shot 100-question, 120-minute simulation of the
 * real CCNA. Questions come from src/final-exam-questions.json, which was
 * parsed once from uploads/CCNA_Study_Guides_Enhanced/section_11_cumulative_exam.md
 * (see parse_final_exam.py).
 *
 * Why a separate view and not just a longer Quiz:
 *   - 51 of 100 questions are short-answer (free-text), not MCQ. The
 *     existing Quiz UI is MCQ-only.
 *   - Real-exam simulation means NO per-question feedback during; results
 *     and reasoning are shown ONLY at the end. The Quiz UI shows feedback
 *     as you go. Different mode, different contract.
 *   - The timer is intentionally prominent. The whole point is pressure.
 *
 * Honest UX flag (also surfaced in the intro screen, not hidden):
 *   Free-text grading is imperfect. We normalize aggressively (case,
 *   whitespace, surrounding punctuation, common variant spellings) and
 *   accept a match if the user's text contains the canonical answer or
 *   vice-versa for short canonical answers. We ALWAYS show the canonical
 *   answer next to theirs in the review, so a false-negative is easy to
 *   spot and self-correct.
 *
 * State machine:
 *   intro  → running (Start)
 *   running → results (Submit, or timer hits 0)
 *   results → intro (Take again)
 */
(function () {
  "use strict";

  const STORAGE_KEY = "ccna.final_exam.attempts";
  const EXAM_LENGTH_MINUTES = 120;

  // ──────────────── Free-text grading ────────────────
  // Used for short-answer questions. Returns true if `user` matches `canon`
  // under generous normalization. False-negatives are still possible
  // (impossible to avoid completely); UI ALWAYS shows the canonical answer
  // beside the user's so they can self-correct.
  function normalize(s) {
    if (s == null) return "";
    return String(s)
      .toLowerCase()
      .replace(/[.,;:!?'"`]/g, " ")
      .replace(/\s+/g, " ")
      .trim();
  }
  function gradeShortAnswer(userText, canon) {
    const u = normalize(userText);
    const c = normalize(canon);
    if (!u || !c) return false;
    if (u === c) return true;
    // Strip leading "yes." / "no." / "true." / "false." prefixes — both sides.
    const stripLead = (x) => x.replace(/^(yes|no|true|false)\b\s*/, "").trim();
    const uS = stripLead(u);
    const cS = stripLead(c);
    if (uS && uS === cS) return true;
    // For short canonical answers (≤4 words), accept if user contains it.
    // E.g. canon "udp 514", user types "udp 514 (syslog default)".
    if (c.split(" ").length <= 4 && u.includes(c)) return true;
    // Also accept if canon contains user (user gave a shorter version).
    // E.g. canon "authentication, authorization, accounting", user "aaa".
    if (u.split(" ").length <= 2 && c.includes(u) && u.length >= 3) return true;
    // Numbers: tolerate trailing units / explanation in parens.
    const numMatch = c.match(/^([\d.\/]+)$/);
    if (numMatch && u.startsWith(numMatch[1])) return true;
    return false;
  }

  function gradeMC(userAnswer, canonAnswer) {
    // canonAnswer is always an array of correct letters (post-schema-migration).
    // userAnswer is a string (single-answer) or array (multi-select).
    const canonSet = new Set(Array.isArray(canonAnswer) ? canonAnswer : [canonAnswer]);
    const userArr = Array.isArray(userAnswer) ? userAnswer : (userAnswer ? [userAnswer] : []);
    if (userArr.length !== canonSet.size) return false;
    for (const l of userArr) if (!canonSet.has((l || "").toUpperCase())) return false;
    return true;
  }

  // Multi-select MCQs are those with multiple correct letters.
  function isMultiSelect(q) {
    return q.type === "mc" && Array.isArray(q.correct) && q.correct.length > 1;
  }

  function formatTime(seconds) {
    if (seconds < 0) seconds = 0;
    const h = Math.floor(seconds / 3600);
    const m = Math.floor((seconds % 3600) / 60);
    const s = seconds % 60;
    return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}:${String(s).padStart(2, "0")}`;
  }

  // ──────────────── Intro screen ────────────────
  function Intro({ questions, lastAttempt, onStart }) {
    const counts = React.useMemo(() => {
      const c = { mc: 0, short: 0, byDomain: {} };
      questions.forEach((q) => {
        c[q.type]++;
        c.byDomain[q.domain] = (c.byDomain[q.domain] || 0) + 1;
      });
      return c;
    }, [questions]);

    return React.createElement(
      "div",
      { className: "fe-intro" },
      React.createElement("h2", null, "Final Exam — CCNA Cumulative"),
      React.createElement(
        "p",
        { className: "fe-blurb" },
        "A timed, 100-question simulation drawn from the cumulative exam in your study guide. The real CCNA gives you 120 minutes. So does this. No feedback until you submit."
      ),

      React.createElement(
        "div",
        { className: "fe-card-row" },
        React.createElement(
          "div",
          { className: "fe-card" },
          React.createElement("div", { className: "fe-card-num" }, questions.length),
          React.createElement("div", { className: "fe-card-label" }, "Questions")
        ),
        React.createElement(
          "div",
          { className: "fe-card" },
          React.createElement("div", { className: "fe-card-num" }, EXAM_LENGTH_MINUTES),
          React.createElement("div", { className: "fe-card-label" }, "Minutes")
        ),
        React.createElement(
          "div",
          { className: "fe-card" },
          React.createElement("div", { className: "fe-card-num" }, counts.mc),
          React.createElement("div", { className: "fe-card-label" }, "Multiple-choice")
        ),
        React.createElement(
          "div",
          { className: "fe-card" },
          React.createElement("div", { className: "fe-card-num" }, counts.short),
          React.createElement("div", { className: "fe-card-label" }, "Short-answer")
        )
      ),

      React.createElement(
        "div",
        { className: "fe-section" },
        React.createElement("h3", null, "Domain coverage"),
        React.createElement(
          "ul",
          { className: "fe-domain-list" },
          Object.entries(counts.byDomain)
            .sort()
            .map(([d, n]) =>
              React.createElement(
                "li",
                { key: d },
                React.createElement("span", { className: "fe-d-name" }, d),
                React.createElement("span", { className: "fe-d-n" }, `${n} questions`)
              )
            )
        )
      ),

      React.createElement(
        "div",
        { className: "fe-warning" },
        React.createElement("strong", null, "Honest note on grading: "),
        "Short-answer questions (about half the exam) are graded by text matching with generous normalization. The grader is conservative — if your answer is essentially right but worded differently, it may mark you wrong. The review screen always shows the expected answer next to yours so you can self-correct. Score this exam as a directional gauge, not an absolute one."
      ),

      lastAttempt &&
        React.createElement(
          "div",
          { className: "fe-section fe-last" },
          React.createElement("h3", null, "Last attempt"),
          React.createElement(
            "p",
            null,
            `${lastAttempt.correct} / ${lastAttempt.total} correct (${Math.round((lastAttempt.correct / lastAttempt.total) * 100)}%) — ${new Date(lastAttempt.finishedAt).toLocaleString()}`
          )
        ),

      React.createElement(
        "div",
        { className: "fe-actions" },
        React.createElement(
          "button",
          { className: "btn btn-primary fe-start", onClick: onStart },
          "Start exam (",
          EXAM_LENGTH_MINUTES,
          " min)"
        )
      )
    );
  }

  // ──────────────── Running exam ────────────────
  function Running({ questions, onSubmit }) {
    const [answers, setAnswers] = React.useState(() => new Array(questions.length).fill(""));
    const [idx, setIdx] = React.useState(0);
    const [secondsLeft, setSecondsLeft] = React.useState(EXAM_LENGTH_MINUTES * 60);
    const [confirmOpen, setConfirmOpen] = React.useState(false);
    const startedAt = React.useRef(Date.now());

    // Timer
    React.useEffect(() => {
      const tick = setInterval(() => {
        setSecondsLeft((s) => {
          if (s <= 1) {
            clearInterval(tick);
            // Use setTimeout so we exit the render cycle before onSubmit
            // mutates parent state.
            setTimeout(() => onSubmit(answers, startedAt.current, true), 0);
            return 0;
          }
          return s - 1;
        });
      }, 1000);
      return () => clearInterval(tick);
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    const q = questions[idx];
    const answered = answers.filter((a) => {
      if (Array.isArray(a)) return a.length > 0;
      return a && String(a).trim();
    }).length;

    function setAnswer(v) {
      setAnswers((prev) => {
        const next = prev.slice();
        next[idx] = v;
        return next;
      });
    }

    function go(delta) {
      const nx = Math.max(0, Math.min(questions.length - 1, idx + delta));
      setIdx(nx);
    }

    function jumpTo(i) {
      setIdx(i);
    }

    function handleSubmit() {
      setConfirmOpen(true);
    }
    function confirmSubmit() {
      onSubmit(answers, startedAt.current, false);
    }

    const lowTime = secondsLeft < 15 * 60;

    return React.createElement(
      "div",
      { className: "fe-running" },

      // Timer + progress bar
      React.createElement(
        "div",
        { className: `fe-toolbar ${lowTime ? "fe-low" : ""}` },
        React.createElement("div", { className: "fe-timer" }, formatTime(secondsLeft)),
        React.createElement(
          "div",
          { className: "fe-progress" },
          `Question ${idx + 1} of ${questions.length} • ${answered} answered`
        ),
        React.createElement(
          "button",
          { className: "btn btn-primary fe-submit", onClick: handleSubmit },
          "Submit exam"
        )
      ),

      // Current question
      React.createElement(
        "div",
        { className: "fe-qcard" },
        React.createElement("div", { className: "fe-qmeta" }, `Q${q.id} • ${q.domain}`),
        React.createElement(
          "div",
          { className: "fe-qtext" },
          q.question,
          isMultiSelect(q) &&
            React.createElement(
              "span",
              { className: "fe-multi-badge" },
              `Pick ${q.correct.length}`
            )
        ),
        q.type === "mc"
          ? React.createElement(
              "div",
              { className: "fe-options" },
              q.options.map(([letter, text]) => {
                const multi = isMultiSelect(q);
                const cur = answers[idx];
                const selectedSet = new Set(Array.isArray(cur) ? cur : (cur ? [cur] : []));
                const isSelected = selectedSet.has(letter);
                const onToggle = () => {
                  if (multi) {
                    const next = new Set(selectedSet);
                    if (next.has(letter)) next.delete(letter);
                    else next.add(letter);
                    setAnswer(Array.from(next).sort());
                  } else {
                    setAnswer(letter);
                  }
                };
                return React.createElement(
                  "label",
                  {
                    key: letter,
                    className: `fe-opt ${isSelected ? "selected" : ""}`,
                  },
                  React.createElement("input", {
                    type: multi ? "checkbox" : "radio",
                    name: `q-${q.id}`,
                    checked: isSelected,
                    onChange: onToggle,
                  }),
                  React.createElement("span", { className: "fe-opt-letter" }, letter),
                  React.createElement("span", { className: "fe-opt-text" }, text)
                );
              })
            )
          : React.createElement("textarea", {
              className: "fe-shortans",
              rows: 3,
              placeholder: "Type your answer…",
              value: answers[idx],
              onChange: (e) => setAnswer(e.target.value),
            })
      ),

      // Prev/Next
      React.createElement(
        "div",
        { className: "fe-nav" },
        React.createElement(
          "button",
          { className: "btn", disabled: idx === 0, onClick: () => go(-1) },
          "← Previous"
        ),
        React.createElement(
          "button",
          {
            className: "btn",
            disabled: idx === questions.length - 1,
            onClick: () => go(1),
          },
          "Next →"
        )
      ),

      // Jump grid
      React.createElement(
        "div",
        { className: "fe-grid" },
        questions.map((qq, i) => {
          const a = answers[i];
          const hasAns = Array.isArray(a) ? a.length > 0 : !!a;
          return React.createElement(
            "button",
            {
              key: qq.id,
              className: `fe-grid-cell ${
                i === idx ? "current" : ""
              } ${hasAns ? "answered" : ""}`,
              onClick: () => jumpTo(i),
              title: `Q${qq.id} ${hasAns ? "(answered)" : "(unanswered)"}`,
            },
            qq.id
          );
        })
      ),

      // Submit-confirm modal
      confirmOpen &&
        React.createElement(
          "div",
          { className: "fe-modal-backdrop", onClick: () => setConfirmOpen(false) },
          React.createElement(
            "div",
            { className: "fe-modal", onClick: (e) => e.stopPropagation() },
            React.createElement("h3", null, "Submit exam?"),
            React.createElement(
              "p",
              null,
              `${answered} of ${questions.length} answered. ${
                answered < questions.length
                  ? `${questions.length - answered} will be marked wrong.`
                  : "All questions answered."
              }`
            ),
            React.createElement(
              "div",
              { className: "fe-modal-actions" },
              React.createElement(
                "button",
                { className: "btn", onClick: () => setConfirmOpen(false) },
                "Keep working"
              ),
              React.createElement(
                "button",
                { className: "btn btn-primary", onClick: confirmSubmit },
                "Submit"
              )
            )
          )
        )
    );
  }

  // ──────────────── Results ────────────────
  function Results({ questions, answers, finishedAt, startedAt, timedOut, onRestart }) {
    const graded = React.useMemo(
      () =>
        questions.map((q, i) => {
          const user = answers[i];
          const correct =
            q.type === "mc"
              ? gradeMC(user, q.correct)
              : gradeShortAnswer(user, q.correct);
          return { q, user, correct };
        }),
      [questions, answers]
    );
    const totalCorrect = graded.filter((g) => g.correct).length;
    const pct = Math.round((totalCorrect / graded.length) * 100);

    // Per-domain breakdown
    const byDomain = {};
    graded.forEach((g) => {
      const d = g.q.domain;
      if (!byDomain[d]) byDomain[d] = { total: 0, correct: 0 };
      byDomain[d].total++;
      if (g.correct) byDomain[d].correct++;
    });

    const verdict =
      pct >= 95
        ? { tone: "great", text: "Exam-ready territory." }
        : pct >= 85
        ? { tone: "good", text: "Strong. Review weak spots, then re-test." }
        : pct >= 75
        ? { tone: "warn", text: "Borderline. Real exam is harder than practice." }
        : pct >= 60
        ? { tone: "warn", text: "More study needed. Re-read weak domains." }
        : { tone: "bad", text: "Significant gaps. Step back to chapter-level review." };

    const minutesUsed = Math.round((finishedAt - startedAt) / 60000);

    // Save attempt
    React.useEffect(() => {
      try {
        const arr = JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]");
        arr.push({
          finishedAt,
          startedAt,
          total: graded.length,
          correct: totalCorrect,
          minutesUsed,
          timedOut,
        });
        // Keep last 20
        const trimmed = arr.slice(-20);
        localStorage.setItem(STORAGE_KEY, JSON.stringify(trimmed));
      } catch (_) {
        /* localStorage may be disabled */
      }
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, []);

    const [filter, setFilter] = React.useState("missed"); // 'missed' | 'all'

    return React.createElement(
      "div",
      { className: "fe-results" },

      React.createElement(
        "div",
        { className: "fe-score-hero" },
        React.createElement(
          "div",
          { className: `fe-score-num tone-${verdict.tone}` },
          `${totalCorrect} / ${graded.length}`
        ),
        React.createElement(
          "div",
          { className: "fe-score-pct" },
          `${pct}%`,
          timedOut &&
            React.createElement(
              "span",
              { className: "fe-timed-out" },
              " (timed out)"
            )
        ),
        React.createElement("div", { className: "fe-verdict" }, verdict.text),
        React.createElement(
          "div",
          { className: "fe-meta" },
          `Completed in ${minutesUsed} min`
        )
      ),

      React.createElement(
        "div",
        { className: "fe-section" },
        React.createElement("h3", null, "By domain"),
        React.createElement(
          "table",
          { className: "fe-domain-table" },
          React.createElement(
            "thead",
            null,
            React.createElement(
              "tr",
              null,
              React.createElement("th", null, "Domain"),
              React.createElement("th", null, "Score"),
              React.createElement("th", null, "%")
            )
          ),
          React.createElement(
            "tbody",
            null,
            Object.entries(byDomain)
              .sort()
              .map(([d, v]) =>
                React.createElement(
                  "tr",
                  { key: d },
                  React.createElement("td", null, d),
                  React.createElement("td", null, `${v.correct} / ${v.total}`),
                  React.createElement(
                    "td",
                    null,
                    `${Math.round((v.correct / v.total) * 100)}%`
                  )
                )
              )
          )
        )
      ),

      React.createElement(
        "div",
        { className: "fe-section" },
        React.createElement(
          "div",
          { className: "fe-review-header" },
          React.createElement("h3", null, "Review"),
          React.createElement(
            "div",
            { className: "fe-filter" },
            React.createElement(
              "button",
              {
                className: `btn btn-sm ${filter === "missed" ? "btn-primary" : ""}`,
                onClick: () => setFilter("missed"),
              },
              `Missed only (${graded.filter((g) => !g.correct).length})`
            ),
            React.createElement(
              "button",
              {
                className: `btn btn-sm ${filter === "all" ? "btn-primary" : ""}`,
                onClick: () => setFilter("all"),
              },
              `All (${graded.length})`
            )
          )
        ),
        React.createElement(
          "div",
          { className: "fe-review-list" },
          graded
            .filter((g) => (filter === "missed" ? !g.correct : true))
            .map((g, i) =>
              React.createElement(
                "div",
                {
                  key: g.q.id,
                  className: `fe-review-item ${g.correct ? "ok" : "bad"}`,
                },
                React.createElement(
                  "div",
                  { className: "fe-review-head" },
                  React.createElement(
                    "span",
                    { className: "fe-review-qnum" },
                    `Q${g.q.id}`
                  ),
                  React.createElement(
                    "span",
                    { className: "fe-review-domain" },
                    g.q.domain
                  ),
                  React.createElement(
                    "span",
                    {
                      className: `fe-review-tag ${
                        g.correct ? "tag-ok" : "tag-bad"
                      }`,
                    },
                    g.correct ? "✓ correct" : "✗ wrong"
                  )
                ),
                React.createElement(
                  "div",
                  { className: "fe-review-q" },
                  g.q.question
                ),
                g.q.type === "mc" &&
                  React.createElement(
                    "div",
                    { className: "fe-review-opts" },
                    (() => {
                      const correctSet = new Set(Array.isArray(g.q.correct) ? g.q.correct : [g.q.correct]);
                      const userSet = new Set(Array.isArray(g.user) ? g.user : (g.user ? [g.user] : []));
                      return g.q.options.map(([letter, text]) =>
                        React.createElement(
                          "div",
                          {
                            key: letter,
                            className: `fe-review-opt ${
                              correctSet.has(letter) ? "is-correct" : ""
                            } ${
                              userSet.has(letter) && !correctSet.has(letter)
                                ? "was-wrong"
                                : ""
                            }`,
                          },
                          `${letter}) ${text}`
                        )
                      );
                    })()
                  ),
                g.q.type === "short" &&
                  React.createElement(
                    "div",
                    { className: "fe-review-ans" },
                    React.createElement(
                      "div",
                      { className: "fe-ans-row" },
                      React.createElement(
                        "span",
                        { className: "fe-ans-label" },
                        "Your answer:"
                      ),
                      React.createElement(
                        "span",
                        { className: "fe-ans-text" },
                        g.user || React.createElement("em", null, "(blank)")
                      )
                    ),
                    React.createElement(
                      "div",
                      { className: "fe-ans-row" },
                      React.createElement(
                        "span",
                        { className: "fe-ans-label" },
                        "Expected:"
                      ),
                      React.createElement(
                        "span",
                        { className: "fe-ans-text fe-ans-canon" },
                        g.q.correct
                      )
                    )
                  ),
                g.q.explanation &&
                  React.createElement(
                    "div",
                    { className: "fe-review-ex" },
                    React.createElement(
                      "strong",
                      null,
                      "Why: "
                    ),
                    g.q.explanation
                  )
              )
            )
        )
      ),

      React.createElement(
        "div",
        { className: "fe-actions" },
        React.createElement(
          "button",
          { className: "btn btn-primary", onClick: onRestart },
          "Take exam again"
        )
      )
    );
  }

  // ──────────────── Outer state machine ────────────────
  function FinalExamMode({ onExit }) {
    const [phase, setPhase] = React.useState("intro");
    const [submission, setSubmission] = React.useState(null);

    const questions = window.FINAL_EXAM_QUESTIONS || [];

    const lastAttempt = React.useMemo(() => {
      try {
        const arr = JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]");
        return arr.length ? arr[arr.length - 1] : null;
      } catch (_) {
        return null;
      }
    }, [phase]);

    if (!questions.length) {
      return React.createElement(
        "div",
        { className: "fe-empty" },
        "Final exam questions failed to load. Check that ",
        React.createElement("code", null, "src/final-exam-questions.json"),
        " is being served and the ",
        React.createElement("code", null, "<script>"),
        " tag in the HTML loads it before final-exam.jsx."
      );
    }

    if (phase === "intro") {
      return React.createElement(Intro, {
        questions,
        lastAttempt,
        onStart: () => setPhase("running"),
      });
    }
    if (phase === "running") {
      return React.createElement(Running, {
        questions,
        onSubmit: (answers, startedAt, timedOut) => {
          setSubmission({
            answers,
            startedAt,
            finishedAt: Date.now(),
            timedOut,
          });
          setPhase("results");
        },
      });
    }
    if (phase === "results" && submission) {
      return React.createElement(Results, {
        questions,
        answers: submission.answers,
        startedAt: submission.startedAt,
        finishedAt: submission.finishedAt,
        timedOut: submission.timedOut,
        onRestart: () => {
          setSubmission(null);
          setPhase("intro");
        },
      });
    }
    return null;
  }

  window.FinalExamMode = FinalExamMode;
})();
