/* ================================================================
   Model Answer Authoring — entire feature module
   ----------------------------------------------------------------
   Backed by the Bengali pipeline at
     POST /api/questions/:id/answer-key
     GET  /api/questions/:id/answer-key
     POST /api/questions/:id/answer-key/approve
     POST /api/tests/:id/answer-keys
     GET  /api/tests/:id/answer-keys

   The screen is self-contained — pick a test, list its questions on the
   left, generate / view / approve the 4-stage answer-key on the right.
   ================================================================ */

const AK_Icon = window.KXUI.Icon;
const AK_Api  = window.KXApi;

/* ---------- shared helpers ---------- */

const AK_LANG_LABEL = { bn: "বাংলা", hi: "हिन्दी", en: "English" };

function ak_useTests() {
  const [tests, setTests] = useState([]);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    let alive = true;
    AK_Api.get("/tests")
      .then(d => { if (alive) { setTests((d || []).filter(t => Number(t.questions) > 0)); setLoading(false); } })
      .catch(() => { if (alive) setLoading(false); });
    return () => { alive = false; };
  }, []);
  return { tests, loading };
}

function ak_useQuestions(testDisplayId) {
  const [questions, setQuestions] = useState([]);
  const [loading, setLoading] = useState(false);
  useEffect(() => {
    if (!testDisplayId) { setQuestions([]); return; }
    let alive = true;
    setLoading(true);
    AK_Api.get(`/tests/${testDisplayId}/questions`)
      .then(d => { if (alive) { setQuestions(d || []); setLoading(false); } })
      .catch(() => { if (alive) setLoading(false); });
    return () => { alive = false; };
  }, [testDisplayId]);
  return { questions, loading };
}

/* Hash a question id to a pleasant accent for chips. */
const AK_PALETTE = ["#3b7bf6", "#2fbe5f", "#f5318d", "#6b5bd2", "#ff9f0a", "#e5484d"];
const ak_colorFor = (qid, all) => {
  const idx = Math.max(0, all.findIndex(q => q.id === qid));
  return AK_PALETTE[idx % AK_PALETTE.length];
};

/* ================================================================
   ROOT — orchestrates test pick, question list, right-pane viewer
   ================================================================ */
const AnswerKeyAuthoring = ({ setScreen }) => {
  const { tests, loading: testsLoading } = ak_useTests();
  const [testId, setTestId] = useState(null);
  const test = tests.find(t => t.id === testId) || null;

  // Auto-pick first test once the list loads (skip if URL hash has a hint).
  useEffect(() => {
    if (!testId && tests.length) {
      const fromHash = decodeURIComponent((location.hash.match(/test=([^&]+)/) || [])[1] || "");
      const found = fromHash && tests.find(t => t.id === fromHash);
      setTestId(found ? found.id : tests[0].id);
    }
  }, [tests, testId]);

  const { questions, loading: questionsLoading } = ak_useQuestions(test?.id);

  // answer keys keyed by question_id
  const [keys, setKeys] = useState({});          // { [qid]: AnswerKey }
  const [running, setRunning] = useState({});    // { [qid]: 'running'|'error' }
  const [bulkProgress, setBulkProgress] = useState(null);  // null | { running, total, succeeded, failed }
  const [activeQid, setActiveQid] = useState(null);
  const [language, setLanguage] = useState("bn");
  const [printable, setPrintable] = useState(false);

  // Hydrate any persisted answer-keys when test/questions arrive.
  useEffect(() => {
    if (!test) return;
    let alive = true;
    AK_Api.get(`/tests/${test.id}/answer-keys`)
      .then(rows => {
        if (!alive) return;
        const byId = {};
        for (const r of rows || []) byId[r.question_id] = r;
        setKeys(byId);
      })
      .catch(() => {});
    return () => { alive = false; };
  }, [test?.id]);

  useEffect(() => {
    if (!activeQid && questions.length) setActiveQid(questions[0].id);
  }, [questions, activeQid]);

  const activeQ = questions.find(q => q.id === activeQid) || null;
  const activeKey = activeQid ? keys[activeQid] : null;

  const runForQuestion = async (qid) => {
    setRunning(r => ({ ...r, [qid]: "running" }));
    try {
      const ak = await AK_Api.post(`/questions/${qid}/answer-key?lang=${language}`);
      setKeys(prev => ({ ...prev, [qid]: ak }));
      setRunning(r => { const c = { ...r }; delete c[qid]; return c; });
    } catch (e) {
      console.error("[answer-key]", e);
      setRunning(r => ({ ...r, [qid]: "error" }));
    }
  };

  const approveQuestion = async (qid) => {
    try {
      await AK_Api.post(`/questions/${qid}/answer-key/approve`);
      setKeys(prev => prev[qid]
        ? { ...prev, [qid]: { ...prev[qid], status: "approved" } }
        : prev);
    } catch (e) { console.error(e); }
  };

  const runForAll = async (skipExisting) => {
    if (!test) return;
    const todo = questions
      .map(q => q.id)
      .filter(id => !skipExisting || !keys[id]);
    if (!todo.length) return;
    setBulkProgress({ running: 0, total: todo.length, succeeded: 0, failed: 0 });
    // Run sequentially client-side so the user sees per-question updates; the
    // backend is the rate-limiter (it caps concurrency anyway).
    let succeeded = 0, failed = 0;
    for (let i = 0; i < todo.length; i++) {
      const qid = todo[i];
      setBulkProgress({ running: i + 1, total: todo.length, succeeded, failed });
      try {
        await runForQuestion(qid);
        succeeded++;
      } catch { failed++; }
    }
    setBulkProgress({ running: todo.length, total: todo.length, succeeded, failed });
    setTimeout(() => setBulkProgress(null), 3500);
  };

  return (
    <div style={{ display:"flex", flexDirection:"column", height:"100%", background:"var(--bg-0)" }}>
      <AK_Header
        tests={tests} testId={testId} setTestId={setTestId} testsLoading={testsLoading}
        language={language} setLanguage={setLanguage}
        printable={printable} setPrintable={setPrintable}
        questions={questions} keys={keys}
        onRunAll={() => runForAll(true)}
        onRegenerateAll={() => runForAll(false)}
        bulkProgress={bulkProgress}
      />

      {!test ? (
        <div style={{ flex:1, display:"grid", placeItems:"center", color:"var(--ink-3)" }}>
          {testsLoading ? "Loading tests…" : "No tests with parsed questions yet — upload one from the Test Library first."}
        </div>
      ) : printable ? (
        <AK_PrintView test={test} questions={questions} keys={keys}/>
      ) : (
        <div className="cx-catalog-split flush" style={{ flex:1, minHeight: 0 }}>
          <AK_QuestionSidebar
            questions={questions}
            keys={keys}
            running={running}
            activeQid={activeQid}
            onPick={setActiveQid}
            loading={questionsLoading}
          />
          <AK_RightPane
            question={activeQ}
            answerKey={activeKey}
            running={!!activeQid && running[activeQid] === "running"}
            error={!!activeQid && running[activeQid] === "error"}
            onRun={() => activeQid && runForQuestion(activeQid)}
            onApprove={() => activeQid && approveQuestion(activeQid)}
            language={language}
          />
        </div>
      )}
    </div>
  );
};

