// Surface 6 — Assessments
//
// Question groups follow the course's two-level hierarchy:
//   course.moduleGroups[]  → collapsible SECTION headers (coloured swatch)
//     course.modules[] (filtered by module.group) → one question GROUP each
//       questions[] → editable question rows
// Starter questions come from ASSESSMENT_SEED (data.jsx), keyed by module id
// so a module rename never breaks the lookup. See video-media-patterns.md §28.

function SurfaceAssessments({ course, onNavigate }) {
  const [tab, setTab] = React.useState('pre');
  const primary = course?.defaultLanguage || 'en';
  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', background: 'var(--bg)' }}
         data-screen-label="Surface 6 · Assessments">

      {/* Header — title left, assessment switcher right. */}
      <div style={{
        display: 'flex', alignItems: 'center', gap: 16,
        padding: '0 24px', height: 52, flex: '0 0 auto',
        borderBottom: '1px solid var(--border)',
        background: 'var(--surface)',
      }}>
        <h2 style={{ margin: 0, fontSize: 14, fontWeight: 600, whiteSpace: 'nowrap' }}>Assessments</h2>
        <div style={{ flex: 1 }} />
        <div style={{
          display: 'flex', gap: 2, padding: 3,
          background: 'var(--surface-inset)', borderRadius: 'var(--radius)',
          border: '1px solid var(--border)',
        }}>
          <TabBtn active={tab === 'pre'} onClick={() => setTab('pre')}>Pre-assessment</TabBtn>
          <TabBtn active={tab === 'post'} onClick={() => setTab('post')}>Post-assessment</TabBtn>
        </div>
      </div>

      <div style={{ flex: 1, overflowY: 'auto', padding: '20px 24px 32px',
        display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) 340px', gap: 20 }}>

        {/* Main column */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 18 }}>
          <AssessmentConfig />
          {/* Remounts per tab so each assessment owns an independent set. */}
          <QuestionGroups key={tab} which={tab} course={course} />
        </div>

        {/* Right sidebar */}
        <aside style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
          <ResultScreenEditor key={tab} which={tab} primary={primary} onNavigate={onNavigate} />
        </aside>
      </div>
    </div>
  );
}

function TabBtn({ active, children, onClick }) {
  return (
    <button onClick={onClick} className="focusable"
      style={{
        display: 'inline-flex', alignItems: 'center', gap: 6,
        padding: '5px 12px', border: 0, borderRadius: 4,
        flex: '0 0 auto', whiteSpace: 'nowrap',
        background: active ? 'var(--surface)' : 'transparent',
        color: active ? 'var(--text)' : 'var(--text-muted)',
        fontSize: 12.5, fontWeight: active ? 600 : 500,
        fontFamily: 'inherit', cursor: 'default',
        boxShadow: active ? 'var(--shadow-sm)' : 'none',
      }}>
      {children}
    </button>
  );
}

// ── Generic centred modal — blur backdrop + rounded card. ────────────────────
function Modal({ title, subtitle, icon, width = 520, onClose, children, footer }) {
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') onClose?.(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [onClose]);
  const Icon = icon ? (I[icon] || I.Image) : null;
  return (
    <>
      <div onClick={onClose} style={{
        position: 'fixed', inset: 0, background: 'rgba(15,23,42,0.4)',
        backdropFilter: 'blur(2px)', zIndex: 60,
      }} />
      <div style={{
        position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)',
        width: `min(${width}px, 94vw)`, maxHeight: '88vh', zIndex: 61,
        background: 'var(--surface)', border: '1px solid var(--border)',
        borderRadius: 'var(--radius-lg)', boxShadow: 'var(--shadow-xl)',
        display: 'flex', flexDirection: 'column', overflow: 'hidden',
      }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10,
          padding: '14px 16px', borderBottom: '1px solid var(--border)' }}>
          {Icon && <div style={{
            width: 28, height: 28, borderRadius: '50%', background: 'var(--accent-bg)',
            display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
            color: 'var(--accent-text)', flexShrink: 0,
          }}><Icon size={14} /></div>}
          <div style={{ flex: 1, minWidth: 0 }}>
            <h3 style={{ margin: 0, fontSize: 14, fontWeight: 600 }}>{title}</h3>
            {subtitle && <p style={{ margin: '2px 0 0', fontSize: 12, color: 'var(--text-muted)' }}>{subtitle}</p>}
          </div>
          <button className="btn sm ghost" onClick={onClose} title="Close"><I.X size={14} /></button>
        </div>
        <div style={{ padding: 16, overflowY: 'auto' }}>{children}</div>
        {footer && <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end',
          padding: '12px 16px', borderTop: '1px solid var(--border)', background: 'var(--surface-2)' }}>
          {footer}
        </div>}
      </div>
    </>
  );
}