/* ================================================================
   HEADER — test picker + bulk actions + progress strip
   ================================================================ */
const AK_Header = ({
  tests, testId, setTestId, testsLoading,
  language, setLanguage,
  printable, setPrintable,
  questions, keys, onRunAll, onRegenerateAll, bulkProgress,
}) => {
  const total = questions.length;
  const generated = questions.filter(q => keys[q.id]).length;
  const approved  = questions.filter(q => keys[q.id]?.status === "approved").length;

  return (
    <div style={{
      padding:"12px 20px", borderBottom:"1px solid var(--line)",
      display:"flex", alignItems:"center", gap:14, flexWrap:"wrap",
      background:"var(--bg-1)",
    }}>
      <div style={{ display:"flex", alignItems:"center", gap: 10 }}>
        <div style={{ width: 28, height: 28, borderRadius: 7,
          background:"var(--ink-btn)",          display:"grid", placeItems:"center", color:"#fff", fontWeight: 700, fontSize: 13 }}>A</div>
        <div>
          <div style={{ color:"var(--ink-0)", fontWeight: 600, fontSize: 14, lineHeight: 1 }}>Model Answer Keys</div>
          <div style={{ fontSize: 10.5, color:"var(--ink-3)", letterSpacing:".05em", textTransform:"uppercase", marginTop: 3 }}>
            scope · perfect answer · logic · deduction-proofing
          </div>
        </div>
      </div>

      <div style={{ width: 1, height: 22, background:"var(--line)", margin:"0 6px" }}/>

      <select
        className="select"
        value={testId || ""}
        onChange={e => setTestId(e.target.value)}
        disabled={testsLoading || !tests.length}
        style={{ width: "min(360px, 100%)", padding:"5px 30px 5px 10px", fontSize: 12 }}
        title="Pick a test"
      >
        {testsLoading
          ? <option value="">Loading tests…</option>
          : tests.length
            ? tests.map(t => (
              <option key={t.id} value={t.id}>
                {t.id} · {(t.title || "Untitled").slice(0, 52)} · {t.questions} qs
              </option>
            ))
            : <option value="">No tests with questions</option>}
      </select>

      <div className="seg" title="Output language for the pipeline">
        {["bn", "hi", "en"].map(l => (
          <button key={l} className={language === l ? "active" : ""} onClick={() => setLanguage(l)}>
            {AK_LANG_LABEL[l]}
          </button>
        ))}
      </div>

      {/* Two counters and three buttons. Without wrapping this row measures
          553px, which on a phone widened the whole layout viewport rather
          than merely overflowing — every other element then rendered at the
          wrong scale. flexWrap + minWidth:0 keeps it inside the screen. */}
      <span style={{ marginLeft: "auto", display:"flex", gap: 6, alignItems:"center",
                     flexWrap: "wrap", minWidth: 0, justifyContent: "flex-end" }}>
        <span className="pill mono" style={{ fontSize: 10 }}>{generated}/{total} generated</span>
        <span className="pill mono" style={{ fontSize: 10, color:"var(--green)", borderColor:"rgba(75,201,123,0.3)" }}>{approved}/{total} approved</span>
        <button className="btn sm" onClick={onRunAll} disabled={!total || generated === total || !!bulkProgress}
          title="Run the pipeline for every question that does not yet have an answer key"
          style={{ opacity: (!total || generated === total || !!bulkProgress) ? 0.5 : 1 }}>
          <AK_Icon name="sparkle" size={11}/> Generate missing
        </button>
        <button className="btn sm" onClick={onRegenerateAll} disabled={!total || !!bulkProgress}
          title="Re-run the pipeline for ALL questions, overwriting existing drafts"
          style={{ opacity: (!total || !!bulkProgress) ? 0.5 : 1 }}>
          <AK_Icon name="sparkle" size={11}/> Regenerate all
        </button>
        <button className={`btn sm ${printable ? "primary" : ""}`} onClick={() => setPrintable(p => !p)}
          title="Toggle printable A4 view">
          {printable ? "Back to authoring" : "Print view"}
        </button>
      </span>

      {bulkProgress && (
        <div style={{ flexBasis:"100%", marginTop: 8 }}>
          <div className="progress" style={{ height: 5, borderRadius: 3 }}>
            <div style={{ width: `${(bulkProgress.running / Math.max(1, bulkProgress.total)) * 100}%` }}/>
          </div>
          <div style={{ marginTop: 4, fontSize: 11, color:"var(--ink-3)", display:"flex", justifyContent:"space-between" }}>
            <span>Running pipeline · {bulkProgress.running} of {bulkProgress.total}</span>
            <span><span style={{ color:"var(--green)" }}>✓ {bulkProgress.succeeded}</span> · <span style={{ color:"var(--red)" }}>✕ {bulkProgress.failed}</span></span>
          </div>
        </div>
      )}
    </div>
  );
};