// ── Toggle — small switch with an onChange + tooltip (unlike the bare Switch,
// ── which owns its own state). Used for "Randomise groups".
// Named `AssessmentToggle` (not `Toggle`) to avoid clobbering the canonical
// controlled `Toggle` in field-widgets.jsx — both files share global scope, so
// a bare `function Toggle` here shadowed the value-based one the layout editors
// rely on (e.g. quiz_gaming Scoring), making those switches render stuck-off.
function AssessmentToggle({ on, onChange, title }) {
  return (
    <button type="button" title={title} aria-pressed={on} className="focusable"
      onClick={() => onChange(!on)}
      style={{ width: 38, height: 22, borderRadius: 999, border: 0, padding: 0,
        cursor: 'default', flex: '0 0 auto', position: 'relative',
        background: on ? 'var(--accent)' : 'var(--border-strong)',
        transition: 'background 150ms' }}>
      <span style={{ position: 'absolute', top: 2, left: on ? 18 : 2,
        width: 18, height: 18, borderRadius: '50%', background: '#fff',
        boxShadow: 'var(--shadow-sm)', transition: 'left 150ms' }} />
    </button>
  );
}

function AssessmentConfig() {
  const [mode, setMode] = React.useState('threshold');
  const [passMark, setPassMark] = React.useState(70);
  const [randomise, setRandomise] = React.useState(true);
  // Pass mark only governs a Threshold assessment.
  const thresholdActive = mode === 'threshold';
  const passMarks = [50, 55, 60, 65, 70, 75, 80, 85, 90, 95, 100];

  return (
    <div className="card" style={{ padding: 16 }}>
      <h3 style={{ margin: '0 0 14px', fontSize: 13, fontWeight: 600 }}>Configuration</h3>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 20 }}>
        <ConfigField label="Mode">
          <select className="field select-elegant" style={{ width: '100%' }}
            value={mode} onChange={e => setMode(e.target.value)}>
            <option value="completion">Completion</option>
            <option value="threshold">Threshold</option>
          </select>
        </ConfigField>

        <ConfigField label="Pass mark"
          hint={thresholdActive ? undefined : 'Threshold mode only'}>
          <select className="field select-elegant" style={{ width: '100%' }}
            value={passMark} disabled={!thresholdActive}
            onChange={e => setPassMark(Number(e.target.value))}>
            {passMarks.map(m => <option key={m} value={m}>{m}%</option>)}
          </select>
        </ConfigField>

        <ConfigField label="Randomise groups">
          <div style={{ display: 'flex', alignItems: 'center', gap: 9 }}>
            <AssessmentToggle on={randomise} onChange={setRandomise}
              title="Shuffles the order in which question groups appear. Doesn't change which questions are picked from each group — see 'Show K of N' on each group for that." />
            <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>{randomise ? 'On' : 'Off'}</span>
          </div>
        </ConfigField>

        <ConfigField label="Background image">
          <BackgroundImageField defaultFilename="assessment-bg-blurred.jpg" />
        </ConfigField>
      </div>
    </div>
  );
}

// ── BackgroundImageField — compact trigger that opens the shared
// ── BackgroundPicker (upload / AI / solid colour) in a modal.
function BackgroundImageField({ defaultFilename }) {
  const [open, setOpen] = React.useState(false);
  const [filename, setFilename] = React.useState(defaultFilename || '');
  const [color, setColor] = React.useState('#0f172a');
  const [mode, setMode] = React.useState(defaultFilename ? 'upload' : 'color');
  const draft = React.useRef({ filename, color, mode });

  const label = mode === 'color' ? color : (filename || 'No image');
  const Glyph = mode === 'color' ? I.Droplet : I.Image;

  return (
    <>
      <button className="btn sm" onClick={() => setOpen(true)}
        style={{ width: '100%', justifyContent: 'flex-start', overflow: 'hidden' }}>
        <Glyph size={12} style={{ flex: '0 0 auto' }} />
        <span className="truncate" style={mode === 'color' ? { fontFamily: 'var(--font-mono)' } : undefined}>
          {label}
        </span>
      </button>
      {open && (
        <Modal title="Background image" icon="Image" width={520}
          subtitle="Shown behind the assessment intro and result screens."
          onClose={() => setOpen(false)}
          footer={<>
            <button className="btn sm ghost" onClick={() => setOpen(false)}>Cancel</button>
            <button className="btn sm primary" onClick={() => {
              setFilename(draft.current.filename);
              setColor(draft.current.color);
              setMode(draft.current.mode);
              setOpen(false);
            }}>Done</button>
          </>}>
          <BackgroundPicker
            defaultFilename={mode !== 'color' ? filename : undefined}
            defaultColor={color}
            defaultMode={mode}
            onModeChange={v => { draft.current.mode = v; }}
            onImageChange={v => { draft.current.filename = v; }}
            onColorChange={v => { draft.current.color = v; }} />
        </Modal>
      )}
    </>
  );
}

function ConfigField({ label, hint, children }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 }}>
      <div style={{
        display: 'flex', alignItems: 'baseline', gap: 6,
        fontSize: 11, color: 'var(--text-muted)',
      }}>
        <span style={{ whiteSpace: 'nowrap' }}>{label}</span>
        {hint && <span style={{ fontSize: 10, color: 'var(--text-faint)',
          whiteSpace: 'nowrap' }}>· {hint}</span>}
      </div>
      <div style={{ height: 30, display: 'flex', alignItems: 'center', minWidth: 0 }}>
        {children}
      </div>
    </div>
  );
}

// ── Seed + mock helpers ──────────────────────────────────────────────────────
let _qid = 0;
const newId = (p) => `${p}-${Date.now().toString(36)}-${_qid++}`;
const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
const defaultAnswers = () => ([
  { id: 'a1', text: '', isCorrect: true, feedback: '' },
  { id: 'a2', text: '', isCorrect: false, feedback: '' },
]);
const cloneAnswers = (arr) => (arr || []).map(a => ({ ...a }));

// Mock AI question generation — same pattern as AiGenerateBar's mocked handler
// (setTimeout in the caller); the real pipeline lands later. Every generated
// question carries aiDrafted:true so the per-question pill renders.
function mockQuestions(label, count) {
  const stems = [
    `Which statement best reflects “${label}”?`,
    `In “${label}”, what should you do first?`,
    `Identify the most effective approach covered in “${label}”.`,
    `What is a common mistake related to “${label}”?`,
    `Pick the response that applies “${label}” correctly.`,
    `Which option is NOT part of “${label}”?`,
  ];
  return Array.from({ length: count }, (_, i) => ({
    id: newId('q'), aiDrafted: true, verified: false,
    prompt: stems[i % stems.length],
    answers: [
      { id: 'a1', text: 'The approach taught in this module.', isCorrect: true,
        feedback: 'Correct — this matches the module guidance.' },
      { id: 'a2', text: 'A plausible but incomplete option.', isCorrect: false,
        feedback: 'Close, but it misses a key step.' },
      { id: 'a3', text: 'A common misconception.', isCorrect: false,
        feedback: 'This is a frequent trap — revisit the module.' },
    ],
  }));
}

function makeGroup(which, m, open, { empty = false } = {}) {
  const seed = (!empty && window.ASSESSMENT_SEED && window.ASSESSMENT_SEED[m.id]) || null;
  const src = seed || (empty ? [] : [{ prompt: '', verified: false, answers: defaultAnswers() }]);
  const questions = src.map((q, qi) => ({
    id: `${which}-${m.id}-q${qi}-${_qid++}`,
    prompt: q.prompt, verified: !!q.verified, aiDrafted: !empty,
    answers: cloneAnswers(q.answers),
  }));
  return { id: `${which}-${m.id}-${_qid++}`, mod: m.id, label: locText(m.title),
    aiDrafted: !empty && questions.length > 0, open: !!open,
    surfaceCount: Math.max(1, questions.length), questions };
}

// Build the section → group hierarchy from the course skeleton.
function seedSections(which, course) {
  const moduleGroups = (course && course.moduleGroups) || [];
  const modules = (course && course.modules) || [];
  return moduleGroups.map((mg, gi) => ({
    id: `${which}-${mg.id}`, label: locText(mg.title), color: mg.color || 'var(--text-faint)',
    collapsed: gi !== 0,
    groups: modules.filter(m => m.group === mg.id)
      .map((m, mi) => makeGroup(which, m, gi === 0 && mi === 0)),
  }));
}