/* ================================================================
   QUESTION SIDEBAR — sectioned list with status dots
   ================================================================ */
const AK_QuestionSidebar = ({ questions, keys, running, activeQid, onPick, loading }) => {
  // Group by inferred section letter (first non-digit prefix in q_no) — falls
  // back to a single "Questions" bucket if numbering is plain.
  const groups = useMemo(() => {
    const m = new Map();
    questions.forEach(q => {
      const key = String(q.num ?? "").match(/^[A-Z]/) ? String(q.num)[0] : "•";
      if (!m.has(key)) m.set(key, []);
      m.get(key).push(q);
    });
    return [...m.entries()].map(([k, items]) => ({ k, items }));
  }, [questions]);

  return (
    <aside style={{ borderRight:"1px solid var(--line)", background:"var(--bg-1)", overflow:"auto" }}>
      <div style={{ padding:"10px 14px", borderBottom:"1px solid var(--line)", position:"sticky", top: 0, background:"var(--bg-1)", zIndex: 1 }}>
        <div style={{ fontSize: 11, color:"var(--ink-3)", textTransform:"uppercase", letterSpacing:".08em" }}>Questions</div>
        <div style={{ color:"var(--ink-0)", fontSize: 13, fontWeight: 500 }}>{questions.length} total</div>
      </div>

      {loading && <div className="muted" style={{ padding: 16, fontSize: 12 }}>Loading…</div>}

      {!loading && groups.length === 0 && (
        <div className="muted" style={{ padding: 16, fontSize: 12 }}>No questions in this test.</div>
      )}

      {groups.map(({ k, items }) => (
        <div key={k}>
          {groups.length > 1 && (
            <div style={{ padding:"8px 14px 4px", fontSize: 10, color:"var(--ink-3)", letterSpacing:".08em", textTransform:"uppercase" }}>
              {k === "•" ? "Questions" : `§${k}`}
            </div>
          )}
          {items.map(q => {
            const ak = keys[q.id];
            const r = running[q.id];
            const active = q.id === activeQid;
            const color = ak_colorFor(q.id, questions);
            return (
              <div key={q.id} onClick={() => onPick(q.id)} style={{
                padding:"9px 14px",
                cursor:"pointer",
                borderLeft: `3px solid ${active ? color : "transparent"}`,
                background: active ? "var(--bg-2)" : "transparent",
                borderBottom:"1px solid var(--line-soft)",
              }}>
                <div style={{ display:"flex", alignItems:"center", gap:6, marginBottom: 4 }}>
                  <span className="mono" style={{ color: active ? color : "var(--ink-2)", fontSize: 12, fontWeight: 600 }}>Q{q.num}</span>
                  <span className="pill mono" style={{ fontSize: 9.5, padding:"1px 5px" }}>{q.type}</span>
                  <span className="pill" style={{ fontSize: 9.5, padding:"1px 5px" }}>{q.maxMarks ?? "—"}m</span>
                  <span style={{ marginLeft:"auto" }}>
                    <AK_StatusBadge ak={ak} running={r}/>
                  </span>
                </div>
                <div style={{ fontSize: 11.5, color: active ? "var(--ink-0)" : "var(--ink-2)", lineHeight: 1.4,
                  fontFamily:"'Hind Siliguri', system-ui, sans-serif",
                  display:"-webkit-box", WebkitLineClamp:2, WebkitBoxOrient:"vertical", overflow:"hidden",
                }}>{q.text}</div>
              </div>
            );
          })}
        </div>
      ))}
    </aside>
  );
};

const AK_StatusBadge = ({ ak, running }) => {
  if (running === "running") {
    return <span style={{ width: 8, height: 8, borderRadius: "50%", background:"var(--accent)", animation:"pulse 1.2s ease-in-out infinite", display:"inline-block" }}/>;
  }
  if (running === "error") {
    return <span style={{ color:"var(--red)", fontSize: 10, fontWeight: 600 }}>✕</span>;
  }
  if (!ak)             return <span style={{ width: 7, height: 7, borderRadius:"50%", background:"var(--ink-4)", display:"inline-block" }}/>;
  if (ak.status === "approved") return <span style={{ color:"var(--green)", fontSize: 11 }}>✓</span>;
  return <span style={{ width: 7, height: 7, borderRadius:"50%", background:"var(--blue)", display:"inline-block" }}/>;
};

/* ================================================================
   RIGHT PANE — 4 stacked cards or empty state
   ================================================================ */