function QuestionGroups({ which, course }) {
  const modules = (course && course.modules) || [];
  const [sections, setSections] = React.useState(() => seedSections(which, course));
  const [confirm, setConfirm] = React.useState(null);
  // The AI panel's count is the default for every per-group Generate, too.
  const [genCount, setGenCount] = React.useState(3);
  const [genScope, setGenScope] = React.useState('module');
  const [genModule, setGenModule] = React.useState(modules[0]?.id || '');

  const allGroups = sections.flatMap(s => s.groups);
  const boundMods = new Set(allGroups.map(g => g.mod));
  const unbound = modules.filter(m => !boundMods.has(m.id));

  // ── Section / group / question mutations ──────────────────────────────────
  const patchSection = (sid, patch) =>
    setSections(ss => ss.map(s => s.id === sid ? { ...s, ...patch } : s));
  const mapGroup = (gid, fn) =>
    setSections(ss => ss.map(s => ({ ...s, groups: s.groups.map(g => g.id === gid ? fn(g) : g) })));
  const patchGroup = (gid, patch) => mapGroup(gid, g => ({ ...g, ...patch }));
  const removeGroup = (gid) =>
    setSections(ss => ss.map(s => ({ ...s, groups: s.groups.filter(g => g.id !== gid) })));
  const patchGroupQuestions = (gid, fn) => mapGroup(gid, g => ({ ...g, questions: fn(g.questions) }));

  const addGroupForModule = (modId) => {
    const m = modId ? modules.find(x => x.id === modId) : modules.find(x => !boundMods.has(x.id));
    if (!m) return;
    const g = makeGroup(which, m, true, { empty: true });
    setSections(ss => ss.map(s => s.id === `${which}-${m.group}`
      ? { ...s, collapsed: false, groups: [...s.groups, g] } : s));
  };

  const addQuestion = (gid) => patchGroupQuestions(gid, qs => [...qs,
    { id: newId('q'), prompt: '', verified: false, aiDrafted: false,
      answers: defaultAnswers(), expanded: true }]);
  const patchQuestion = (gid, qid, patch) =>
    patchGroupQuestions(gid, qs => qs.map(q => q.id === qid ? { ...q, ...patch } : q));
  const removeQuestion = (gid, qid) =>
    patchGroupQuestions(gid, qs => qs.filter(q => q.id !== qid));

  // ── AI generation ─────────────────────────────────────────────────────────
  const generateGroup = (gid, count, mode) => mapGroup(gid, g => {
    const fresh = mockQuestions(g.label, count);
    const questions = mode === 'append' ? [...g.questions, ...fresh] : fresh;
    return { ...g, aiDrafted: true, questions, surfaceCount: Math.max(1, questions.length) };
  });
  const generateAll = (count) => setSections(ss => ss.map(s => ({ ...s,
    groups: s.groups.map(g => {
      const fresh = mockQuestions(g.label, count);
      return { ...g, aiDrafted: true, questions: fresh, surfaceCount: Math.max(1, fresh.length) };
    }) })));
  const generateModule = (modId, count) => setSections(ss => ss.map(s => ({ ...s,
    groups: s.groups.map(g => {
      if (g.mod !== modId) return g;
      const fresh = mockQuestions(g.label, count);
      return { ...g, aiDrafted: true, questions: fresh, surfaceCount: Math.max(1, fresh.length) };
    }) })));

  const doConfirm = () => {
    if (!confirm) return;
    if (confirm.kind === 'group') removeGroup(confirm.gid);
    if (confirm.kind === 'question') removeQuestion(confirm.gid, confirm.qid);
    setConfirm(null);
  };

  return (
    <div>
      <AiQuestionPanel
        modules={modules} count={genCount} setCount={setGenCount}
        scope={genScope} setScope={setGenScope}
        moduleId={genModule} setModuleId={setGenModule}
        onGenerate={() => { genScope === 'all' ? generateAll(genCount) : generateModule(genModule, genCount); }} />

      {unbound.length > 0 && (
        <UnboundModulesError modules={unbound} onAdd={addGroupForModule} />
      )}

      <div style={{ display: 'flex', alignItems: 'center', gap: 8, margin: '4px 0 10px' }}>
        <h3 style={{ margin: 0, fontSize: 13, fontWeight: 600, whiteSpace: 'nowrap', flex: '0 0 auto' }}>Question groups</h3>
        <span style={{ fontSize: 11, color: 'var(--text-faint)',
          fontFamily: 'var(--font-mono)', background: 'var(--surface-inset)',
          padding: '2px 6px', borderRadius: 4, flex: '0 0 auto' }}>{allGroups.length}</span>
        <div style={{ flex: 1, height: 1, background: 'var(--border)' }} />
        {unbound.length > 0 && (
          <button className="btn sm" style={{ flex: '0 0 auto' }} onClick={() => addGroupForModule()}>
            <I.Plus size={12} />Add group
          </button>
        )}
      </div>

      <div style={{ display: 'grid', gap: 14 }}>
        {sections.map(section => (
          <SectionBlock key={section.id} section={section}
            onToggle={() => patchSection(section.id, { collapsed: !section.collapsed })}
            defaultGenCount={genCount}
            onPatchGroup={patchGroup}
            onDeleteGroup={(g) => setConfirm({ kind: 'group', gid: g.id, label: g.label, count: g.questions.length })}
            onAddQuestion={addQuestion}
            onPatchQuestion={patchQuestion}
            onDeleteQuestion={(gid, qid) => setConfirm({ kind: 'question', gid, qid })}
            onGenerateGroup={generateGroup} />
        ))}
      </div>

      {confirm && confirm.kind === 'group' && (
        <ConfirmDialog
          title={`Delete "${confirm.label}"?`}
          body={confirm.count > 0
            ? `This also removes ${confirm.count} question${confirm.count === 1 ? '' : 's'} in the group. This can't be undone.`
            : "This can't be undone."}
          confirmLabel="Delete group"
          onConfirm={doConfirm} onCancel={() => setConfirm(null)} />
      )}
      {confirm && confirm.kind === 'question' && (
        <ConfirmDialog
          title="Delete this question?"
          body="The question and its answers will be removed. This can't be undone."
          confirmLabel="Delete question"
          onConfirm={doConfirm} onCancel={() => setConfirm(null)} />
      )}
    </div>
  );
}

// ── AI question generation panel ─────────────────────────────────────────────
function AiQuestionPanel({ modules, count, setCount, scope, setScope, moduleId, setModuleId, onGenerate }) {
  const [busy, setBusy] = React.useState(false);
  const fire = () => {
    if (busy) return;
    setBusy(true);
    setTimeout(() => { onGenerate(); setBusy(false); }, 700);
  };
  return (
    <div className="card" style={{ padding: 14, marginBottom: 16,
      display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap',
      background: 'var(--ai-bg)', borderColor: 'var(--accent-border)' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 9, flex: '0 0 auto' }}>
        <div style={{ width: 28, height: 28, borderRadius: '50%', flex: '0 0 auto',
          display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
          background: 'var(--surface)', color: 'var(--ai-text)', border: '1px solid var(--accent-border)' }}>
          <I.Sparkle size={14} />
        </div>
        <div>
          <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>Generate questions</div>
          <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>Draft assessment questions from the module content.</div>
        </div>
      </div>
      <div style={{ flex: 1 }} />
      <label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11.5, color: 'var(--text-muted)' }}>
        Scope
        <select className="field select-elegant" value={scope} onChange={e => setScope(e.target.value)}
          style={{ height: 30, fontSize: 12.5, paddingRight: 26 }}>
          <option value="module">This module</option>
          <option value="all">All modules</option>
        </select>
      </label>
      {scope === 'module' && (
        <select className="field select-elegant" value={moduleId} onChange={e => setModuleId(e.target.value)}
          style={{ height: 30, fontSize: 12.5, maxWidth: 220, paddingRight: 26 }}>
          {modules.map(m => <option key={m.id} value={m.id}>{m.id} · {locText(m.title)}</option>)}
        </select>
      )}
      <label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11.5, color: 'var(--text-muted)' }}>
        Per module
        <input type="number" className="field" min={1} max={10} value={count}
          onChange={e => setCount(clamp(parseInt(e.target.value, 10) || 1, 1, 10))}
          style={{ width: 56, height: 30, fontSize: 12.5, textAlign: 'center' }} />
      </label>
      <button className="btn sm primary" onClick={fire} disabled={busy} style={{ flex: '0 0 auto' }}>
        <I.Sparkle size={12} />{busy ? 'Generating…' : 'Generate questions'}
      </button>
    </div>
  );
}

// ── Unbound-module validation banner — dynamic, no hardcoded module ids. ─────
function UnboundModulesError({ modules, onAdd }) {
  return (
    <div style={{
      padding: 12, marginBottom: 16, background: 'var(--error-bg)',
      border: '1px solid var(--error)', borderRadius: 'var(--radius-md)',
      display: 'flex', alignItems: 'flex-start', gap: 10,
    }}>
      <I.AlertCircle size={16} style={{ color: 'var(--error-text)', marginTop: 1 }} />
      <div style={{ flex: 1 }}>
        <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--error-text)', marginBottom: 4 }}>
          {modules.length === 1
            ? `Module ${modules[0].id} has no question group bound`
            : `${modules.length} modules have no question group bound`}
        </div>
        <p style={{ margin: '0 0 8px', fontSize: 12, color: 'var(--error-text)', lineHeight: 1.5 }}>
          Each included module needs one question group, otherwise its content goes unassessed — this blocks export.
        </p>
        <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
          {modules.map(m => (
            <button key={m.id} className="btn sm primary" style={{ height: 26 }}
              onClick={() => onAdd(m.id)}>
              <I.Plus size={11} />Add {m.id} group
              <span style={{ opacity: 0.85, fontWeight: 400 }}>· {locText(m.title)}</span>
            </button>
          ))}
        </div>
      </div>
    </div>
  );
}