const AK_RightPane = ({ question, answerKey, running, error, onRun, onApprove, language }) => {
  if (!question) {
    return <div style={{ display:"grid", placeItems:"center", color:"var(--ink-3)" }}>Pick a question on the left.</div>;
  }
  return (
    <main style={{ overflow:"auto", padding: "20px 26px", display:"flex", flexDirection:"column", gap: 14, background: "var(--bg-0)" }}>
      <AK_QuestionHeader q={question} ak={answerKey} running={running} error={error} onRun={onRun} onApprove={onApprove} language={language}/>

      {!answerKey && !running && !error && (
        <div style={{ padding: 32, textAlign:"center", border:"1px dashed var(--line-strong)", borderRadius: 10, color:"var(--ink-3)", background:"var(--bg-1)" }}>
          <div style={{ fontSize: 28, marginBottom: 6 }}>✨</div>
          <div style={{ color:"var(--ink-1)", fontSize: 13.5, fontWeight: 500, marginBottom: 4 }}>No model answer yet</div>
          <div style={{ fontSize: 12 }}>Click <b>Generate</b> above. The pipeline runs scope → perfect answer → logic → deduction-proofing (~50s on Gemini Pro).</div>
        </div>
      )}

      {running && <AK_RunningCard q={question} language={language}/>}
      {error && !running && (
        <div style={{ padding: 14, border:"1px solid rgba(255,107,107,0.3)", background:"rgba(255,107,107,0.06)", borderRadius: 8, color:"var(--red)", fontSize: 12 }}>
          The pipeline failed. Open the eavesdropper (<span className="mono">/api/_eavesdrop/stream</span>) to see why, then retry.
        </div>
      )}

      {answerKey && <AK_ScopeCard ak={answerKey}/>}
      {answerKey && <AK_PerfectCard ak={answerKey} q={question}/>}
      {answerKey && <AK_LogicCard ak={answerKey}/>}
      {answerKey && <AK_DeductionCard ak={answerKey}/>}
    </main>
  );
};

const AK_QuestionHeader = ({ q, ak, running, error, onRun, onApprove, language }) => (
  <div style={{ display:"flex", alignItems:"flex-start", gap: 12, padding:"12px 14px",
    background:"var(--bg-1)", border:"1px solid var(--line)", borderRadius: 10 }}>
    <div style={{ flexShrink: 0, paddingTop: 1 }}>
      <span className="mono" style={{ color:"var(--accent)", fontSize: 13, fontWeight: 700 }}>Q{q.num}</span>
    </div>
    <div style={{ flex:1, minWidth: 0 }}>
      <div style={{ display:"flex", alignItems:"center", gap: 6, marginBottom: 5 }}>
        <span className="pill mono" style={{ fontSize: 10 }}>{q.type}</span>
        <span className="pill" style={{ fontSize: 10 }}>{q.maxMarks} marks</span>
        {ak && (
          <span className={`pill mono`} style={{
            fontSize: 10,
            color: ak.status === "approved" ? "var(--green)" : "var(--amber)",
            borderColor: ak.status === "approved" ? "rgba(75,201,123,0.3)" : "rgba(246,181,59,0.3)",
            background: ak.status === "approved" ? "var(--green-bg)" : "var(--amber-bg)",
          }}>{ak.status === "approved" ? "✓ approved" : "draft"}</span>
        )}
        {ak && <span className="pill mono" style={{ fontSize: 10 }}>{AK_LANG_LABEL[ak.language] || ak.language}</span>}
        <span style={{ marginLeft:"auto", display:"flex", gap: 6 }}>
          <button className="btn sm" onClick={onRun} disabled={running}
            style={{ opacity: running ? 0.5 : 1 }}>
            <AK_Icon name="sparkle" size={11}/>
            {ak ? " Regenerate" : ` Generate (${AK_LANG_LABEL[language] || language})`}
          </button>
          {ak && ak.status !== "approved" && (
            <button className="btn sm success" onClick={onApprove}>
              <AK_Icon name="check" size={11}/> Approve
            </button>
          )}
        </span>
      </div>
      <div style={{ color:"var(--ink-0)", fontSize: 13.5, lineHeight: 1.45,
        fontFamily:"'Hind Siliguri', system-ui, sans-serif" }}>
        {q.text}
      </div>
    </div>
  </div>
);

/* ---------- Running indicator ---------- */
const AK_RunningCard = ({ q, language }) => {
  const stages = [
    "Identifying scope & syllabus anchor",
    "Composing the perfect answer + flow",
    "Explaining the chemistry / logic per criterion",
    "Generating deduction-proof variants + rebuttals",
  ];
  // No real per-stage SSE yet — animate sequentially for honest UX.
  const [now] = useState(Date.now());
  const elapsed = (Date.now() - now) / 1000;
  return (
    <div style={{ padding: 18, background:"var(--bg-1)", border:"1px solid var(--line-strong)", borderRadius: 10 }}>
      <div style={{ display:"flex", alignItems:"center", gap: 8, marginBottom: 12 }}>
        <span style={{ width:14, height:14, border:"2px solid var(--line-strong)", borderTopColor:"var(--accent)", borderRadius:"50%", animation:"spin 1s linear infinite", display:"inline-block" }}/>
        <span style={{ color:"var(--ink-0)", fontWeight: 600, fontSize: 13 }}>Pipeline running for Q{q.num} ({AK_LANG_LABEL[language] || language})</span>
        <span className="mono" style={{ marginLeft:"auto", fontSize: 11, color:"var(--ink-3)" }}>~50s typical</span>
      </div>
      <div style={{ display:"flex", flexDirection:"column", gap: 5, fontSize: 12.5, color:"var(--ink-2)" }}>
        {stages.map((s, i) => (
          <div key={i}><span style={{ color:"var(--accent)" }}>›</span> {s}</div>
        ))}
      </div>
      <style>{`@keyframes spin { to { transform: rotate(360deg); } }
        @keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
      `}</style>
    </div>
  );
};

/* ---------- Lightweight Bengali-friendly markdown renderer ----------
   The pipeline prompts produce text like:
     - paragraphs separated by blank lines
     - **bold** spans
     - "* " bullet list rows
     - "1." / "১." numbered list rows
     - indented "    " continuation rows (inline reactions, etc.)
   We parse those into proper block elements so long answers flow naturally
   without `<pre>` quirks or fixed heights cutting content off. */
const AK_BN_NUM = /^([0-9]+|[০-৯]+)[.)]\s+/;
const AK_INLINE_BOLD = /(\*\*[^*]+\*\*)/g;

function ak_renderInline(s) {
  if (s == null) return null;
  const parts = String(s).split(AK_INLINE_BOLD);
  return parts.map((p, i) =>
    p.startsWith("**") && p.endsWith("**")
      ? <strong key={i} style={{ color: "var(--accent)" }}>{p.slice(2, -2)}</strong>
      : <React.Fragment key={i}>{p}</React.Fragment>
  );
}

function ak_renderMarkdown(text, opts = {}) {
  if (!text) return null;
  const className = opts.className || "bn";
  const baseStyle = {
    fontSize: opts.fontSize || 13.5,
    lineHeight: 1.6,
    color: opts.color || "var(--ink-0)",
    fontFamily: opts.font || "'Hind Siliguri', 'Source Serif 4', serif",
    overflowWrap: "anywhere",
    wordBreak: "break-word",
  };
  const lines = String(text).replace(/\r\n/g, "\n").split("\n");
  const blocks = [];
  let i = 0;
  let key = 0;
  while (i < lines.length) {
    const raw = lines[i];
    const trimmed = raw.trim();

    // Blank line → paragraph break (rendered as gap between blocks).
    if (!trimmed) { i++; continue; }

    // Bullet block — collect consecutive "* " (or "- ") items, treating
    // indented continuation lines as part of the previous item.
    if (/^[*\-]\s+/.test(trimmed)) {
      const items = [];
      while (i < lines.length) {
        const r = lines[i];
        const t = r.trim();
        if (/^[*\-]\s+/.test(t)) {
          items.push(t.replace(/^[*\-]\s+/, ""));
          i++;
        } else if (/^\s{2,}\S/.test(r) && items.length) {
          // Continuation of the last bullet (indented).
          items[items.length - 1] += "\n" + t;
          i++;
        } else {
          break;
        }
      }
      blocks.push(
        <ul key={key++} className={className} style={{ ...baseStyle, margin: "4px 0 10px", paddingLeft: 22 }}>
          {items.map((it, j) => (
            <li key={j} style={{ marginBottom: 4, whiteSpace: "pre-wrap" }}>
              {ak_renderInline(it)}
            </li>
          ))}
        </ul>
      );
      continue;
    }

    // Numbered block (English or Bengali digits).
    if (AK_BN_NUM.test(trimmed)) {
      const items = [];
      while (i < lines.length) {
        const r = lines[i];
        const t = r.trim();
        const m = t.match(AK_BN_NUM);
        if (m) {
          items.push(t.slice(m[0].length));
          i++;
        } else if (/^\s{2,}\S/.test(r) && items.length) {
          items[items.length - 1] += "\n" + t;
          i++;
        } else {
          break;
        }
      }
      blocks.push(
        <ol key={key++} className={className} style={{ ...baseStyle, margin: "4px 0 10px", paddingLeft: 22 }}>
          {items.map((it, j) => (
            <li key={j} style={{ marginBottom: 4, whiteSpace: "pre-wrap" }}>
              {ak_renderInline(it)}
            </li>
          ))}
        </ol>
      );
      continue;
    }

    // Plain paragraph — keep going until we hit a blank line or a list marker.
    const para = [trimmed];
    i++;
    while (i < lines.length) {
      const t = lines[i].trim();
      if (!t) break;
      if (/^[*\-]\s+/.test(t) || AK_BN_NUM.test(t)) break;
      para.push(t);
      i++;
    }
    blocks.push(
      <p key={key++} className={className}
         style={{ ...baseStyle, margin: "0 0 10px", whiteSpace: "pre-wrap" }}>
        {ak_renderInline(para.join("\n"))}
      </p>
    );
  }
  return blocks;
}

/* ---------- Card 1: Scope ---------- */
const AK_ScopeCard = ({ ak }) => {
  const s = ak.scope;
  if (!s) return null;
  return (
    <AkSection title="Scope & Syllabus" accent="var(--accent-2)" id="scope">
      <div style={{ marginBottom: 4 }}>{ak_renderMarkdown(s.scope_summary)}</div>
      <div style={{ display:"grid", gridTemplateColumns:"160px 1fr", gap: "4px 12px", marginBottom: 8 }}>
        <KV label="Syllabus anchor" value={s.syllabus_anchor}/>
        <KV label="Bloom level" value={s.bloom_level}/>
        <KV label="Scope tags" value={s.scope_keywords?.length
          ? s.scope_keywords.map((k, i) => <span key={i} className="pill mono" style={{ fontSize: 10, marginRight: 4 }}>{k}</span>)
          : "—"}/>
      </div>
      <ListBlock title="Sub-skills" items={s.sub_skills}/>
      <ListBlock title="Prerequisites" items={s.prerequisites}/>
      <ListBlock title="Common pitfalls" items={s.common_pitfalls} accent="amber"/>
    </AkSection>
  );
};