// ── Section block — collapsible module-group header + its question groups. ───
function SectionBlock({ section, onToggle, defaultGenCount, onPatchGroup, onDeleteGroup,
  onAddQuestion, onPatchQuestion, onDeleteQuestion, onGenerateGroup }) {
  const { collapsed } = section;
  return (
    <div>
      <button onClick={onToggle}
        style={{ display: 'flex', alignItems: 'center', gap: 10, width: '100%',
          padding: '8px 12px', border: '1px solid var(--border)',
          borderRadius: collapsed ? 'var(--radius-md)' : 'var(--radius-md) var(--radius-md) 0 0',
          background: 'var(--surface-2)', cursor: 'default',
          fontFamily: 'inherit', textAlign: 'left', color: 'var(--text)' }}>
        <span style={{ width: 10, height: 10, borderRadius: 3, flex: '0 0 auto',
          background: section.color }} />
        <span style={{ fontSize: 12.5, fontWeight: 700, letterSpacing: '.01em', minWidth: 0 }} className="truncate">
          {section.label}
        </span>
        <span style={{ fontSize: 11, color: 'var(--text-faint)', flex: '0 0 auto' }}>
          {section.groups.length} module{section.groups.length === 1 ? '' : 's'}
        </span>
        <div style={{ flex: 1 }} />
        {collapsed ? <I.ChevronDown size={14} style={{ color: 'var(--text-muted)' }} />
                   : <I.ChevronUp size={14} style={{ color: 'var(--text-muted)' }} />}
      </button>

      {!collapsed && (
        <div style={{ border: '1px solid var(--border)', borderTop: 0,
          borderRadius: '0 0 var(--radius-md) var(--radius-md)',
          padding: 10, display: 'grid', gap: 8, background: 'var(--bg)' }}>
          {section.groups.length === 0 ? (
            <div style={{ fontSize: 12, color: 'var(--text-faint)', padding: '8px 4px' }}>
              No modules yet.
            </div>
          ) : section.groups.map(g => (
            <QuestionGroupCard key={g.id} group={g} defaultGenCount={defaultGenCount}
              onPatch={patch => onPatchGroup(g.id, patch)}
              onDelete={() => onDeleteGroup(g)}
              onAddQuestion={() => onAddQuestion(g.id)}
              onPatchQuestion={(qid, patch) => onPatchQuestion(g.id, qid, patch)}
              onDeleteQuestion={(qid) => onDeleteQuestion(g.id, qid)}
              onGenerate={(count, mode) => onGenerateGroup(g.id, count, mode)} />
          ))}
        </div>
      )}
    </div>
  );
}

function QuestionGroupCard({ group, defaultGenCount, onDelete, onAddQuestion, onPatch,
  onPatchQuestion, onDeleteQuestion, onGenerate }) {
  const open = group.open;
  const N = group.questions.length;
  return (
    <div className="card">
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, width: '100%', padding: '10px 12px' }}>
        <I.GripVertical size={13} style={{ color: 'var(--text-faint)', flex: '0 0 auto', cursor: 'grab' }} />
        <button onClick={() => onPatch({ open: !open })}
          style={{ display: 'flex', alignItems: 'center', gap: 10, flex: 1, minWidth: 0,
            border: 0, background: 'transparent', cursor: 'default',
            fontFamily: 'inherit', color: 'var(--text)', textAlign: 'left', padding: 0 }}>
          <span style={{
            fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
            padding: '2px 6px', borderRadius: 4, background: 'var(--surface-inset)',
            color: 'var(--text-muted)', flex: '0 0 auto',
          }}>{group.mod}</span>
          <span className="truncate" style={{ fontSize: 13, fontWeight: 600, minWidth: 0 }}>{group.label}</span>
          <span style={{ fontSize: 11.5, color: 'var(--text-muted)', whiteSpace: 'nowrap', flex: '0 0 auto' }}>
            {N} question{N === 1 ? '' : 's'}
          </span>
        </button>
        <GroupGenerateButton defaultCount={defaultGenCount} onGenerate={onGenerate} />
        <div style={{ display: 'flex', gap: 6, flex: '0 0 auto' }}>
          <button className="btn sm danger" title="Delete group" onClick={onDelete}
            style={{ width: 28, height: 28, padding: 0, justifyContent: 'center' }}>
            <I.Trash size={13} />
          </button>
          <button className="btn sm ghost" title={open ? 'Collapse' : 'Expand'}
            onClick={() => onPatch({ open: !open })}
            style={{ width: 28, height: 28, padding: 0, justifyContent: 'center' }}>
            {open ? <I.ChevronUp size={14} /> : <I.ChevronDown size={14} />}
          </button>
        </div>
      </div>

      {open && (
        <div style={{ padding: '14px', borderTop: '1px solid var(--border)', display: 'grid', gap: 10 }}>
          <SurfaceCountField surfaceCount={group.surfaceCount} total={N}
            onChange={v => onPatch({ surfaceCount: v })} />
          {N === 0 && (
            <div style={{ fontSize: 12, color: 'var(--text-faint)' }}>
              No questions in this group yet.
            </div>
          )}
          {group.questions.map((q, i) => (
            <QuestionRow key={q.id} num={i + 1} question={q}
              onPatch={patch => onPatchQuestion(q.id, patch)}
              onDelete={() => onDeleteQuestion(q.id)} />
          ))}
          <button className="btn sm ghost" style={{ alignSelf: 'flex-start' }}
            onClick={onAddQuestion}>
            <I.Plus size={11} />Add question
          </button>
        </div>
      )}
    </div>
  );
}

// ── Per-group Generate — popover with count + replace/append. ────────────────
function GroupGenerateButton({ defaultCount, onGenerate }) {
  const [open, setOpen] = React.useState(false);
  const [count, setCount] = React.useState(defaultCount);
  React.useEffect(() => { if (!open) setCount(defaultCount); }, [defaultCount, open]);
  const run = (mode) => { onGenerate(count, mode); setOpen(false); };
  return (
    <div style={{ position: 'relative', flex: '0 0 auto' }}>
      <button className="btn sm ghost" title="Generate questions for this module"
        onClick={() => setOpen(o => !o)} style={{ color: 'var(--ai-text)' }}>
        <I.Sparkle size={12} />Generate
      </button>
      {open && (
        <>
          <div onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 40 }} />
          <div style={{ position: 'absolute', top: 'calc(100% + 6px)', right: 0, zIndex: 41,
            width: 230, background: 'var(--surface)', border: '1px solid var(--border)',
            borderRadius: 'var(--radius-md)', boxShadow: 'var(--shadow-lg)', padding: 12 }}>
            <div style={{ fontSize: 12, fontWeight: 600, marginBottom: 8 }}>Generate questions</div>
            <label style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
              gap: 8, fontSize: 11.5, color: 'var(--text-muted)', marginBottom: 10 }}>
              How many
              <input type="number" className="field" min={1} max={10} value={count}
                onChange={e => setCount(clamp(parseInt(e.target.value, 10) || 1, 1, 10))}
                style={{ width: 56, height: 28, fontSize: 12.5, textAlign: 'center' }} />
            </label>
            <div style={{ display: 'flex', gap: 6 }}>
              <button className="btn sm primary" style={{ flex: 1, justifyContent: 'center' }}
                onClick={() => run('replace')}>Replace</button>
              <button className="btn sm" style={{ flex: 1, justifyContent: 'center' }}
                onClick={() => run('append')}>Append</button>
            </div>
            <p style={{ margin: '8px 0 0', fontSize: 10.5, color: 'var(--text-faint)', lineHeight: 1.4 }}>
              Replace overwrites the current questions; Append adds to them.
            </p>
          </div>
        </>
      )}
    </div>
  );
}

// ── Show K of N — surfaceCount editor. ───────────────────────────────────────
function SurfaceCountField({ surfaceCount, total, onChange }) {
  const max = Math.max(1, total);
  const val = clamp(surfaceCount ?? max, 1, max);
  return (
    <div style={{ padding: '10px 12px', background: 'var(--surface-inset)',
      borderRadius: 'var(--radius)', display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, flex: '0 0 auto' }}>
        <span style={{ fontSize: 12, fontWeight: 600 }}>Show</span>
        <input type="number" className="field" min={1} max={max} value={val}
          onChange={e => onChange(clamp(parseInt(e.target.value, 10) || 1, 1, max))}
          style={{ width: 56, height: 28, fontSize: 12.5, textAlign: 'center' }} />
        <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>of {total}</span>
      </div>
      <span style={{ fontSize: 11, color: 'var(--text-faint)', flex: 1, minWidth: 160, lineHeight: 1.4 }}>
        {val} of {total} question{total === 1 ? '' : 's'} are presented to each learner. Use “Randomise groups” above to shuffle the order groups appear in.
      </span>
    </div>
  );
}