/* ---------- Card 2: Perfect answer + flow + key points ---------- */
const AK_PerfectCard = ({ ak, q }) => {
  const p = ak.perfect;
  if (!p) return null;
  return (
    <AkSection title="The Perfect Answer" accent="var(--accent)" id="perfect">
      <div className="bn" style={{
        margin:"0 0 12px", background:"var(--bg-2)", padding:"14px 16px",
        border:"1px solid var(--line)", borderLeft: "3px solid var(--accent)",
        borderRadius: 6, color:"var(--ink-0)",
        overflowWrap: "anywhere", wordBreak: "break-word",
      }}>
        {ak_renderMarkdown(p.perfect_answer, { fontSize: 14 })}
      </div>
      {p.answer_flow?.length > 0 && (
        <ListBlock title="Answer flow" items={p.answer_flow} numbered/>
      )}
      {p.diagram_hint && (
        <div style={{ padding:"8px 10px", background:"var(--amber-bg)", border:"1px solid rgba(246,181,59,0.3)", borderRadius: 6, marginBottom: 8, fontSize: 12, color:"var(--amber)" }}>
          ✎ {p.diagram_hint}
        </div>
      )}
      {p.key_points?.length > 0 && (
        <div>
          <div style={{ fontSize: 10.5, color:"var(--ink-3)", textTransform:"uppercase", letterSpacing:".08em", marginBottom: 5 }}>
            Key points · rubric mapping
          </div>
          <table className="cx-table compact">
            <thead>
              <tr>
                <th style={th}>Criterion</th>
                <th style={th}>What the answer says</th>
                <th style={{ ...th, width: 50, textAlign:"right" }}>m</th>
              </tr>
            </thead>
            <tbody>
              {p.key_points.map((kp, i) => (
                <tr key={i}>
                  <td style={tdMono}>{kp.criterion_id}</td>
                  <td style={td} className="bn">{kp.point}</td>
                  <td style={{ ...td, textAlign:"right" }} className="mono">{kp.mark_weight}</td>
                </tr>
              ))}
            </tbody>
          </table>
          <div style={{ fontSize: 10.5, color:"var(--ink-3)", marginTop: 4 }} className="mono">
            Σ {p.key_points.reduce((a, k) => a + Number(k.mark_weight || 0), 0)} / {q.maxMarks}
          </div>
        </div>
      )}
    </AkSection>
  );
};

/* ---------- Card 3: Logic behind perfection ---------- */
const AK_LogicCard = ({ ak }) => {
  const l = ak.logic;
  if (!l) return null;
  return (
    <AkSection title="Logic — why this answer is correct" accent="var(--green)" id="logic">
      <div style={{ marginBottom: 4 }}>{ak_renderMarkdown(l.logic_summary)}</div>
      {l.per_criterion_logic?.length > 0 && (
        <div style={{ display:"flex", flexDirection:"column", gap: 8, marginBottom: 12 }}>
          {l.per_criterion_logic.map((c, i) => (
            <div key={i} style={{ padding:"10px 12px", background:"var(--bg-2)", border:"1px solid var(--line)", borderRadius: 6 }}>
              <div style={{ display:"flex", gap: 8, alignItems:"center", marginBottom: 4 }}>
                <span className="mono pill" style={{ fontSize: 10 }}>{c.criterion_id}</span>
                <span className="bn" style={{ fontSize: 12, color:"var(--ink-2)" }}>{c.principle}</span>
                {c.evidence_in_answer && c.evidence_in_answer !== "(implied)" && (
                  <span style={{ marginLeft:"auto", fontSize: 10.5 }} className="mono muted">
                    cites: <span style={{ color:"var(--accent)" }}>“{c.evidence_in_answer}”</span>
                  </span>
                )}
              </div>
              <div style={{ fontSize: 13, color:"var(--ink-0)", lineHeight: 1.5 }}>
                {ak_renderMarkdown(c.reasoning, { fontSize: 13 })}
              </div>
            </div>
          ))}
        </div>
      )}
      {l.citations?.length > 0 && (
        <div>
          <div style={{ fontSize: 10.5, color:"var(--ink-3)", textTransform:"uppercase", letterSpacing:".08em", marginBottom: 5 }}>Citations</div>
          <div style={{ display:"flex", flexWrap:"wrap", gap: 6 }}>
            {l.citations.map((c, i) => (
              <span key={i} className="pill mono" style={{ fontSize: 10.5 }}>
                <b style={{ marginRight: 4, color:"var(--accent)" }}>{c.kind}</b>{c.label}{c.page ? ` · p.${c.page}` : ""}
              </span>
            ))}
          </div>
        </div>
      )}
      {l.alternative_correct_explanations?.length > 0 && (
        <ListBlock title="Alternative correct paths" items={l.alternative_correct_explanations}/>
      )}
    </AkSection>
  );
};