function QuestionRow({ num, question, onPatch, onDelete }) {
  const expanded = question.expanded;
  const promptLabel = question.prompt ? stripTagsForLabel(question.prompt) : 'Untitled question';
  return (
    <div style={{ border: '1px solid var(--border)', borderRadius: 'var(--radius)', background: 'var(--surface)' }}>
      <div onClick={() => onPatch({ expanded: !expanded })}
        style={{ display: 'grid', gridTemplateColumns: '28px 1fr auto auto',
          gap: 10, alignItems: 'center', padding: '8px 10px', cursor: 'default' }}>
        <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
          color: 'var(--text-muted)' }}>Q{num}</span>
        <span className="truncate" style={{ fontSize: 13,
          color: question.prompt ? 'var(--text)' : 'var(--text-faint)' }}>{promptLabel}</span>
        <span style={{ fontSize: 11, color: 'var(--text-faint)' }}>{question.answers.length} answers</span>
        {question.verified
          ? <I.CheckCircle size={13} style={{ color: 'var(--success)' }} />
          : <span style={{ width: 13 }} />}
      </div>
      {expanded && (
        <div style={{ padding: '0 12px 12px', display: 'flex', flexDirection: 'column', gap: 12 }}>
          <label style={{ display: 'grid', gap: 4 }}>
            <span style={{ fontSize: 11.5, color: 'var(--text-muted)' }}>Question prompt</span>
            <input className="field" value={question.prompt}
              onChange={e => onPatch({ prompt: e.target.value })}
              placeholder="Type the question…"
              style={{ width: '100%', fontSize: 13 }} />
          </label>
          <AnswersEditor answers={question.answers}
            onChange={next => onPatch({ answers: next })} />
          <div style={{ display: 'flex', alignItems: 'center' }}>
            <div style={{ flex: 1 }} />
            <button className="btn sm ghost danger" onClick={onDelete} title="Delete question">
              <I.Trash size={12} />Delete
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

// ── Result screen — DEFAULT-language text only. Other languages are edited on
// ── the Localisation surface (§28). Body keeps the protected {correct}/{total}
// ── tokens (atomic chips, non-editable contents).
function ResultScreenEditor({ which, primary, onNavigate }) {
  const TOKENS = [{ key: 'correct', label: 'correct' }, { key: 'total', label: 'total' }];
  const RS = window.FRAMING_CONTENT_SAMPLES || {};
  const src = which === 'pre' ? (RS.preAssessment || {}) : (RS.postAssessment || {});
  const heading0 = which === 'pre' ? (locText(src.resultTitle) || 'Thank you') : (locText(src.passTitle) || 'Well done!');
  const body0 = which === 'pre' ? locText(src.resultBody) : locText(src.passBody);
  const ack0 = locText(src.acknowledgement);

  const [heading, setHeading] = React.useState(heading0);

  return (
    <div className="card" style={{ padding: 14 }}>
      <h3 style={{ margin: '0 0 12px', fontSize: 12.5, fontWeight: 600,
        textTransform: 'uppercase', letterSpacing: '.06em', color: 'var(--text-muted)' }}>
        Result screen
      </h3>

      <div style={{ display: 'grid', gap: 14 }}>
        <label style={{ display: 'grid', gap: 5 }}>
          <span style={{ fontSize: 12.5, fontWeight: 500 }}>
            {which === 'pre' ? 'Pre-result heading' : 'Pass heading'}
          </span>
          <input className="field" value={heading} onChange={e => setHeading(e.target.value)}
            style={{ width: '100%' }} />
        </label>

        <div>
          <div style={{ fontSize: 12.5, fontWeight: 500, marginBottom: 6 }}>Body</div>
          {/* {correct}/{total} render as protected, non-editable chips (no
              toolbar insert buttons — tokenInsert={false}). */}
          <RichTextEditor value={body0} tokens={TOKENS} tokenInsert={false} minHeight={140}
            placeholder="Write the message learners see…" />
        </div>

        {which === 'post' && (
          <div>
            <div style={{ fontSize: 12.5, fontWeight: 500, marginBottom: 6 }}>Acknowledgement clause</div>
            <RichTextEditor value={ack0} minHeight={140}
              placeholder="The statement the learner confirms…" />
          </div>
        )}

      </div>
    </div>
  );
}

Object.assign(window, { SurfaceAssessments });