/* ---------- Card 4: Deduction proofing ---------- */
const AK_DeductionCard = ({ ak }) => {
  const d = ak.deduction_proof;
  if (!d) return null;
  return (
    <AkSection title="Deduction-proofing" accent="var(--violet)" id="proof">
      <div style={{ marginBottom: 4 }}>{ak_renderMarkdown(d.deduction_proof_summary)}</div>

      <AK_TwoCol>
        <Sub title="Accept also" color="var(--green)">
          {d.accept_also?.map((a, i) => (
            <div key={i} style={proofCard("var(--green)")}>
              <div style={{ display:"flex", alignItems:"baseline", gap: 6, marginBottom: 4 }}>
                <span style={{ color:"var(--green)", fontWeight: 600, fontSize: 13 }}>✓</span>
                <span style={{ flex: 1, color:"var(--ink-0)", fontSize: 12.5 }}>{a.variant}</span>
                <span className="mono pill" style={{ fontSize: 10, color:"var(--green)", borderColor:"rgba(75,201,123,0.3)" }}>{a.max_credit}m</span>
              </div>
              <div style={{ fontSize: 12, color:"var(--ink-2)", lineHeight: 1.45 }}>
                {ak_renderMarkdown(a.why_acceptable, { fontSize: 12, color: "var(--ink-2)" })}
              </div>
            </div>
          ))}
        </Sub>

        <Sub title="Reject also" color="var(--red)">
          {d.reject_also?.map((a, i) => (
            <div key={i} style={proofCard("var(--red)")}>
              <div style={{ display:"flex", alignItems:"baseline", gap: 6, marginBottom: 4 }}>
                <span style={{ color:"var(--red)", fontWeight: 600, fontSize: 13 }}>✕</span>
                <span style={{ flex: 1, color:"var(--ink-0)", fontSize: 12.5 }}>{a.variant}</span>
                {a.credit_if_partial > 0 && (
                  <span className="mono pill amber" style={{ fontSize: 10 }}>+{a.credit_if_partial}m</span>
                )}
              </div>
              <div style={{ fontSize: 12, color:"var(--ink-2)", lineHeight: 1.45 }}>
                {ak_renderMarkdown(a.why_rejected, { fontSize: 12, color: "var(--ink-2)" })}
              </div>
            </div>
          ))}
        </Sub>
      </AK_TwoCol>

      {d.marking_discontinuities?.length > 0 && (
        <Sub title="Marking discontinuities">
          {d.marking_discontinuities.map((m, i) => (
            <div key={i} style={proofCard("var(--amber)")}>
              <div style={{ display:"flex", alignItems:"baseline", gap: 6, marginBottom: 4 }}>
                <span className="bn" style={{ flex: 1, color:"var(--ink-0)", fontSize: 12.5 }}>{m.trigger}</span>
                <span className="mono pill" style={{ fontSize: 10, color:"var(--amber)", borderColor:"rgba(246,181,59,0.3)" }}>−{m.deduction}m</span>
              </div>
              <div style={{ fontSize: 12, color:"var(--ink-2)" }}>
                {ak_renderMarkdown(m.rationale, { fontSize: 12, color: "var(--ink-2)" })}
              </div>
            </div>
          ))}
        </Sub>
      )}

      {d.common_objections?.length > 0 && (
        <Sub title="Common objections + rebuttals">
          {d.common_objections.map((o, i) => (
            <div key={i} style={proofCard("var(--blue)")}>
              <div className="bn" style={{ fontSize: 12.5, color:"var(--ink-1)", marginBottom: 4, fontStyle:"italic", overflowWrap:"anywhere" }}>
                <span style={{ color:"var(--blue)", marginRight: 4 }}>›</span>“{o.objection}”
              </div>
              <div style={{ fontSize: 12.5, color:"var(--ink-0)", lineHeight: 1.5 }}>
                {ak_renderMarkdown(o.rebuttal, { fontSize: 12.5 })}
              </div>
            </div>
          ))}
        </Sub>
      )}

      {d.minimum_acceptable_representation && (
        <Sub title="Minimum acceptable representation">
          <div className="bn" style={{ padding:"8px 10px", background:"var(--bg-2)", border:"1px solid var(--line)", borderRadius: 6, fontSize: 12.5, color:"var(--ink-0)" }}>
            {d.minimum_acceptable_representation}
          </div>
        </Sub>
      )}

      {d.examiner_checklist?.length > 0 && (
        <Sub title="Examiner checklist">
          <ol style={{ margin: 0, paddingLeft: 18, color:"var(--ink-1)", fontSize: 12.5 }} className="bn">
            {d.examiner_checklist.map((c, i) => <li key={i} style={{ marginBottom: 3 }}>{c}</li>)}
          </ol>
        </Sub>
      )}
    </AkSection>
  );
};

/* ================================================================
   Layout primitives
   ================================================================ */
const AkSection = ({ title, accent, id, children }) => (
  <section id={id} style={{
    background:"var(--bg-1)", border:"1px solid var(--line)", borderRadius: 10,
    flexShrink: 0,           // never let the parent flex compress a card
  }}>
    <div style={{
      padding:"10px 14px", background:"var(--bg-2)",
      borderTopLeftRadius: 10, borderTopRightRadius: 10,
      borderBottom:"1px solid var(--line)",
      borderLeft: `3px solid ${accent || "var(--accent)"}`,
      color:"var(--ink-0)", fontWeight: 600, fontSize: 13,
      display:"flex", alignItems:"center", gap: 8,
    }}>
      <span>{title}</span>
    </div>
    <div style={{ padding: 14, overflowWrap: "anywhere", wordBreak: "break-word" }}>
      {children}
    </div>
  </section>
);

const Sub = ({ title, color, children }) => (
  <div style={{ marginTop: 10 }}>
    <div style={{ fontSize: 10.5, color: color || "var(--ink-3)", textTransform:"uppercase", letterSpacing:".08em", marginBottom: 6, fontWeight: 500 }}>
      {title}
    </div>
    <div style={{ display:"flex", flexDirection:"column", gap: 6 }}>{children}</div>
  </div>
);

const KV = ({ label, value }) => (
  <>
    <div style={{ fontSize: 11, color:"var(--ink-3)", textTransform:"uppercase", letterSpacing:".05em" }}>{label}</div>
    <div style={{ fontSize: 12.5, color:"var(--ink-1)" }} className="bn">{value || "—"}</div>
  </>
);

const ListBlock = ({ title, items, numbered, accent }) => {
  if (!items || items.length === 0) return null;
  const color = accent === "amber" ? "var(--amber)" : "var(--ink-3)";
  const Tag = numbered ? "ol" : "ul";
  return (
    <div style={{ marginBottom: 10 }}>
      <div style={{ fontSize: 10.5, color, textTransform:"uppercase", letterSpacing:".08em", marginBottom: 4 }}>{title}</div>
      <Tag className="bn" style={{ margin: 0, paddingLeft: 18, color:"var(--ink-1)", fontSize: 12.5 }}>
        {items.map((it, i) => <li key={i} style={{ marginBottom: 2 }}>{it}</li>)}
      </Tag>
    </div>
  );
};

const AK_TwoCol = ({ children }) => (
  <div style={{ display:"grid", gridTemplateColumns:"1fr 1fr", gap: 10 }}>{children}</div>
);

const th = { textAlign:"left", padding:"6px 8px", fontSize: 10.5, color:"var(--ink-3)", textTransform:"uppercase", letterSpacing:".06em", borderBottom:"1px solid var(--line)" };
const td = { padding:"6px 8px", fontSize: 12.5, color:"var(--ink-1)", borderBottom:"1px solid var(--line-soft)", verticalAlign:"top" };
const tdMono = { ...td, fontFamily:"'IBM Plex Mono', monospace", fontSize: 11, color:"var(--ink-2)", whiteSpace:"nowrap" };

const proofCard = (col) => ({
  padding:"8px 10px",
  background:"var(--bg-2)",
  border:"1px solid var(--line-soft)",
  borderLeft: `2px solid ${col}`,
  borderRadius: 4,
});

/* ================================================================
   Print view — flatten every answer-key onto one scrollable A4-styled column
   ================================================================ */
const AK_PrintView = ({ test, questions, keys }) => {
  const generated = questions.filter(q => keys[q.id]);
  return (
    <div style={{ flex: 1, overflow:"auto", padding: "16px 0", background:"#ece5d8" }}>
      {/* A4 on paper, but 210mm is 794px — on a phone that forced the whole
          layout viewport wider than the screen. Cap it for the screen; the
          @page rule in print CSS is what actually governs the printout. */}
      <div className="ak-a4" style={{ width: "210mm", maxWidth: "100%", margin:"0 auto", padding: "16mm 14mm",
        background:"#fdfbf6", color:"#1a1715",
        fontFamily:"'Source Serif 4', Georgia, serif", fontSize: "10.5pt", lineHeight: 1.5,
        boxShadow:"0 6px 22px rgba(0,0,0,.12)",
      }}>
        <div style={{ borderBottom:"2pt solid #1a1715", paddingBottom:"6mm", marginBottom:"6mm" }}>
          <div style={{ fontSize: 8.5, letterSpacing: ".18em", textTransform:"uppercase", color:"#807870" }}>
            Model Answer Key · v1 · {new Date().toISOString().slice(0, 10)}
          </div>
          <h1 style={{ margin: "2mm 0 0", fontSize: 20, fontFamily:"'Source Serif 4', serif", fontWeight: 600 }}>
            {test.title}
            <span style={{ fontSize: 11, color:"#807870", display:"block", marginTop: 2, fontWeight: 400 }}>
              {test.id} · {test.subject || "—"} · {test.className || "—"} · {generated.length} / {questions.length} questions
            </span>
          </h1>
        </div>

        {generated.length === 0 && (
          <p style={{ color:"#807870" }}>No answer keys generated yet. Go back, click <b>Generate missing</b>, then return here.</p>
        )}

        {generated.map(q => {
          const ak = keys[q.id];
          return (
            <article key={q.id} style={{ borderTop:"0.5pt solid #d5cfc7", padding:"4mm 0 6mm", pageBreakInside:"avoid" }}>
              <h2 style={{ margin:"0 0 2mm", fontSize: 13, fontWeight: 600 }}>
                <span style={{ color:"#b85c1c", fontFamily:"'JetBrains Mono', monospace", marginRight: 6 }}>Q{q.num}</span>
                <span style={{ fontSize: 9, color:"#807870" }}>· {q.type} · {q.maxMarks} m · {ak.status}</span>
              </h2>
              <p style={{ margin:"0 0 3mm", fontFamily:"'Hind Siliguri', serif", fontSize: 11.5, color:"#1a1715" }}>{q.text}</p>

              {ak.scope && (
                <div style={{ margin:"2mm 0" }}>
                  <PrintLabel>Scope</PrintLabel>
                  <div className="bn" style={{ fontSize: 10.5 }}>{ak.scope.scope_summary}</div>
                  <div style={{ fontSize: 9, color:"#807870", marginTop: 1 }}>{ak.scope.syllabus_anchor}</div>
                </div>
              )}

              {ak.perfect && (
                <div style={{ margin:"2mm 0" }}>
                  <PrintLabel>Perfect answer</PrintLabel>
                  <div className="bn" style={{ fontSize: 11, whiteSpace:"pre-wrap", background:"#f2ebdd", padding:"2mm 3mm", borderLeft:"2pt solid #b85c1c" }}>
                    {ak.perfect.perfect_answer}
                  </div>
                </div>
              )}

              {ak.logic?.logic_summary && (
                <div style={{ margin:"2mm 0" }}>
                  <PrintLabel>Logic</PrintLabel>
                  <div className="bn" style={{ fontSize: 10.5 }}>{ak.logic.logic_summary}</div>
                </div>
              )}

              {ak.deduction_proof?.deduction_proof_summary && (
                <div style={{ margin:"2mm 0" }}>
                  <PrintLabel>Deduction-proofing</PrintLabel>
                  <div className="bn" style={{ fontSize: 10.5 }}>{ak.deduction_proof.deduction_proof_summary}</div>
                </div>
              )}
            </article>
          );
        })}
      </div>
    </div>
  );
};

const PrintLabel = ({ children }) => (
  <div style={{ fontSize: 8.2, color:"#807870", letterSpacing:".12em", textTransform:"uppercase", margin:"0 0 1mm" }}>
    {children}
  </div>
);

window.AnswerKeyAuthoring = AnswerKeyAuthoring;
