/* screens-lesson.jsx — Curriculum overview + Lesson page (theory + tools) */
const { useState: lState, useEffect: lEffect, useRef: lRef } = React;

/* Snapshot static content_blocks from data.js before app.jsx overwrites window.EDSTUTIA.modules.
   Used as fallback for Supabase lessons that haven't had content_blocks saved yet. */
const _STATIC_BLOCKS = {};
try {
  ((window.EDSTUTIA && window.EDSTUTIA.modules) || []).forEach(m =>
    (m.lessons || []).forEach(l => {
      if (l.title && l.content_blocks && l.content_blocks.length)
        _STATIC_BLOCKS[l.title] = l.content_blocks;
    })
  );
} catch (_) {}

/* ── MCQ interactive block (learner view) ───────────────────────── */
function MCQInteractive({ block }) {
  const [sel, setSel] = lState(null);
  const [checked, setChecked] = lState(false);
  const correct = block.correct ?? 0;
  return (
    <Tool icon="quiz" kicker="Knowledge check" title={block.question || 'Quiz question'}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {(block.options || []).map((opt, i) => {
          const isCorrect = i === correct;
          const isSel = sel === i;
          let border = 'var(--border)', bg = 'var(--surface-2)';
          if (checked) {
            if (isCorrect) { border = 'var(--lime)'; bg = 'var(--lime-soft)'; }
            else if (isSel) { border = '#E0584F'; bg = 'rgba(224,88,79,.08)'; }
          } else if (isSel) { border = 'var(--lime)'; bg = 'var(--lime-soft)'; }
          return (
            <button key={i} onClick={() => { if (!checked) setSel(i); }}
              style={{ padding: '12px 16px', borderRadius: 10, border: '1.5px solid ' + border, background: bg, textAlign: 'left', cursor: checked ? 'default' : 'pointer', fontSize: 15.5, fontWeight: 600, display: 'flex', alignItems: 'center', gap: 12, transition: 'all .15s' }}>
              {checked && isCorrect && <Icon name="check" size={17} style={{ color: 'var(--lime-strong)', flex: 'none' }} />}
              {checked && isSel && !isCorrect && <span style={{ color: '#E0584F', fontWeight: 900, flex: 'none', lineHeight: 1 }}>✕</span>}
              {opt}
            </button>
          );
        })}
      </div>
      {!checked ? (
        <button className="btn btn-primary btn-sm" style={{ marginTop: 16 }} disabled={sel === null} onClick={() => setChecked(true)}>Check answer</button>
      ) : (
        <div style={{ marginTop: 16, padding: '14px 18px', borderRadius: 12, background: sel === correct ? 'var(--lime-soft)' : 'rgba(224,88,79,.08)', border: '1.5px solid ' + (sel === correct ? 'var(--lime)' : '#E0584F') }}>
          <p style={{ fontWeight: 800, fontSize: 15.5, color: sel === correct ? 'var(--lime-strong)' : '#E0584F', marginBottom: block.explanation ? 8 : 0 }}>
            {sel === correct ? '✓ Correct!' : '✕ Not quite — the correct answer is highlighted above.'}
          </p>
          {block.explanation && <p style={{ fontSize: 15, lineHeight: 1.55 }}>{block.explanation}</p>}
          <button onClick={() => { setSel(null); setChecked(false); }} style={{ marginTop: 10, fontSize: 13, fontWeight: 700, color: 'var(--text-muted)', background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}>Try again</button>
        </div>
      )}
    </Tool>
  );
}

/* ── Checklist interactive block (learner view, progress persisted) ─ */
function ChecklistInteractive({ block, blockId, lessonId }) {
  const [ticked, setTicked] = usePersist('chk_' + lessonId + '_' + blockId, {});
  const total = (block.items || []).length;
  const done = Object.values(ticked).filter(Boolean).length;
  return (
    <Tool icon="check" kicker="Checklist" title={block.title || 'Complete the steps'}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
        {(block.items || []).map((item, i) => (
          <label key={i} style={{ display: 'flex', alignItems: 'flex-start', gap: 12, cursor: 'pointer', padding: '10px 14px', borderRadius: 10, background: ticked[i] ? 'var(--lime-soft)' : 'var(--surface-2)', border: '1px solid ' + (ticked[i] ? 'var(--lime)' : 'var(--border)'), transition: 'all .15s' }}>
            <input type="checkbox" checked={!!ticked[i]} onChange={e => setTicked(t => ({ ...t, [i]: e.target.checked }))} style={{ width: 18, height: 18, marginTop: 2, accentColor: 'var(--lime)', flex: 'none', cursor: 'pointer' }} />
            <span style={{ fontSize: 16, lineHeight: 1.5, textDecoration: ticked[i] ? 'line-through' : 'none', color: ticked[i] ? 'var(--text-muted)' : 'var(--text)', fontWeight: 600 }}>{item}</span>
          </label>
        ))}
      </div>
      {total > 0 && (
        <div style={{ marginTop: 16 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
            <span className="muted" style={{ fontSize: 13, fontWeight: 700 }}>{done} of {total} complete</span>
            {done === total && <span style={{ color: 'var(--lime-strong)', fontWeight: 800, fontSize: 13 }}>✓ All done!</span>}
          </div>
          <div className="progress"><i style={{ width: (done / total * 100) + '%' }} /></div>
        </div>
      )}
    </Tool>
  );
}

/* ── Instructor intro circles with click-to-play modal ─────────── */
function InstructorIntros({ b, onFirstPlay }) {
  const [playing, setPlaying] = React.useState(null);
  const [hasPlayed, setHasPlayed] = React.useState(false);
  const instructors = (b.instructors || []);
  const handlePlay = (i) => { setPlaying(i); if (!hasPlayed) { setHasPlayed(true); onFirstPlay?.(); } };
  const COLORS = ['#2d6a4f', '#1d3557', '#6b3fa0', '#b5451b'];
  const initials = (name) => (name || '?').split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase();

  return (
    <div style={{ marginTop: 28, marginBottom: 12 }}>
      {b.heading && <h3 style={{ textAlign: 'center', fontSize: 22, fontFamily: 'var(--font-head)', fontWeight: 800, marginBottom: 32 }}>{b.heading}</h3>}
      <div style={{ display: 'flex', gap: 40, justifyContent: 'center', flexWrap: 'wrap' }}>
        {instructors.map((inst, i) => {
          const hasVideo = !!(inst.videoUrl);
          return (
            <div key={i} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 14 }}>
              <button
                onClick={() => hasVideo && handlePlay(i)}
                style={{
                  width: 200, height: 200, borderRadius: '50%',
                  border: '3px solid var(--lime)',
                  boxShadow: '0 6px 28px rgba(0,0,0,.14)',
                  background: inst.imageUrl ? '#000' : COLORS[i % COLORS.length],
                  cursor: hasVideo ? 'pointer' : 'default',
                  padding: 0, position: 'relative', overflow: 'hidden',
                  transition: 'transform .18s, box-shadow .18s',
                  flexShrink: 0,
                }}
                onMouseEnter={e => { if (hasVideo) { e.currentTarget.style.transform = 'scale(1.05)'; e.currentTarget.style.boxShadow = '0 10px 36px rgba(0,0,0,.22)'; }}}
                onMouseLeave={e => { e.currentTarget.style.transform = 'scale(1)'; e.currentTarget.style.boxShadow = '0 6px 28px rgba(0,0,0,.14)'; }}
                title={hasVideo ? `Watch ${inst.name}'s intro` : inst.name || 'Coming soon'}
              >
                {/* Photo or initials background */}
                {inst.imageUrl ? (
                  <img src={inst.imageUrl} alt={inst.name || ''} style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'center top', display: 'block' }} />
                ) : (
                  <span style={{ fontSize: 52, fontWeight: 800, color: 'rgba(255,255,255,.22)', fontFamily: 'var(--font-head)', userSelect: 'none', letterSpacing: -2 }}>
                    {initials(inst.name)}
                  </span>
                )}
                {/* Play overlay */}
                {hasVideo ? (
                  <div style={{
                    position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column',
                    alignItems: 'center', justifyContent: 'center', gap: 6,
                    background: inst.imageUrl ? 'rgba(0,0,0,0)' : 'rgba(0,0,0,.32)',
                    transition: 'background .2s',
                  }}
                    onMouseEnter={e => { e.currentTarget.style.background = 'rgba(0,0,0,.42)'; }}
                    onMouseLeave={e => { e.currentTarget.style.background = inst.imageUrl ? 'rgba(0,0,0,0)' : 'rgba(0,0,0,.32)'; }}
                  >
                    <div style={{ width: 52, height: 52, borderRadius: '50%', background: 'rgba(255,255,255,.92)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                      <svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M6 4l12 6-12 6V4z" fill="#14180D"/></svg>
                    </div>
                    <span style={{ color: '#fff', fontSize: 11, fontWeight: 700, letterSpacing: '.04em', textShadow: '0 1px 4px rgba(0,0,0,.6)' }}>WATCH INTRO</span>
                  </div>
                ) : (
                  <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                    <span style={{ color: 'rgba(255,255,255,.55)', fontSize: 12, fontWeight: 600 }}>Coming soon</span>
                  </div>
                )}
              </button>
              {inst.name && <p style={{ margin: 0, fontWeight: 700, fontSize: 16, textAlign: 'center' }}>{inst.name}</p>}
              {inst.title && <p style={{ margin: '2px 0 0', color: 'var(--text-muted)', fontSize: 13, textAlign: 'center' }}>{inst.title}</p>}
              {inst.org && <p style={{ margin: '1px 0 0', color: 'var(--lime-strong)', fontSize: 12, fontWeight: 700, textAlign: 'center', letterSpacing: '.02em' }}>{inst.org}</p>}
            </div>
          );
        })}
      </div>

      {/* Video modal */}
      {playing !== null && (() => {
        const inst = instructors[playing];
        const raw = inst.videoUrl || '';
        const isSynthesia = raw.includes('share.synthesia.io') && !raw.includes('/embeds/');
        const embedUrl = isSynthesia ? raw.replace('share.synthesia.io/', 'share.synthesia.io/embeds/videos/') : raw;
        return (
          <div
            onClick={() => setPlaying(null)}
            style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,.72)', zIndex: 9000, display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 24 }}
          >
            <div onClick={e => e.stopPropagation()} style={{ background: 'var(--surface)', borderRadius: 20, overflow: 'hidden', width: '100%', maxWidth: 780, boxShadow: '0 24px 80px rgba(0,0,0,.5)', display: 'flex', flexDirection: 'column' }}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 20px', borderBottom: '1px solid var(--border)' }}>
                <div>
                  <p style={{ margin: 0, fontWeight: 800, fontSize: 17 }}>{inst.name}</p>
                  {inst.title && <p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)' }}>{inst.title}</p>}
                  {inst.org && <p style={{ margin: '1px 0 0', fontSize: 12, fontWeight: 700, color: 'var(--lime-strong)' }}>{inst.org}</p>}
                </div>
                <button onClick={() => setPlaying(null)} style={{ background: 'var(--surface-2)', border: 'none', borderRadius: 10, width: 36, height: 36, cursor: 'pointer', fontSize: 20, color: 'var(--text-muted)', display: 'grid', placeItems: 'center' }}>×</button>
              </div>
              <div style={{ aspectRatio: '16/9', background: '#000' }}>
                <iframe src={embedUrl} width="100%" height="100%" style={{ border: 'none', display: 'block' }} allow="autoplay; fullscreen" allowFullScreen title={inst.name} />
              </div>
            </div>
          </div>
        );
      })()}
    </div>
  );
}

/* ── Render a single content block in the learner view ──────────── */
function AIOppCanvas({ b, lessonId }) {
  const storageKey = 'edstutia_canvas_' + (lessonId || 'default');
  const EMPTY = {
    course: '', moduleLesson: '', targetLearners: '', challenge: '',
    outcome: '', mastery: '', gap: '', approach: '',
    aiChecks: [], aiExplain: '',
    riskCheck: '',
    redesignChoice: '', rationale: '',
    reflection: ''
  };

  const [vals, setVals] = lState(() => {
    try { return JSON.parse(localStorage.getItem(storageKey)) || EMPTY; }
    catch { return EMPTY; }
  });

  const update = (key, val) => {
    setVals(v => {
      const next = { ...v, [key]: val };
      try { localStorage.setItem(storageKey, JSON.stringify(next)); } catch {}
      return next;
    });
  };

  const toggleCheck = (key, opt) => {
    const cur = vals[key] || [];
    update(key, cur.includes(opt) ? cur.filter(x => x !== opt) : [...cur, opt]);
  };

  const iStyle = { width: '100%', padding: '10px 12px', borderRadius: 8, border: '1.5px solid var(--border)', background: 'var(--surface)', color: 'var(--text)', fontSize: 14, fontFamily: 'inherit', boxSizing: 'border-box', outline: 'none' };
  const taStyle = { ...iStyle, minHeight: 120, resize: 'vertical', lineHeight: 1.65 };
  const h3Style = { color: 'var(--lime-strong)', fontFamily: 'var(--font-head)', fontSize: 17, fontWeight: 800, marginBottom: 10, marginTop: 0 };
  const secStyle = { marginBottom: 28 };

  const SectionBox = ({ children }) => (
    <div style={{ background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 12, padding: '18px 20px' }}>{children}</div>
  );

  const aiOpts = ['Practice more frequently', 'Receive personalized feedback', 'Experience realistic scenarios', 'Build confidence', 'Improve accessibility', 'Explore multiple perspectives'];
  const redesignOpts = ['Enhance the current activity', 'Redesign the experience', 'Create a new AI-supported experience', 'Create a new VR-supported experience', 'Do not use technology here'];

  return (
    <div style={{ maxWidth: 720, margin: '0 auto', paddingBottom: 40 }}>
      {/* Title card */}
      <div style={{ background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 14, padding: '24px 28px', marginBottom: 32 }}>
        <h2 style={{ fontFamily: 'var(--font-head)', fontSize: 22, fontWeight: 900, margin: '0 0 4px' }}>Opportunity Mapping Canvas</h2>
        <p style={{ color: 'var(--text-muted)', fontSize: 13, margin: '0 0 20px' }}>Using Backward Design to Align AI and VR Integration with Learning Outcomes</p>
        <p style={{ fontSize: 13, color: 'var(--text-muted)', margin: '0 0 16px', lineHeight: 1.6 }}>Use this worksheet to intentionally evaluate where AI or VR can enhance learning. Begin with the desired learning outcome, identify evidence of mastery, consider risks/concerns, then determine whether AI or VR improves practice, feedback, personalization, accessibility, or application.</p>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px 20px' }}>
          {[['course', 'Course'], ['moduleLesson', 'Module / Lesson / Assignment'], ['targetLearners', 'Target Learners']].map(([k, lbl]) => (
            <div key={k}><label style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-muted)', display: 'block', marginBottom: 5, textTransform: 'uppercase', letterSpacing: '.04em' }}>{lbl}</label><input value={vals[k]} onChange={e => update(k, e.target.value)} style={iStyle} placeholder={lbl} /></div>
          ))}
          <div style={{ gridColumn: '1 / -1' }}><label style={{ fontSize: 12, fontWeight: 700, color: 'var(--text-muted)', display: 'block', marginBottom: 5, textTransform: 'uppercase', letterSpacing: '.04em' }}>Learning Challenge You Want to Solve</label><input value={vals.challenge} onChange={e => update('challenge', e.target.value)} style={iStyle} placeholder="Describe the learning challenge…" /></div>
        </div>
      </div>

      {/* Sections 1–4 */}
      {[
        ['outcome', '1. Learning Outcome', 'What should students be able to DO by the end of this experience?'],
        ['mastery', '2. Evidence of Mastery', 'How will students demonstrate they achieved the learning outcome? What observable evidence proves mastery?'],
        ['gap', '3. Current Learning Gap', 'Where do students typically struggle? Consider misconceptions, confidence gaps, lack of practice, application challenges, or feedback limitations.'],
        ['approach', '4. Current Learning Approach', 'How do students currently practice or develop this skill? What works well? What could be improved?'],
      ].map(([k, title, ph]) => (
        <div key={k} style={secStyle}>
          <h3 style={h3Style}>{title}</h3>
          <SectionBox><textarea value={vals[k]} onChange={e => update(k, e.target.value)} style={{ ...taStyle, border: 'none', background: 'transparent', padding: 0, minHeight: 100 }} placeholder={ph} /></SectionBox>
        </div>
      ))}

      {/* Section 5: Opportunity */}
      <div style={secStyle}>
        <h3 style={h3Style}>5. Opportunity</h3>
        <SectionBox>
          <p style={{ fontSize: 13, color: 'var(--text-muted)', margin: '0 0 12px' }}>Where could AI or VR add meaningful value? Could either technology help students:</p>
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 6, marginBottom: 16 }}>
            {aiOpts.map(opt => (
              <label key={opt} style={{ display: 'flex', alignItems: 'center', gap: 9, cursor: 'pointer', fontSize: 14, padding: '6px 10px', borderRadius: 8, background: (vals.aiChecks || []).includes(opt) ? 'var(--lime-soft)' : 'transparent', transition: 'background .15s' }}>
                <input type="checkbox" checked={(vals.aiChecks || []).includes(opt)} onChange={() => toggleCheck('aiChecks', opt)} style={{ width: 15, height: 15, accentColor: 'var(--lime-strong)', flex: 'none' }} />
                {opt}
              </label>
            ))}
          </div>
          <p style={{ fontSize: 13, fontWeight: 700, color: 'var(--text-muted)', margin: '0 0 6px' }}>Explain:</p>
          <textarea value={vals.aiExplain} onChange={e => update('aiExplain', e.target.value)} style={{ ...taStyle, minHeight: 80, border: 'none', background: 'transparent', padding: 0 }} placeholder="Explain which AI opportunities apply and how…" />
        </SectionBox>
      </div>

      {/* Section 6: Risk Check */}
      <div style={secStyle}>
        <h3 style={h3Style}>6. Risk Check</h3>
        <SectionBox><textarea value={vals.riskCheck} onChange={e => update('riskCheck', e.target.value)} style={{ ...taStyle, border: 'none', background: 'transparent', padding: 0, minHeight: 100 }} placeholder="Could AI or VR unintentionally replace the thinking, creativity, or productive struggle students need? What guidelines or guardrails should be included?" /></SectionBox>
      </div>

      {/* Section 7: Redesign Decision */}
      <div style={secStyle}>
        <h3 style={h3Style}>7. Redesign Decision</h3>
        <SectionBox>
          <p style={{ fontSize: 13, color: 'var(--text-muted)', margin: '0 0 12px' }}>Based on your analysis, how will you integrate AI or VR?</p>
          <div style={{ marginBottom: 16 }}>
            {redesignOpts.map(opt => (
              <label key={opt} style={{ display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer', fontSize: 14, padding: '7px 10px', borderRadius: 8, background: vals.redesignChoice === opt ? 'var(--lime-soft)' : 'transparent', transition: 'background .15s', marginBottom: 4 }}>
                <input type="radio" name={'redesign_' + lessonId} checked={vals.redesignChoice === opt} onChange={() => update('redesignChoice', opt)} style={{ width: 15, height: 15, accentColor: 'var(--lime-strong)', flex: 'none' }} />
                {opt}
              </label>
            ))}
          </div>
          <p style={{ fontSize: 13, fontWeight: 700, color: 'var(--text-muted)', margin: '0 0 6px' }}>Rationale:</p>
          <textarea value={vals.rationale} onChange={e => update('rationale', e.target.value)} style={{ ...taStyle, minHeight: 80, border: 'none', background: 'transparent', padding: 0 }} placeholder="Explain your decision…" />
        </SectionBox>
      </div>

      {/* Final Reflection */}
      <div style={secStyle}>
        <h3 style={h3Style}>Final Reflection</h3>
        <SectionBox><textarea value={vals.reflection} onChange={e => update('reflection', e.target.value)} style={{ ...taStyle, minHeight: 140, border: 'none', background: 'transparent', padding: 0 }} placeholder="How does this tech integration improve the student learning experience and better support achievement of learning outcomes?" /></SectionBox>
      </div>

      {/* Footer bar */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, paddingTop: 16, borderTop: '1px solid var(--border)', flexWrap: 'wrap' }}>
        <span style={{ fontSize: 12, color: 'var(--lime-strong)', fontWeight: 700 }}>✓ Answers auto-saved on this device</span>
        <div style={{ marginLeft: 'auto', display: 'flex', gap: 10 }}>
          <a href="assets/01Edstutia_Opportunity_Mapping_Worksheet - rev 7.15.26.pdf" download style={{ padding: '8px 16px', borderRadius: 9, border: '1.5px solid var(--border)', background: 'var(--surface-2)', color: 'var(--text)', fontSize: 13, fontWeight: 700, textDecoration: 'none' }}>Download blank PDF</a>
          <button onClick={() => { if (confirm('Clear all your answers? This cannot be undone.')) { try { localStorage.removeItem(storageKey); } catch {} setVals(EMPTY); } }} style={{ padding: '8px 16px', borderRadius: 9, border: '1.5px solid var(--border)', background: 'transparent', color: 'var(--text-muted)', fontSize: 13, fontWeight: 700, cursor: 'pointer' }}>Clear answers</button>
        </div>
      </div>
    </div>
  );
}

function renderContentBlock(b, lessonId, opts = {}) {
  const CV = window.CALLOUT_VARIANTS || {};
  switch (b.type) {
    case 'heading':
      return <div key={b.id} style={{ marginTop: 32, marginBottom: 14 }}><h2 style={{ fontSize: b.level === 3 ? 22 : 30, fontFamily: 'var(--font-head)', fontWeight: 800, lineHeight: 1.2, margin: 0 }}>{b.content}</h2></div>;
    case 'text':
      return <p key={b.id} style={{ fontSize: 17.5, lineHeight: 1.65, marginTop: 14, whiteSpace: 'pre-wrap' }}>{b.content}</p>;
    case 'callout': {
      const v = CV[b.variant] || { bg: 'var(--lime-soft)', border: 'var(--lime)', icon: 'target' };
      return (
        <div key={b.id} style={{ display: 'flex', gap: 14, alignItems: 'flex-start', background: v.bg, borderLeft: '5px solid ' + v.border, borderRadius: 12, padding: '16px 20px', marginTop: 20 }}>
          <Icon name={v.icon} size={22} style={{ color: v.border, flex: 'none', marginTop: 2 }} />
          <p style={{ margin: 0, fontSize: 17, fontWeight: 700, lineHeight: 1.5 }}>{b.content}</p>
        </div>
      );
    }
    case 'divider':
      return <hr key={b.id} style={{ border: 'none', borderTop: '1px solid var(--border)', margin: '28px 0' }} />;
    case 'video':
      return <div key={b.id} style={{ marginTop: 22 }}><VideoPlayer src={b.url} title={b.caption} onFirstPlay={opts.onComplete} />{b.caption && <p className="muted" style={{ fontSize: 14, marginTop: 8, textAlign: 'center' }}>{b.caption}</p>}</div>;
    case 'image':
      return b.url ? <div key={b.id} style={{ marginTop: 22, textAlign: 'center' }}><img src={b.url} alt={b.alt || b.caption || ''} style={{ maxWidth: '100%', borderRadius: 'var(--radius)', border: '1px solid var(--border)' }} />{b.caption && <p className="muted" style={{ fontSize: 14, marginTop: 8 }}>{b.caption}</p>}</div> : null;
    case 'pdf':
      return b.url ? (
        <a key={b.id} href={b.url} target="_blank" style={{ display: 'flex', alignItems: 'center', gap: 16, padding: '16px 20px', borderRadius: 'var(--radius)', border: '1px solid var(--border)', background: 'var(--surface)', textDecoration: 'none', color: 'var(--text)', marginTop: 18, boxShadow: 'var(--shadow)' }}>
          <div style={{ width: 44, height: 44, background: 'var(--lime-soft)', color: 'var(--lime-strong)', borderRadius: 12, display: 'grid', placeItems: 'center', flex: 'none' }}><Icon name="book" size={22} /></div>
          <div style={{ flex: 1 }}><strong style={{ display: 'block', fontSize: 16 }}>{b.label || 'Download document'}</strong><span className="muted" style={{ fontSize: 13 }}>Click to open</span></div>
          <Icon name="arrow" size={18} style={{ color: 'var(--text-muted)' }} />
        </a>
      ) : null;
    case 'embed': {
      if (!b.url) return null;
      const isGForm = b.url.includes('docs.google.com/forms');
      const embedUrl = isGForm
        ? b.url.split('?')[0] + '?embedded=true'
        : b.url;
      const iframeH = b.height || (isGForm ? 900 : 520);
      return (
        <div key={b.id} style={{ marginTop: 22 }}>
          {isGForm && (
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10, padding: '8px 14px', background: 'var(--surface-2)', borderRadius: 10, border: '1px solid var(--border)' }}>
              <span style={{ fontSize: 18 }}>📋</span>
              <span style={{ fontSize: 13, fontWeight: 700, color: 'var(--text-muted)' }}>Complete the form below — your responses are saved automatically in Google Forms.</span>
            </div>
          )}
          <iframe
            src={embedUrl}
            title={b.caption || 'Embedded content'}
            width="100%"
            height={iframeH}
            style={{ border: 'none', borderRadius: 12, display: 'block' }}
            allowFullScreen
            frameBorder="0"
          />
          {b.caption && <p className="muted" style={{ fontSize: 14, marginTop: 8, textAlign: 'center' }}>{b.caption}</p>}
        </div>
      );
    }
    case 'instructor-intros':
      return <InstructorIntros key={b.id} b={b} onFirstPlay={opts.onComplete} />;
    case 'ai-opportunity-canvas':
      return <div key={b.id} style={{ marginTop: 24 }}><AIOppCanvas b={b} lessonId={lessonId} /></div>;
    case 'mcq':
      return <div key={b.id} style={{ marginTop: 24 }}><MCQInteractive block={b} /></div>;
    case 'checklist':
      return <div key={b.id} style={{ marginTop: 24 }}><ChecklistInteractive block={b} blockId={b.id} lessonId={lessonId} /></div>;
    case 'journal':
      return <div key={b.id} style={{ marginTop: 24 }}><Journal id={lessonId + '_' + b.id} prompt={b.prompt || "What is the most useful idea you'll take from this?"} /></div>;
    case 'discussion':
      return <div key={b.id} style={{ marginTop: 24 }}><Discussion id={lessonId} /></div>;
    case 'aichat':
      return <div key={b.id} style={{ marginTop: 24 }}><AIChat /></div>;
    case 'upload':
      return <div key={b.id} style={{ marginTop: 24 }}><FileUpload lessonId={lessonId + '_upload_' + b.id} label={b.label} hint={b.hint} onComplete={opts.onComplete} /></div>;
    case 'video-upload':
      return <div key={b.id} style={{ marginTop: 24 }}><VideoUpload lessonId={lessonId + '_vidupload_' + b.id} label={b.label} hint={b.hint} onComplete={opts.onComplete} /></div>;
    default:
      return null;
  }
}

/* ---------- Theory content (rich for the lead lesson, outline for others) ---------- */
const LESSON_THEORY = {
  m1l1: [
    { p: "Immersive technology isn't about novelty \u2014 it's about putting learners inside an experience they couldn't safely or practically have any other way. This lesson looks at why that matters, and where it genuinely helps." },
    { h: "Why VR? Value and impact" },
    { p: "Virtual reality lets students practise, explore and feel a concept rather than only read about it. A nursing student can rehearse a hard conversation; a biology student can stand inside a cell; a history class can walk through a marketplace that no longer exists." },
    { key: "Reach for immersion when presence, space, scale, or safe practice is the point \u2014 not simply to add technology." },
    { h: "Positive outcomes in education" },
    { list: ["Stronger recall through learning by doing", "Higher motivation and time spent on task", "Safe rehearsal of high-stakes or costly scenarios", "Access to places and scales impossible in a classroom"] },
    { h: "Presence, immersion & engagement" },
    { p: "These three words are easy to mix up. Immersion is what the system delivers \u2014 the richness of sight, sound and tracking. Presence is what the learner feels \u2014 the sense of really being there. Engagement is the result \u2014 sustained, invested attention." },
    { quote: "When learners feel present, they respond to the virtual world as if it were real \u2014 and that is when the deepest learning happens.", by: "Edstutia learning model" },
    { h: "Levels of immersion" },
    { p: "Immersion is a spectrum, not a switch. It runs from a 360\u00B0 video viewed on a phone, through interactive 3D on a desktop, all the way to a fully tracked headset where you move naturally through space. More immersion isn't always better \u2014 match the level to your learning goal." },
    { h: "Cybersickness & wellness" },
    { p: "A small number of learners feel queasy when what their eyes see doesn't match what their body feels. The fix is mostly design: keep early sessions short, turn on comfort settings, build in breaks, and never make immersion mandatory." },
    { key: "First sessions should be short. Always offer a desktop alternative, and let anyone pause at any time." },
  ],
};

function Theory({ lesson }) {
  const blocks = LESSON_THEORY[lesson.id];
  const plain = blocks ? blocks.map(b => b.p || b.h || b.quote || (b.list || []).join('. ') || b.key).join(' ') : lesson.summary;
  return (
    <Tool icon="book" kicker="Read &amp; absorb" title="Overview">
      <button className="btn btn-soft btn-sm" style={{ marginBottom: 8 }} onClick={() => speak(plain)}><Icon name="volume" size={16} /> Read this aloud</button>
      {blocks ? blocks.map((b, i) => {
        if (b.h) return <h3 key={i} style={{ fontSize: 21, marginTop: 22 }}>{b.h}</h3>;
        if (b.p) return <p key={i} style={{ fontSize: 17.5, lineHeight: 1.65, marginTop: 12 }}>{b.p}</p>;
        if (b.key) return (
          <div key={i} className="row gap-10" style={{ alignItems: 'flex-start', marginTop: 16, padding: 16, borderRadius: 'var(--radius)', background: 'var(--lime-soft)', borderLeft: '5px solid var(--lime)' }}>
            <Icon name="target" size={22} style={{ color: 'var(--lime-strong)', flex: 'none', marginTop: 2 }} />
            <p style={{ fontSize: 17, fontWeight: 700, lineHeight: 1.5 }}>{b.key}</p>
          </div>
        );
        if (b.quote) return (
          <blockquote key={i} style={{ margin: '22px 0 6px', padding: '4px 0 4px 22px', borderLeft: '4px solid var(--lime)' }}>
            <p style={{ fontFamily: 'var(--font-head)', fontSize: 22, fontWeight: 800, lineHeight: 1.35, letterSpacing: '-.01em' }}>“{b.quote}”</p>
            <cite className="muted" style={{ fontStyle: 'normal', fontWeight: 700, fontSize: 14, display: 'block', marginTop: 8 }}>— {b.by}</cite>
          </blockquote>
        );
        if (b.list) return (
          <ul key={i} style={{ listStyle: 'none', padding: 0, margin: '14px 0 0', display: 'grid', gap: 10 }}>
            {b.list.map((li, j) => (
              <li key={j} className="row gap-10" style={{ alignItems: 'flex-start' }}>
                <span style={{ width: 24, height: 24, borderRadius: '50%', background: 'var(--lime-soft)', color: 'var(--lime-strong)', display: 'grid', placeItems: 'center', flex: 'none', marginTop: 1 }}><Icon name="check" size={15} /></span>
                <span style={{ fontSize: 17, lineHeight: 1.5 }}>{li}</span>
              </li>
            ))}
          </ul>
        );
        return null;
      }) : (
        <div>
          <p style={{ fontSize: 17.5, lineHeight: 1.65 }}>{lesson.summary}</p>
          <h3 style={{ fontSize: 18, marginTop: 22, marginBottom: 12 }}>In this lesson you'll cover</h3>
          <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'grid', gap: 14 }}>
            {lesson.topics.map((t, j) => (
              <li key={j} className="row gap-10" style={{ alignItems: 'flex-start', lineHeight: 1.4 }}>
                <span style={{ width: 24, height: 24, borderRadius: '50%', background: 'var(--lime-soft)', color: 'var(--lime-strong)', display: 'grid', placeItems: 'center', flex: 'none', marginTop: 1, fontFamily: 'var(--font-head)', fontWeight: 800, fontSize: 13 }}>{j + 1}</span>
                <span style={{ fontSize: 17, lineHeight: 1.5 }}>{t}</span>
              </li>
            ))}
          </ul>
        </div>
      )}
    </Tool>
  );
}

/* ---- Video Player ---- */
function VideoPlayer({ src, title, onFirstPlay }) {
  const [fired, setFired] = lState(false);
  const videoRef = lRef(null);
  const highWater = lRef(0);
  const fire = () => { if (!fired) { setFired(true); onFirstPlay?.(); } };
  const onTimeUpdate = () => { const v = videoRef.current; if (v && v.currentTime > highWater.current) highWater.current = v.currentTime; };
  const onSeeking = () => { const v = videoRef.current; if (v && v.currentTime > highWater.current + 0.5) v.currentTime = highWater.current; };

  if (!src) return (
    <div style={{ aspectRatio: '16/9', background: 'var(--surface-2)', borderRadius: 'var(--radius)', display: 'grid', placeItems: 'center', color: 'var(--text-muted)', border: '2px dashed var(--border)' }}>
      <div className="col" style={{ alignItems: 'center', gap: 12 }}>
        <Icon name="play" size={40} />
        <span style={{ fontWeight: 700, fontSize: 15 }}>No video URL set</span>
        <span style={{ fontSize: 13 }}>Editors: add a video URL in the lesson settings</span>
      </div>
    </div>
  );

  const isYoutube = src.includes('youtube.com') || src.includes('youtu.be');
  const isVimeo = src.includes('vimeo.com');
  const isSynthesia = src.includes('synthesia.io');

  if (isYoutube || isVimeo || isSynthesia) {
    let embedSrc = src;
    if (isYoutube) {
      const vid = src.match(/(?:v=|youtu\.be\/)([A-Za-z0-9_-]{11})/)?.[1];
      if (vid) embedSrc = `https://www.youtube.com/embed/${vid}?rel=0&modestbranding=1`;
    }
    if (isVimeo) {
      const vid = src.match(/vimeo\.com\/(\d+)/)?.[1];
      if (vid) embedSrc = `https://player.vimeo.com/video/${vid}`;
    }
    if (isSynthesia && !src.includes('/embeds/')) {
      embedSrc = src.replace('share.synthesia.io/', 'share.synthesia.io/embeds/videos/');
    }
    return (
      <div className="video-wrapper" style={{ position: 'relative' }}>
        <iframe src={embedSrc} title={title || 'Lesson video'} allowFullScreen frameBorder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" />
        {/* Transparent overlay catches the first click (iframe clicks are undetectable otherwise) */}
        {!fired && <div onClick={fire} style={{ position: 'absolute', inset: 0, cursor: 'pointer', zIndex: 1 }} />}
      </div>
    );
  }

  return (
    <div className="video-wrapper">
      <video ref={videoRef} controls src={src} onPlay={fire} onTimeUpdate={onTimeUpdate} onSeeking={onSeeking} style={{ width: '100%', height: '100%', objectFit: 'contain', background: '#000' }}>
        Your browser doesn't support video playback.
      </video>
    </div>
  );
}

function LessonVideo({ lesson, onFirstPlay }) {
  const [url, setUrl] = lState(lesson.video_url || '');
  const [editing, setEditing] = lState(false);
  const [draft, setDraft] = lState(url);
  const isEditor = window.EDSTUTIA?.learner && ['editor', 'staff'].includes(window.EDSTUTIA.learner.role);

  const save = async () => {
    setUrl(draft);
    lesson.video_url = draft;
    setEditing(false);
    if (window.supabaseClient && !lesson.id.toString().startsWith('m')) {
      await window.supabaseClient.from('lessons').update({ video_url: draft }).eq('id', lesson.id);
    }
  };

  return (
    <Tool icon="play" kicker="Lesson video" title={lesson.title}>
      <VideoPlayer src={url} title={lesson.title} onFirstPlay={onFirstPlay} />
      {isEditor && (
        editing ? (
          <div className="row gap-10" style={{ marginTop: 14 }}>
            <input value={draft} onChange={e => setDraft(e.target.value)} placeholder="YouTube, Vimeo, or direct MP4 URL…"
              style={{ flex: 1, padding: '10px 14px', border: '1.5px solid var(--lime)', borderRadius: 10, background: 'var(--surface-2)', color: 'var(--text)', fontSize: 15 }} />
            <button className="btn btn-primary btn-sm" onClick={save}><Icon name="check" size={16} /> Save</button>
            <button className="btn btn-ghost btn-sm" onClick={() => setEditing(false)}>Cancel</button>
          </div>
        ) : (
          <button className="btn btn-ghost btn-sm" style={{ marginTop: 12 }} onClick={() => setEditing(true)}>
            <Icon name="pen" size={15} /> {url ? 'Change video URL' : 'Add video URL'}
          </button>
        )
      )}
    </Tool>
  );
}

/* journal prompts + TOC labels per tool */
const PROMPTS = {
  m1l1: "Think of one topic you teach where students often struggle to picture something. How might stepping inside it in VR change their understanding?",
  m1l2: "What worried you, even slightly, about getting set up in Edstutia? How might a student of yours feel \u2014 and what would help them?",
  m2l1: "After talking with an AI persona, what surprised you? Where could a persona like this genuinely help your learners?",
  m2l2: "What is one learning outcome you'd want an AI persona to support, and how would you know it was working?",
  m3l1: "Which single activity from this course are you most likely to actually run? What's the first small step?",
};
const TOC_LABEL = { blank: 'Lesson Content', theory: 'Overview', flashcards: 'Flashcards', hotspot: 'The headset', quiz: 'Knowledge check', journal: 'Reflect', dragsort: 'Sort it out', storyboard: 'Template', discussion: 'Discussion', aichat: 'AI sandbox', live: 'Live session', video: 'Video', upload: 'Assignment', 'video-upload': 'Video submission' };
const TOC_ICON = { blank: 'book', theory: 'book', flashcards: 'cards', hotspot: 'headset', quiz: 'quiz', journal: 'pen', dragsort: 'drag', storyboard: 'layers', discussion: 'chat', aichat: 'robot', live: 'calendar', video: 'play', upload: 'upload', 'video-upload': 'film' };

function LiveCallout({ lesson, go }) {
  const E = window.EDSTUTIA;
  const live = E.live || [];
  const s = live.find(x => x.lesson === lesson.title) || live[0];
  return (
    <Tool icon="calendar" kicker="Synchronous" title="This is a live session">
      <p className="muted" style={{ marginBottom: 16 }}>You'll join your instructor and cohort in real time. Here's the session — the room opens an hour before.</p>
      <LiveSessionCard s={s} onJoin={() => go({ name: 'live' })} />
    </Tool>
  );
}

function BlockBuilder({ lesson }) {
  const E = window.EDSTUTIA;
  const isEditor = E && E.learner && ['editor', 'staff'].includes(E.learner.role);
  const [blocks, setBlocks] = lState(lesson.content_blocks || []);
  const [isEditing, setIsEditing] = lState(false);

  const renderBlock = (b, i) => {
    switch (b.type) {
      case 'heading': return <h2 style={{ fontSize: 28, marginTop: 32, marginBottom: 16 }}>{b.content}</h2>;
      case 'text': return <p style={{ fontSize: 17.5, lineHeight: 1.65, marginBottom: 20 }}>{b.content}</p>;
      case 'mcq': return (
        <div className="card card-pad" style={{ background: 'var(--surface-1)', marginBottom: 24, border: '1px solid var(--border)' }}>
           <h3 style={{ fontSize: 19, marginBottom: 16 }}>{b.content}</h3>
           <div className="stack gap-10">
             {(b.options || []).map((opt, j) => (
                <button key={j} className="btn btn-ghost" style={{ justifyContent: 'flex-start', textAlign: 'left', background: 'var(--surface-2)', border: '1px solid var(--border)', padding: '12px 16px', borderRadius: 8 }}>{opt}</button>
             ))}
           </div>
        </div>
      );
      case 'pdf': return (
        <div className="card card-pad row gap-16" style={{ background: 'var(--surface-1)', marginBottom: 24, alignItems: 'center', border: '1px solid var(--border)' }}>
          <div style={{ padding: 14, background: 'var(--lime-soft)', color: 'var(--lime-strong)', borderRadius: 10 }}><Icon name="book" /></div>
          <div>
            <h4 style={{ margin: 0, fontSize: 16 }}>Attached Document</h4>
            <a href={b.url || '#'} target="_blank" className="muted" style={{ fontSize: 14, textDecoration: 'underline' }}>{b.content}</a>
          </div>
        </div>
      );
      case 'video': return (
        <div style={{ marginBottom: 24 }}>
          <VideoPlayer src={b.content} title={b.caption} />
          {b.caption && <p className="muted" style={{ fontSize: 14, marginTop: 8, textAlign: 'center' }}>{b.caption}</p>}
        </div>
      );
      case 'image': return (
        <div style={{ marginBottom: 24, textAlign: 'center' }}>
          <img src={b.content} alt={b.caption || ''} style={{ maxWidth: '100%', borderRadius: 'var(--radius)', border: '1px solid var(--border)' }} />
          {b.caption && <p className="muted" style={{ fontSize: 14, marginTop: 8 }}>{b.caption}</p>}
        </div>
      );
      case 'callout': return (
        <div className="row gap-10" style={{ alignItems: 'flex-start', marginBottom: 20, padding: 16, borderRadius: 'var(--radius)', background: 'var(--lime-soft)', borderLeft: '5px solid var(--lime)' }}>
          <Icon name="target" size={22} style={{ color: 'var(--lime-strong)', flex: 'none', marginTop: 2 }} />
          <p style={{ fontSize: 17, fontWeight: 700, lineHeight: 1.5 }}>{b.content}</p>
        </div>
      );
      default: return null;
    }
  };

  const addBlock = (type) => {
    const defaults = {
      mcq: { content: 'New Question', options: ['Option A', 'Option B'] },
      heading: { content: 'New Heading' },
      text: { content: 'Start typing your lesson text here...' },
      pdf: { content: 'document.pdf', url: '' },
      video: { content: '', caption: '' },
      image: { content: '', caption: '' },
      callout: { content: 'Key takeaway or callout text...' }
    };
    const newBlock = { id: crypto.randomUUID(), type, ...(defaults[type] || { content: '' }) };
    const nextBlocks = [...blocks, newBlock];
    setBlocks(nextBlocks);
    saveBlocks(nextBlocks);
  };

  const updateBlock = (id, newContent) => {
    const nextBlocks = blocks.map(b => b.id === id ? { ...b, ...newContent } : b);
    setBlocks(nextBlocks);
    saveBlocks(nextBlocks);
  };

  const moveBlock = (index, dir) => {
    if (index + dir < 0 || index + dir >= blocks.length) return;
    const nextBlocks = [...blocks];
    const temp = nextBlocks[index];
    nextBlocks[index] = nextBlocks[index + dir];
    nextBlocks[index + dir] = temp;
    setBlocks(nextBlocks);
    saveBlocks(nextBlocks);
  };

  const deleteBlock = (index) => {
    const nextBlocks = [...blocks];
    nextBlocks.splice(index, 1);
    setBlocks(nextBlocks);
    saveBlocks(nextBlocks);
  };

  const saveBlocks = async (newBlocks) => {
    lesson.content_blocks = newBlocks;
    if (window.supabaseClient && !lesson.id.toString().startsWith('l')) {
      await window.supabaseClient.from('lessons').update({ content_blocks: newBlocks }).eq('id', lesson.id);
    }
  };

  if (!isEditor && blocks.length === 0) {
    return <div className="card card-pad muted" style={{ padding: 40, textAlign: 'center', marginTop: 24 }}>This lesson is currently empty.</div>;
  }

  return (
    <div style={{ marginTop: 24 }}>
      {isEditor && (
         <div className="row" style={{ justifyContent: 'space-between', marginBottom: 24, paddingBottom: 16, borderBottom: '1px solid var(--border)' }}>
            <h3 style={{ margin: 0 }}>Block Editor</h3>
            <button className={`btn btn-sm ${isEditing ? 'btn-primary' : 'btn-ghost'}`} onClick={() => setIsEditing(!isEditing)}>
              <Icon name="pen" size={14} /> {isEditing ? 'Done Editing' : 'Edit Blocks'}
            </button>
         </div>
      )}

      <div className="stack">
        {blocks.map((b, i) => (
          <div key={b.id} style={{ position: 'relative', padding: isEditing ? 16 : 0, background: isEditing ? 'var(--surface-1)' : 'transparent', border: isEditing ? '1px dashed var(--border)' : 'none', borderRadius: 8, marginBottom: isEditing ? 16 : 0 }}>
             
             {isEditing ? (
               <div className="stack gap-10">
                 <div className="row gap-10" style={{ justifyContent: 'space-between' }}>
                    <span className="eyebrow">{b.type.toUpperCase()} BLOCK</span>
                    <div className="row gap-6">
                      <button className="btn btn-soft btn-sm" disabled={i===0} onClick={() => moveBlock(i, -1)}><Icon name="chevdown" size={14} style={{ transform: 'rotate(180deg)' }} /></button>
                      <button className="btn btn-soft btn-sm" disabled={i===blocks.length-1} onClick={() => moveBlock(i, 1)}><Icon name="chevdown" size={14} /></button>
                      <button className="btn btn-soft btn-sm" style={{ color: '#e74c3c' }} onClick={() => deleteBlock(i)}><Icon name="minus" size={14} /></button>
                    </div>
                 </div>
                 {(b.type === 'heading' || b.type === 'text' || b.type === 'callout') ? (
                   <textarea className="card-pad" style={{ width: '100%', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', resize: 'vertical', fontSize: b.type === 'heading' ? 20 : 16, fontWeight: b.type === 'heading' ? 600 : 400 }} value={b.content} onChange={e => updateBlock(b.id, { content: e.target.value })} rows={b.type === 'text' ? 4 : 1} />
                 ) : b.type === 'pdf' ? (
                   <div className="stack gap-6">
                     <input className="card-pad" style={{ width: '100%', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)' }} value={b.content} onChange={e => updateBlock(b.id, { content: e.target.value })} placeholder="Display label (e.g. Module 1 Handout.pdf)" />
                     <input className="card-pad" style={{ width: '100%', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)' }} value={b.url || ''} onChange={e => updateBlock(b.id, { url: e.target.value })} placeholder="URL (Supabase storage link or external URL)" />
                   </div>
                 ) : b.type === 'video' ? (
                   <div className="stack gap-6">
                     <input className="card-pad" style={{ width: '100%', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)' }} value={b.content} onChange={e => updateBlock(b.id, { content: e.target.value })} placeholder="YouTube URL, Vimeo URL, or direct MP4 link" />
                     <input className="card-pad" style={{ width: '100%', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)' }} value={b.caption || ''} onChange={e => updateBlock(b.id, { caption: e.target.value })} placeholder="Optional caption" />
                     {b.content && <VideoPlayer src={b.content} title={b.caption} />}
                   </div>
                 ) : b.type === 'image' ? (
                   <div className="stack gap-6">
                     <input className="card-pad" style={{ width: '100%', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)' }} value={b.content} onChange={e => updateBlock(b.id, { content: e.target.value })} placeholder="Image URL or Supabase storage path" />
                     <input className="card-pad" style={{ width: '100%', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)' }} value={b.caption || ''} onChange={e => updateBlock(b.id, { caption: e.target.value })} placeholder="Alt text / caption" />
                     {b.content && <img src={b.content} alt={b.caption || ''} style={{ maxWidth: '100%', borderRadius: 8, border: '1px solid var(--border)', marginTop: 8 }} />}
                   </div>
                 ) : b.type === 'mcq' ? (
                   <div className="stack gap-10">
                      <input className="card-pad" style={{ width: '100%', background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', fontWeight: 600 }} value={b.content} onChange={e => updateBlock(b.id, { content: e.target.value })} placeholder="Question text" />
                      {(b.options || []).map((opt, j) => (
                        <div key={j} className="row gap-6">
                          <input className="card-pad" style={{ flex: 1, background: 'var(--bg)', border: '1px solid var(--border)', color: 'var(--text)', padding: '10px 14px' }} value={opt} onChange={e => { const newOpts = [...b.options]; newOpts[j] = e.target.value; updateBlock(b.id, { options: newOpts }); }} />
                          <button className="btn btn-soft btn-sm" onClick={() => { const newOpts = [...b.options]; newOpts.splice(j, 1); updateBlock(b.id, { options: newOpts }); }}><Icon name="minus" size={14} /></button>
                        </div>
                      ))}
                      <button className="btn btn-ghost btn-sm" style={{ alignSelf: 'flex-start' }} onClick={() => { const newOpts = [...b.options, 'New Option']; updateBlock(b.id, { options: newOpts }); }}><Icon name="plus" size={14} /> Add Option</button>
                   </div>
                 ) : null}
               </div>
             ) : renderBlock(b, i)}

          </div>
        ))}
      </div>

      {isEditing && (
        <div className="card card-pad" style={{ marginTop: 16, background: 'var(--surface-2)', border: '1px dashed var(--border)' }}>
          <p className="muted" style={{ fontSize: 12.5, fontWeight: 800, letterSpacing: '.08em', textTransform: 'uppercase', marginBottom: 10 }}>Add block</p>
          <div className="row gap-10" style={{ flexWrap: 'wrap' }}>
            <button className="btn btn-soft btn-sm" onClick={() => addBlock('heading')}><Icon name="pen" size={16} /> Heading</button>
            <button className="btn btn-soft btn-sm" onClick={() => addBlock('text')}><Icon name="pen" size={16} /> Text</button>
            <button className="btn btn-soft btn-sm" onClick={() => addBlock('callout')}><Icon name="target" size={16} /> Callout</button>
            <button className="btn btn-soft btn-sm" onClick={() => addBlock('mcq')}><Icon name="quiz" size={16} /> MCQ</button>
            <button className="btn btn-soft btn-sm" onClick={() => addBlock('pdf')}><Icon name="book" size={16} /> PDF/Link</button>
            <button className="btn btn-soft btn-sm" onClick={() => addBlock('video')}><Icon name="play" size={16} /> Video</button>
            <button className="btn btn-soft btn-sm" onClick={() => addBlock('image')}><Icon name="layers" size={16} /> Image</button>
          </div>
        </div>
      )}
    </div>
  );
}

function renderTool(tool, lesson, go, onComplete) {
  switch (tool) {
    case 'blank': return <BlockBuilder lesson={lesson} />;
    case 'theory': return <Theory lesson={lesson} />;
    case 'flashcards': return <Flashcards />;
    case 'hotspot': return <Hotspot />;
    case 'quiz': return <Quiz />;
    case 'journal': return <Journal id={lesson.id} prompt={PROMPTS[lesson.id] || "What is the most useful idea you'll take from this lesson, and how will you use it?"} />;
    case 'dragsort': return <DragSort />;
    case 'storyboard': return <Storyboard />;
    case 'discussion': return <Discussion id={lesson.id} />;
    case 'aichat': return <AIChat />;
    case 'live': return <LiveCallout lesson={lesson} go={go} />;
    case 'video': return <LessonVideo lesson={lesson} onFirstPlay={onComplete} />;
    case 'upload': return <FileUpload lessonId={lesson.id} label="Submit your assignment" hint="Upload a PDF, Word, or PowerPoint file — up to 50 MB" onComplete={onComplete} />;
    case 'video-upload': return <VideoUpload lessonId={lesson.id} onComplete={onComplete} />;
    default: return null;
  }
}

function LessonPage({ id, go, layout }) {
  const E = window.EDSTUTIA;
  const mod = E.modules.find(m => m.lessons && m.lessons.some(l => l.id === id));
  
  if (!mod) {
    // Fallback if the lesson doesn't exist (e.g. deleted or old cached route)
    setTimeout(() => go({ name: 'dashboard' }), 0);
    return null;
  }

  const lesson = mod.lessons.find(l => l.id === id);
  if (!lesson) return null;
  
  const idx = mod.lessons.indexOf(lesson);
  const next = mod.lessons[idx + 1] || null;
  const [active, setActive] = lState((lesson.tools || [])[0]);
  const refs = lRef({});
  const [complete, setComplete] = lState(lesson.status === 'completed');
  const [pendingSurveys, setPendingSurveys] = lState([]);
  const startTimeRef = lRef(Date.now());
  const scrollSentinel = lRef(null);

  // Classify lesson for auto-completion behaviour
  const blocks = lesson.content_blocks || [];
  const hasVideoBlock = blocks.some(b => ['video', 'instructor-intros'].includes(b.type)) || !!lesson.video_url;
  const hasUploadBlock = blocks.some(b => ['upload', 'video-upload'].includes(b.type))
    || (lesson.tools || []).some(t => ['upload', 'video-upload'].includes(t));
  const allCourseLessonsForGate = (window.EDSTUTIA.modules || []).flatMap(m => m.lessons || []);
  const surveyGateLessonForGate = allCourseLessonsForGate.find(l =>
    l.kind === 'survey-gate' || l.title?.toLowerCase().includes('pre-course survey')
  );
  const isSurveyLessonLocal = surveyGateLessonForGate ? surveyGateLessonForGate.id === id : false;
  const isTextLesson = !hasVideoBlock && !hasUploadBlock && !isSurveyLessonLocal && blocks.length > 0;
  const isAutoLesson = hasVideoBlock || hasUploadBlock || isTextLesson;

  /* Pre-confidence: disabled — edlearners don't get this popup */
  lEffect(() => {
    if (!window.supabaseClient || !window.ME_UUID) return;
    const E = window.EDSTUTIA;
    const _role = E?.learner?.role;
    return; // disabled for all roles
    const allCourseLessons = (E.modules || [])
      .filter(m => !m.status || m.status === 'published' || String(m.id).match(/^m\d/))
      .flatMap(m => (m.lessons || []).filter(l => l.status !== 'draft'));
    if (!allCourseLessons.length || allCourseLessons[0].id !== id) return;

    (async () => {
      const { data } = await window.supabaseClient
        .from('confidence_checks')
        .select('id')
        .eq('user_id', window.ME_UUID)
        .eq('module_id', 'course')
        .eq('type', 'pre')
        .maybeSingle();
      if (!data) {
        setTimeout(() => setPendingSurveys([{
          type: 'confidence', checkType: 'pre', moduleId: 'course', moduleTitle: null,
        }]), 900);
      }
    })();
  }, [id]);

  const toggleComplete = async () => {
    const nextVal = !complete;
    setComplete(nextVal);
    lesson.status = nextVal ? 'completed' : 'in-progress';
    lesson.progress = nextVal ? 100 : 50;
    // Immediately unlock the next lesson in-memory so the curriculum gate sees it
    if (nextVal && next && next.status === 'locked') next.status = 'published';
    // Synchronous flag so Curriculum gate doesn't race with DB upsert
    if (nextVal) window.EDSTUTIA._completedIds = (window.EDSTUTIA._completedIds || new Set()).add(id);
    else window.EDSTUTIA._completedIds?.delete(id);

    const elapsed = Math.round((Date.now() - startTimeRef.current) / 1000);
    startTimeRef.current = Date.now();

    if (window.supabaseClient && window.ME_UUID) {
      await window.supabaseClient.from('lesson_progress').upsert({
        user_id: window.ME_UUID,
        lesson_id: id,
        status: lesson.status,
        progress: lesson.progress,
        time_spent_seconds: elapsed,
        completed_at: nextVal ? new Date().toISOString() : null,
      }, { onConflict: 'user_id, lesson_id' });
    }

    if (nextVal) {
      const _role = window.EDSTUTIA?.learner?.role;
      const _isEdLearner = !['editor', 'staff'].includes(_role);
      if (_isEdLearner) {
        const queue = [];
        const E = window.EDSTUTIA;

        /* Fire module review only when the LAST lesson of the module is completed */
        const sortedModLessons = [...(mod.lessons || [])].sort((a, b) => (a.position ?? 9999) - (b.position ?? 9999) || new Date(a.created_at) - new Date(b.created_at));
        const lastLesson = sortedModLessons[sortedModLessons.length - 1];
        const isLastLesson = lastLesson && String(lastLesson.id) === String(id);

        if (isLastLesson && window.supabaseClient && window.ME_UUID) {
          const [{ data: confDone }, { data: pulseDone }] = await Promise.all([
            window.supabaseClient.from('confidence_checks').select('id')
              .eq('user_id', window.ME_UUID).eq('module_id', String(mod.id)).eq('type', 'post').maybeSingle(),
            window.supabaseClient.from('experience_responses').select('id')
              .eq('user_id', window.ME_UUID).eq('module_id', String(mod.id)).eq('response_type', 'module_satisfaction').maybeSingle(),
          ]);
          if (!pulseDone) queue.push({ type: 'module_pulse', moduleId: mod.id, moduleTitle: mod.title });
          if (!confDone)  queue.push({ type: 'confidence', checkType: 'post', moduleId: mod.id, moduleTitle: mod.title });
        }

        /* NPS + overall satisfaction only fire when the FULL COURSE is complete */
        const allCourseLessons = (E.modules || []).flatMap(m => m.lessons || []);
        const courseComplete = allCourseLessons.length > 0 && allCourseLessons.every(l => l.status === 'completed');

        if (courseComplete && window.supabaseClient && window.ME_UUID) {
          const [{ data: npsDone }, { data: satDone }] = await Promise.all([
            window.supabaseClient.from('experience_responses').select('id')
              .eq('user_id', window.ME_UUID).eq('module_id', 'course').eq('response_type', 'nps').maybeSingle(),
            window.supabaseClient.from('experience_responses').select('id')
              .eq('user_id', window.ME_UUID).eq('module_id', 'course').eq('response_type', 'satisfaction').maybeSingle(),
          ]);
          if (!npsDone) queue.push({ type: 'nps', moduleId: 'course' });
          if (!satDone) queue.push({ type: 'course_satisfaction', moduleId: 'course' });
        }

        setPendingSurveys(queue);
      }
    }
  };

  const autoComplete = () => { if (!complete) toggleComplete(); };

  // Scroll-to-bottom auto-complete for text/reading lessons
  lEffect(() => {
    if (!isTextLesson || complete) return;
    const el = scrollSentinel.current;
    if (!el) return;
    const obs = new IntersectionObserver(([entry]) => { if (entry.isIntersecting) autoComplete(); }, { threshold: 0.1 });
    obs.observe(el);
    return () => obs.disconnect();
  }, [isTextLesson, complete]);

  const sections = lesson.tools || [];
  const jump = (t) => {
    const el = refs.current[t];
    if (el) { setActive(t); window.scrollTo({ top: el.getBoundingClientRect().top + window.scrollY - 84, behavior: 'smooth' }); }
  };
  lEffect(() => {
    const onScroll = () => {
      let cur = sections[0];
      for (const t of sections) { const el = refs.current[t]; if (el && el.getBoundingClientRect().top < 160) cur = t; }
      setActive(cur);
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    return () => window.removeEventListener('scroll', onScroll);
  }, [id]);

  const guided = layout === 'guided';
  const isEditor = E && E.learner && ['editor', 'staff'].includes(E.learner.role);
  const isEdLearner = !isEditor;
  const hasBlocks = lesson.content_blocks && lesson.content_blocks.length > 0;

  const Header = (
    <div className="card" style={{ overflow: 'hidden', marginBottom: 6 }}>
      <div style={{ background: 'var(--brand-ink)', color: '#fff', padding: '26px 28px' }}>
        <div className="row" style={{ justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 16 }}>
          <button className="row gap-6" onClick={() => go({ name: 'curriculum', focus: mod.id })}
            style={{ border: 'none', background: 'none', padding: 0, margin: 0, font: 'inherit', color: 'rgba(255,255,255,.55)', textAlign: 'left', cursor: 'pointer', fontWeight: 600, fontSize: 13, letterSpacing: '.01em' }}>
            <Icon name="chevron" size={14} style={{ transform: 'rotate(180deg)' }} /> {mod.title}
          </button>
          {isEditor && (
            <button onClick={() => go({ name: 'editor', id: lesson.id })}
              style={{ display: 'flex', alignItems: 'center', gap: 7, padding: '7px 14px', borderRadius: 9, background: 'rgba(255,255,255,.12)', border: '1px solid rgba(255,255,255,.2)', color: '#fff', fontWeight: 700, fontSize: 13, cursor: 'pointer', flex: 'none', transition: 'background .15s' }}
              onMouseEnter={e => e.currentTarget.style.background = 'rgba(255,255,255,.2)'}
              onMouseLeave={e => e.currentTarget.style.background = 'rgba(255,255,255,.12)'}>
              <Icon name="pen" size={15} /> Edit lesson
            </button>
          )}
        </div>
        <h1 style={{ fontSize: 34, color: '#fff', marginBottom: lesson.summary ? 8 : 16 }}>{lesson.title}</h1>
        {lesson.summary && <p style={{ fontSize: 17, color: 'rgba(255,255,255,.72)', marginBottom: 16, maxWidth: 680, lineHeight: 1.55 }}>{lesson.summary}</p>}
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 13, fontWeight: 600, color: 'rgba(255,255,255,.55)' }}>
          <Icon name="clock" size={13} /> {lesson.minutes} min
        </span>
      </div>
      <div className="row" style={{ padding: '12px 28px', justifyContent: 'flex-end' }}>
        <span className="row gap-6" style={{ fontWeight: 700, fontSize: 13.5, color: complete ? 'var(--lime-strong)' : 'var(--text-muted)' }}>
          {complete && <Icon name="check" size={15} />}{complete ? 'Completed' : 'In progress'}
        </span>
      </div>
    </div>
  );

  const Body = (
    <div>
      {hasBlocks ? (
        /* ── New block-based lesson ── */
        <div style={{ marginTop: 8 }}>
          {lesson.content_blocks.map(b => renderContentBlock(b, lesson.id, { onComplete: autoComplete }))}
          {isTextLesson && !complete && <div ref={scrollSentinel} style={{ height: 1, marginTop: 8 }} />}
        </div>
      ) : (
        /* ── Legacy tool-based lesson ── */
        sections.map(t => (
          <div key={t} ref={el => (refs.current[t] = el)} style={{ scrollMarginTop: 84 }}>
            {renderTool(t, lesson, go, autoComplete)}
          </div>
        ))
      )}
      {/* Practical application — legacy tool-based lessons only */}
      {!hasBlocks && lesson.practical && (
        <section className="card card-pad enter" style={{ marginTop: 22, background: 'var(--lime-soft)', borderColor: 'transparent' }}>
          <div className="row gap-10" style={{ marginBottom: 8 }}>
            <Icon name="flask" size={24} style={{ color: 'var(--lime-strong)' }} />
            <h3 style={{ fontSize: 21 }}>Practical application</h3>
          </div>
          <p style={{ fontSize: 17.5, lineHeight: 1.6 }}>{lesson.practical}</p>
        </section>
      )}
      {/* Footer nav */}
      {(() => {
        const allCourseLessons = (E.modules || []).flatMap(m => m.lessons || []);
        const surveyGateLesson = allCourseLessons.find(l =>
          l.kind === 'survey-gate' || l.title?.toLowerCase().includes('pre-course survey') || l.title?.toLowerCase().includes('pre course survey')
        );
        const isSurveyLesson = surveyGateLesson ? surveyGateLesson.id === id : false;

        // Survey lesson — manual confirmation required
        if (isSurveyLesson && !complete) {
          return (
            <div className="card card-pad" style={{ marginTop: 22 }}>
              <div style={{ padding: '18px 20px', background: 'rgba(234,179,8,.07)', border: '1px solid rgba(234,179,8,.3)', borderRadius: 12, marginBottom: 16, display: 'flex', gap: 14, alignItems: 'flex-start' }}>
                <span style={{ fontSize: 22, flex: 'none', marginTop: 2 }}>📋</span>
                <div>
                  <p style={{ margin: 0, fontWeight: 700, fontSize: 15 }}>Survey required before continuing</p>
                  <p style={{ margin: '4px 0 0', fontSize: 14, color: 'var(--text-muted)', lineHeight: 1.55 }}>
                    Fill in the survey above, then click the button below to confirm completion and unlock the next module. Your responses are saved automatically in Google Forms.
                  </p>
                </div>
              </div>
              <button className="btn btn-primary btn-lg" style={{ width: '100%', justifyContent: 'center', fontSize: 16 }} onClick={toggleComplete}>
                <Icon name="check" size={20} /> I've completed the survey — unlock next module
              </button>
            </div>
          );
        }

        // Video lesson not yet watched — gate next lesson
        if (hasVideoBlock && !complete) {
          return (
            <div className="card card-pad row" style={{ marginTop: 22, justifyContent: 'space-between', flexWrap: 'wrap', gap: 12 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 16px', borderRadius: 10, background: 'rgba(234,179,8,.07)', border: '1px solid rgba(234,179,8,.25)', color: 'var(--text-muted)', fontSize: 14, fontWeight: 600 }}>
                <Icon name="play" size={16} /> Watch the video above to continue
              </div>
            </div>
          );
        }

        // Exercise lesson not yet submitted — gate next lesson
        if (hasUploadBlock && !complete) {
          return (
            <div className="card card-pad row" style={{ marginTop: 22, justifyContent: 'space-between', flexWrap: 'wrap', gap: 12 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 16px', borderRadius: 10, background: 'rgba(234,179,8,.07)', border: '1px solid rgba(234,179,8,.25)', color: 'var(--text-muted)', fontSize: 14, fontWeight: 600 }}>
                <Icon name="flag" size={16} /> Submit your work above to continue
              </div>
            </div>
          );
        }

        // Completed state indicator for auto-complete lessons
        const leftSlot = complete ? (
          <span className="row gap-6" style={{ fontWeight: 700, fontSize: 14, color: 'var(--lime-strong)' }}>
            <Icon name="check" size={16} /> {isSurveyLesson ? 'Survey completed' : 'Completed'}
          </span>
        ) : !isAutoLesson ? (
          // Non-auto lesson (e.g. AI chat only) — keep manual button
          <button className="btn btn-ghost btn-lg" onClick={toggleComplete}>
            <Icon name="flag" size={19} /> Mark lesson complete
          </button>
        ) : null;

        const nextBtn = next
          ? <button className="btn btn-primary btn-lg" onClick={() => go({ name: 'lesson', id: next.id })}>Next: {next.title} <Icon name="arrow" size={19} /></button>
          : <button className="btn btn-primary btn-lg" onClick={() => go({ name: 'curriculum' })}>Back to curriculum <Icon name="arrow" size={19} /></button>;

        return (
          <div className="card card-pad row" style={{ marginTop: 22, justifyContent: leftSlot ? 'space-between' : 'flex-end', flexWrap: 'wrap', gap: 12 }}>
            {leftSlot}
            {nextBtn}
          </div>
        );
      })()}
      {/* Grade card — only visible when a published grade exists */}
      {(() => { const GC = window.MyGradeCard; return GC ? <GC lessonId={id} /> : null; })()}
    </div>
  );

  const Toc = (
    <nav style={{ position: 'sticky', top: 84 }}>
      <p className="nav-label" style={{ padding: '0 6px 8px' }}>On this page</p>
      <div className="col" style={{ gap: 2 }}>
        {sections.map(t => (
          <button key={t} onClick={() => jump(t)} className="row gap-10"
            style={{ border: 'none', background: 'none', padding: 0, margin: 0, font: 'inherit', color: 'inherit', textAlign: 'left', cursor: 'pointer', padding: '9px 12px', borderRadius: 10, fontWeight: 700, fontSize: 14.5,
              background: active === t ? 'var(--lime-soft)' : 'transparent', color: active === t ? 'var(--lime-strong)' : 'var(--text-muted)' }}>
            <Icon name={TOC_ICON[t]} size={17} /> {TOC_LABEL[t]}
          </button>
        ))}
      </div>
    </nav>
  );

  const SurveyQueueComp = window.SurveyQueue;

  return (
    <div className={'page enter' + (guided ? ' page-wide' : '')}>
      {pendingSurveys.length > 0 && SurveyQueueComp && (
        <SurveyQueueComp surveys={pendingSurveys} onComplete={() => setPendingSurveys([])} />
      )}
      {guided ? (
        <div className="grid" style={{ gridTemplateColumns: '220px 1fr', gap: 30, alignItems: 'start' }}>
          <div>{Toc}</div>
          <div style={{ minWidth: 0 }}>{Header}{Body}</div>
        </div>
      ) : (
        <div style={{ maxWidth: 760, margin: '0 auto' }}>{Header}{Body}</div>
      )}
    </div>
  );
}

/* ---------- Lesson templates used in Add Lesson modal ---------- */
const LESSON_TEMPLATES = [
  { id: 'blank',        icon: 'plus',    label: 'Blank',        desc: 'Start with an empty block editor' },
  { id: 'video',        icon: 'play',    label: 'Video Lesson', desc: 'Pre-seeded with a video block' },
  { id: 'quiz',         icon: 'target',  label: 'Quiz',         desc: 'Multiple-choice assessment' },
  { id: 'reading',      icon: 'book',    label: 'Reading',      desc: 'Headings, text, and callouts' },
  { id: 'announcement', icon: 'bell',    label: 'Announcement', desc: 'Highlighted notice for learners' },
];

const TEMPLATE_BLOCKS = {
  blank:        [],
  video:        [{ type: 'video', url: '', caption: '' }, { type: 'text', text: 'Add notes or context below.' }],
  quiz:         [{ type: 'heading', text: 'Knowledge Check', level: 2 }, { type: 'text', text: 'Answer the following questions.' }, { type: 'mcq', question: '', options: ['Option A', 'Option B', 'Option C', 'Option D'], correct: 0, explanation: '' }],
  reading:      [{ type: 'heading', text: '', level: 2 }, { type: 'text', text: '' }, { type: 'callout', variant: 'tip', text: '' }],
  announcement: [{ type: 'callout', variant: 'info', text: '' }, { type: 'text', text: '' }],
};

/* ---------- Curriculum overview ---------- */
function statusOf(l) { return l.status; }
function Curriculum({ go, focus }) {
  const E = window.EDSTUTIA;
  const role = E?.learner?.role;
  const isEditor = ['editor', 'staff'].includes(role);
  const isEdLearner = !isEditor; // all non-admin roles: edlearner, learner, client, etc.
  const [openModules, setOpenModules] = lState(focus ? [focus] : (E.modules && E.modules.length ? [E.modules[0].id] : []));
  const [modules, setModules] = lState(E.modules || []);
  const [modal, setModal] = lState(null);
  const [menuOpen, setMenuOpen] = lState(null);
  const [loadingModules, setLoadingModules] = lState((isEditor || isEdLearner) && !!window.supabaseClient);
  const [completedIds, setCompletedIds] = lState(window.EDSTUTIA._completedIds || new Set());

  /* EdLearner: pure Supabase fetch — published lessons only, sequential lock per module */
  lEffect(() => {
    if (!window.supabaseClient || !isEdLearner) return;
    (async () => {
      const [{ data: rawMods }, { data: prog }] = await Promise.all([
        window.supabaseClient.from('modules').select('*, lessons(*)').order('num', { ascending: true }),
        window.ME_UUID
          ? window.supabaseClient.from('lesson_progress').select('lesson_id').eq('user_id', window.ME_UUID).eq('status', 'completed')
          : Promise.resolve({ data: [] }),
      ]);
      const doneIds = new Set((prog || []).map(r => r.lesson_id));
      setCompletedIds(doneIds);
      window.EDSTUTIA._completedIds = doneIds;
      if (!rawMods || !rawMods.length) { setLoadingModules(false); return; }

      const processed = rawMods.map(m => {
        const publishedLessons = [...(m.lessons || [])]
          .filter(l => l.status === 'published')
          .sort((a, b) => (a.position ?? 9999) - (b.position ?? 9999) || new Date(a.created_at) - new Date(b.created_at))
          .map((l, i, arr) => {
            const staticCb = _STATIC_BLOCKS[l.title];
            let cb = l.content_blocks;
            if (staticCb && staticCb.length) {
              const sbTypes = new Set((l.content_blocks || []).map(b => b.type));
              if (staticCb.some(b => !sbTypes.has(b.type))) cb = staticCb;
            }
            const base = (cb && cb !== l.content_blocks) ? { ...l, content_blocks: cb } : l;
            if (doneIds.has(l.id)) return { ...base, status: 'completed' };
            if (i === 0) return base;
            return doneIds.has(arr[i - 1].id) ? base : { ...base, status: 'locked' };
          });
        return { ...m, lessons: publishedLessons };
      }).filter(m => m.lessons.length > 0);

      setModules(processed);
      window.EDSTUTIA.modules = processed;
      if (!openModules.length && processed.length) setOpenModules([processed[0].id]);
      setLoadingModules(false);
    })();
  }, []);

  /* Editors: always fetch live from Supabase so drafts survive logout/login */
  lEffect(() => {
    if (!isEditor || !window.supabaseClient) return;
    (async () => {
      setLoadingModules(true);
      const { data } = await window.supabaseClient
        .from('modules')
        .select('*, lessons(*)')
        .order('num', { ascending: true });
      if (data) {
        const sorted = data.map(m => ({
          ...m,
          lessons: [...(m.lessons || [])].sort((a, b) =>
            (a.position ?? 9999) - (b.position ?? 9999) ||
            new Date(a.created_at) - new Date(b.created_at)
          ),
        }));
        // Deduplicate by num — keep only the first (lowest id alphabetically = earliest) per num
        const seenNums = new Map();
        for (const m of sorted) {
          if (!seenNums.has(m.num)) seenNums.set(m.num, m);
        }
        const dedupedSorted = Array.from(seenNums.values());
        // For each Supabase module, merge in static lessons that are genuinely new:
        // - title doesn't already exist in the Supabase module, AND
        // - lesson has content_blocks with a block type not used anywhere in the module
        // (prevents re-adding replaced static lessons while still surfacing new ones like instructor-intros)
        const staticByNum = {};
        (E.modules || []).forEach(m => { if (isStatic(m.id)) staticByNum[m.num] = m; });
        const enriched = dedupedSorted.map(sbMod => {
          const staticMod = staticByNum[sbMod.num];
          if (!staticMod) return sbMod;
          const sbTitles = new Set((sbMod.lessons || []).map(l => l.title?.toLowerCase().trim()));
          const allSbBlockTypes = new Set((sbMod.lessons || []).flatMap(l => (l.content_blocks || []).map(b => b.type)));
          const missing = (staticMod.lessons || []).filter(l => {
            if (sbTitles.has(l.title?.toLowerCase().trim())) return false;
            const types = (l.content_blocks || []).map(b => b.type);
            return types.length > 0 && types.some(t => !allSbBlockTypes.has(t));
          });
          if (!missing.length) return sbMod;
          const allLessons = [...missing, ...(sbMod.lessons || [])].sort((a, b) =>
            (a.num ?? 9999) - (b.num ?? 9999) || (a.position ?? 9999) - (b.position ?? 9999));
          return { ...sbMod, lessons: allLessons };
        });
        // Exclude static modules whose num already exists in Supabase
        const supabaseNums = new Set(enriched.map(m => m.num));
        const staticMods = (E.modules || []).filter(m => String(m.id).match(/^m\d/) && !supabaseNums.has(m.num));
        const merged = [...enriched, ...staticMods];
        setModules(merged);
        window.EDSTUTIA.modules = merged;
        if (!openModules.length && merged.length) setOpenModules([merged[0].id]);

        // Repair Supabase lessons whose content_blocks are missing block types that static data has
        // (catches lessons inserted with wrong/empty blocks from earlier attempts)
        for (const sbMod of enriched) {
          const staticMod = staticByNum[sbMod.num];
          if (!staticMod) continue;
          for (const sbLesson of (sbMod.lessons || [])) {
            if (isStatic(sbLesson.id)) continue;
            const staticLesson = (staticMod.lessons || []).find(l =>
              l.title?.toLowerCase().trim() === sbLesson.title?.toLowerCase().trim()
            );
            if (!staticLesson?.content_blocks?.length) continue;
            const sbTypes = new Set((sbLesson.content_blocks || []).map(b => b.type));
            const needsRepair = staticLesson.content_blocks.some(b => !sbTypes.has(b.type));
            if (!needsRepair) continue;
            // Replace content_blocks with the canonical static version
            window.supabaseClient.from('lessons').update({ content_blocks: staticLesson.content_blocks }).eq('id', sbLesson.id);
            setModules(ms => {
              const n = ms.map(m => m.id !== sbMod.id ? m : {
                ...m, lessons: (m.lessons || []).map(ll =>
                  ll.id === sbLesson.id ? { ...ll, content_blocks: staticLesson.content_blocks } : ll
                )
              });
              window.EDSTUTIA.modules = n;
              return n;
            });
          }
        }

        // Auto-persist any merged static lessons into Supabase so they survive navigation
        // Uses a session-scoped guard to prevent duplicate inserts on rapid remounts
        window._staticInsertGuard = window._staticInsertGuard || new Set();
        for (const sbMod of enriched) {
          const pendingStatic = (sbMod.lessons || []).filter(l => isStatic(l.id));
          for (const l of pendingStatic) {
            const key = sbMod.id + ':' + l.title;
            if (window._staticInsertGuard.has(key)) continue;
            window._staticInsertGuard.add(key);
            const minPos = (sbMod.lessons || [])
              .filter(ll => !isStatic(ll.id))
              .reduce((m, ll) => Math.min(m, ll.position ?? 0), 0);
            window.supabaseClient.from('lessons').insert({
              module_id: sbMod.id,
              title: l.title,
              type: l.type || 'ASYNCHRONOUS',
              status: l.status || 'draft',
              minutes: l.minutes || 15,
              position: minPos - 1,
              content_blocks: l.content_blocks || [],
            }).select().single().then(({ data: nl }) => {
              if (!nl) { window._staticInsertGuard.delete(key); return; }
              // Swap the static ID for the real Supabase UUID in state
              setModules(ms => {
                const n = ms.map(m => m.id !== sbMod.id ? m : {
                  ...m, lessons: (m.lessons || []).map(ll => ll.id === l.id ? { ...ll, id: nl.id } : ll)
                });
                window.EDSTUTIA.modules = n;
                return n;
              });
            });
          }
        }
      }
      setLoadingModules(false);
    })();
  }, []);

  lEffect(() => { if (focus && !openModules.includes(focus)) setOpenModules([...openModules, focus]); }, [focus]);

  const isStatic = (id) => String(id).match(/^m/);

  // Transparently migrate a static demo module (and all its lessons) into Supabase.
  // Returns the migrated module with real UUIDs, or null on failure.
  const migrateStaticModule = async (staticMod) => {
    if (!window.supabaseClient || !isStatic(staticMod.id)) return staticMod;
    // Check if already migrated by num (handles repeated calls after prior migration)
    const { data: existing } = await window.supabaseClient
      .from('modules').select('id, title, num, status, kind, lessons(*)').eq('num', staticMod.num).maybeSingle();
    if (existing) {
      const existingLessons = (existing.lessons || []).map(l => ({ ...l }));
      const migrated = { ...staticMod, id: existing.id, lessons: existingLessons.length ? existingLessons : staticMod.lessons };
      setModules(ms => { const n = ms.map(m => m.id === staticMod.id ? migrated : m); window.EDSTUTIA.modules = n; return n; });
      return migrated;
    }
    const { data: newMod, error } = await window.supabaseClient
      .from('modules')
      .insert({ title: staticMod.title, num: staticMod.num, status: staticMod.status || 'draft', kind: staticMod.kind || 'ASYNCHRONOUS' })
      .select().single();
    if (error || !newMod) return null;
    const newLessons = [];
    for (let i = 0; i < (staticMod.lessons || []).length; i++) {
      const l = staticMod.lessons[i];
      const { data: nl } = await window.supabaseClient
        .from('lessons')
        .insert({ module_id: newMod.id, title: l.title, type: l.type || 'ASYNCHRONOUS', status: 'draft', minutes: l.minutes || 15, position: i, content_blocks: l.content_blocks || [] })
        .select().single();
      if (nl) newLessons.push({ ...l, id: nl.id, module_id: newMod.id });
    }
    const migrated = { ...staticMod, id: newMod.id, lessons: newLessons };
    setModules(ms => { const n = ms.map(m => m.id === staticMod.id ? migrated : m); window.EDSTUTIA.modules = n; return n; });
    return migrated;
  };

  const [draggedIdx, setDraggedIdx] = lState(null);
  const [dragOverIdx, setDragOverIdx] = lState(null);
  const [renamingLesson, setRenamingLesson] = lState(null); // { id, value }

  const startLessonRename = (l) => { setMenuOpen(null); setRenamingLesson({ id: l.id, value: l.title, originalTitle: l.title }); };
  const saveLessonRename = async () => {
    if (!renamingLesson) return;
    let { id, value } = renamingLesson;
    const trimmed = value.trim();
    if (!trimmed) { setRenamingLesson(null); return; }
    if (isStatic(id)) {
      const parentMod = modules.find(m => (m.lessons || []).some(l => l.id === id));
      if (parentMod) {
        const migrated = await migrateStaticModule(parentMod);
        if (migrated) id = migrated.lessons.find(l => l.title === renamingLesson.originalTitle || l.title === value)?.id || id;
      }
    }
    const newMods = modules.map(m => ({ ...m, lessons: (m.lessons || []).map(l => l.id === id ? { ...l, title: trimmed } : l) }));
    setModules(newMods);
    window.EDSTUTIA.modules = newMods;
    setRenamingLesson(null);
    if (window.supabaseClient && !isStatic(id)) await window.supabaseClient.from('lessons').update({ title: trimmed }).eq('id', id);
  };

  const handleDragStart = (e, index) => {
    setDraggedIdx(index);
    e.dataTransfer.effectAllowed = 'move';
  };

  const handleDragOver = (e, index) => {
    e.preventDefault();
    if (draggedIdx === null || draggedIdx === index) return;
    setDragOverIdx(index);
  };

  const handleDrop = async (e, targetIdx) => {
    e.preventDefault();
    setDragOverIdx(null);
    if (draggedIdx === null || draggedIdx === targetIdx) {
      setDraggedIdx(null);
      return;
    }
    const newMods = [...modules];
    const [moved] = newMods.splice(draggedIdx, 1);
    newMods.splice(targetIdx, 0, moved);
    newMods.forEach((m, i) => m.num = i + 1);
    setModules(newMods);
    window.EDSTUTIA.modules = newMods;
    setDraggedIdx(null);
    if (window.supabaseClient) {
      for (const m of newMods) {
        if (!isStatic(m.id)) await window.supabaseClient.from('modules').update({ num: m.num }).eq('id', m.id);
      }
    }
  };

  /* ── Lesson drag-to-reorder (within a module) ── */
  const [lessonDrag, setLessonDrag] = lState({ from: null, over: null, modId: null });

  const lessonDragStart = (e, lessonId, modId) => {
    e.stopPropagation(); // prevent the module drag from also firing
    setLessonDrag({ from: lessonId, over: null, modId });
    e.dataTransfer.effectAllowed = 'move';
  };
  const lessonDragOver = (e, lessonId) => {
    e.preventDefault();
    e.stopPropagation();
    if (lessonDrag.over !== lessonId) setLessonDrag(s => ({ ...s, over: lessonId }));
  };
  const lessonDragLeave = (e) => {
    e.stopPropagation();
    setLessonDrag(s => ({ ...s, over: null }));
  };
  const lessonDragEnd = () => setLessonDrag({ from: null, over: null, modId: null });

  const lessonDrop = async (e, targetId) => {
    e.preventDefault();
    e.stopPropagation();
    const { from, modId } = lessonDrag;
    setLessonDrag({ from: null, over: null, modId: null });
    if (!from || from === targetId || !modId) return;
    const mod = modules.find(m => m.id === modId);
    if (!mod) return;
    const lessons = [...(mod.lessons || [])];
    const fromIdx = lessons.findIndex(l => l.id === from);
    const toIdx   = lessons.findIndex(l => l.id === targetId);
    if (fromIdx === -1 || toIdx === -1) return;
    lessons.splice(toIdx, 0, lessons.splice(fromIdx, 1)[0]);
    let realModId = modId;
    let realLessons = lessons;
    if (isStatic(modId) || lessons.some(l => isStatic(l.id))) {
      const parentMod = modules.find(m => m.id === modId);
      const migrated = await migrateStaticModule(parentMod);
      if (!migrated) return;
      realModId = migrated.id;
      realLessons = migrated.lessons;
    }
    const newMods = modules.map(m => m.id === realModId ? { ...m, lessons: realLessons } : m);
    setModules(newMods);
    window.EDSTUTIA.modules = newMods;
    if (window.supabaseClient) {
      await Promise.all(realLessons.map((l, i) =>
        window.supabaseClient.from('lessons').update({ position: i }).eq('id', l.id)
      ));
    }
  };

  const submitModal = async (e) => {
    e.preventDefault();
    const fd = new FormData(e.target);
    const title = fd.get('title');
    
    if (modal.type === 'renameModule') {
      let modId = modal.id;
      if (isStatic(modId)) {
        const mod = modules.find(m => m.id === modId);
        const migrated = await migrateStaticModule(mod);
        if (!migrated) { setModal(null); return; }
        modId = migrated.id;
      }
      const newMods = modules.map(m => m.id === modId ? { ...m, title } : m);
      setModules(newMods);
      window.EDSTUTIA.modules = newMods;
      if (window.supabaseClient) await window.supabaseClient.from('modules').update({ title }).eq('id', modId);
    } 
    else if (modal.type === 'addModule') {
      const num = modules.length + 1;
      const newMod = { id: crypto.randomUUID(), title, num, kind: 'standard', status: 'draft', lessons: [] };
      setModules([...modules, newMod]);
      window.EDSTUTIA.modules = [...modules, newMod];
      if (window.supabaseClient) {
        const { data } = await window.supabaseClient.from('modules').insert({ title, num, status: 'draft' }).select().single();
        if (data) {
           const syncMods = modules.map(m => m.id === newMod.id ? { ...m, id: data.id } : m);
           syncMods.push({ ...newMod, id: data.id });
           setModules([...modules, { ...newMod, id: data.id }]);
           window.EDSTUTIA.modules = [...modules, { ...newMod, id: data.id }];
        }
      }
    }
    else if (modal.type === 'addLesson') {
      const template = modal.selectedTemplate || 'blank';

      if (!window.supabaseClient) {
        setModal({ ...modal, error: 'No database connection.' });
        return;
      }

      setModal({ ...modal, saving: true, error: null });
      let moduleId = modal.moduleId;

      if (isStatic(moduleId)) {
        const mod = modules.find(m => m.id === moduleId);
        const migrated = await migrateStaticModule(mod);
        if (!migrated) {
          setModal({ ...modal, error: 'Could not migrate module to database.', saving: false });
          return;
        }
        moduleId = migrated.id;
      }

      const seedBlocks = (TEMPLATE_BLOCKS[template] || []).map(b => ({ ...b, id: crypto.randomUUID() }));

      const { data, error: dbErr } = await window.supabaseClient.from('lessons').insert({
        module_id: moduleId,
        title,
        type: 'ASYNCHRONOUS',
        status: 'draft',
        content_blocks: seedBlocks,
      }).select().single();

      if (dbErr || !data) {
        setModal({ ...modal, error: 'Could not create lesson: ' + (dbErr?.message || 'Unknown error'), saving: false });
        return;
      }

      const newLesson = { id: data.id, title, type: 'ASYNCHRONOUS', status: 'draft', content_blocks: seedBlocks, minutes: 15, summary: '' };
      const newMods = modules.map(m => m.id === moduleId ? { ...m, lessons: [...(m.lessons || []), newLesson] } : m);
      setModules(newMods);
      window.EDSTUTIA.modules = newMods;
      setModal(null);
      go({ name: 'editor', id: data.id });
      return;
    }
    setModal(null);
  };

  const toggleModule = (e, id) => {
    e.stopPropagation();
    if (openModules.includes(id)) {
      setOpenModules(openModules.filter(m => m !== id));
    } else {
      setOpenModules([...openModules, id]);
    }
  };

  const collapseAll = () => setOpenModules([]);
  const expandAll = () => setOpenModules(modules.map(m => m.id));

  const publishAll = async () => {
    const newMods = modules.map(m => ({
      ...m,
      status: 'published',
      lessons: m.lessons ? m.lessons.map(l => ({ ...l, status: 'published' })) : []
    }));
    setModules(newMods);
    window.EDSTUTIA.modules = newMods;
  };

  const togglePublish = async (e, isMod, item, modId) => {
    e.stopPropagation();
    const newStatus = item.status === 'published' ? 'draft' : 'published';
    if (isMod) {
      let id = item.id;
      if (isStatic(id)) {
        const migrated = await migrateStaticModule(item);
        if (!migrated) return;
        id = migrated.id;
      }
      setModules(ms => { const n = ms.map(m => m.id === id ? { ...m, status: newStatus } : m); window.EDSTUTIA.modules = n; return n; });
      if (window.supabaseClient) await window.supabaseClient.from('modules').update({ status: newStatus }).eq('id', id);
    } else {
      let lid = item.id, mid = modId;
      if (isStatic(lid)) {
        const parentMod = modules.find(m => m.id === mid);
        const migrated = await migrateStaticModule(parentMod);
        if (!migrated) return;
        mid = migrated.id;
        lid = migrated.lessons.find(l => l.title === item.title)?.id || lid;
      }
      setModules(ms => { const n = ms.map(m => m.id === mid ? { ...m, lessons: m.lessons.map(l => l.id === lid ? { ...l, status: newStatus } : l) } : m); window.EDSTUTIA.modules = n; return n; });
      if (window.supabaseClient && !isStatic(lid)) await window.supabaseClient.from('lessons').update({ status: newStatus }).eq('id', lid);
    }
  };

  const deleteItem = async (isMod, id, modId) => {
    if (!confirm('Are you sure you want to delete this?')) return;
    if (isMod) {
      const newMods = modules.filter(m => m.id !== id);
      setModules(newMods); window.EDSTUTIA.modules = newMods;
      if (window.supabaseClient && !isStatic(id)) await window.supabaseClient.from('modules').delete().eq('id', id);
    } else {
      const newMods = modules.map(m => m.id === modId ? { ...m, lessons: m.lessons.filter(l => l.id !== id) } : m);
      setModules(newMods); window.EDSTUTIA.modules = newMods;
      if (window.supabaseClient && !isStatic(id)) await window.supabaseClient.from('lessons').delete().eq('id', id);
    }
  };

  const duplicateItem = async (isMod, item, modId) => {
    if (isMod) {
      let srcId = item.id;
      if (isStatic(srcId)) {
        const migrated = await migrateStaticModule(item);
        if (!migrated) return;
        srcId = migrated.id;
      }
      const newMod = { ...item, id: crypto.randomUUID(), title: item.title + ' (Copy)', num: modules.length + 1 };
      const newMods = [...modules, newMod];
      setModules(newMods); window.EDSTUTIA.modules = newMods;
      if (window.supabaseClient) await window.supabaseClient.from('modules').insert({ title: newMod.title, num: newMod.num, status: newMod.status || 'draft' });
    } else {
      let mid = modId;
      if (isStatic(item.id)) {
        const parentMod = modules.find(m => m.id === mid);
        const migrated = await migrateStaticModule(parentMod);
        if (!migrated) return;
        mid = migrated.id;
      }
      const newLesson = { ...item, id: crypto.randomUUID(), title: item.title + ' (Copy)', status: 'draft' };
      const newMods = modules.map(m => m.id === mid ? { ...m, lessons: [...(m.lessons||[]), newLesson] } : m);
      setModules(newMods); window.EDSTUTIA.modules = newMods;
      if (window.supabaseClient) {
        const { data: nl } = await window.supabaseClient.from('lessons').insert({ module_id: mid, title: newLesson.title, type: newLesson.type || 'ASYNCHRONOUS', status: 'draft', content_blocks: [] }).select().single();
        if (nl) {
          const synced = modules.map(m => m.id === mid ? { ...m, lessons: [...(m.lessons||[]).filter(l => l.id !== newLesson.id), { ...newLesson, id: nl.id }] } : m);
          setModules(synced); window.EDSTUTIA.modules = synced;
        }
      }
    }
  };

  return (
    <div className="page enter" style={{ maxWidth: 860, margin: '0 auto' }}>

      {/* Header Bar */}
      <div className="row gap-16" style={{ justifyContent: 'space-between', alignItems: 'center', marginBottom: 24, marginTop: 12 }}>
        <div>
           <h1 style={{ fontSize: 32, margin: 0 }}>Course Modules</h1>
           {isEditor && <p className="muted" style={{ margin: '4px 0 0', fontSize: 15 }}>Manage your curriculum content and structure.</p>}
        </div>
        
        {isEditor && (
           <div className="row gap-10">
              <button className="btn btn-ghost btn-sm" onClick={openModules.length ? collapseAll : expandAll}>
                 {openModules.length ? 'Collapse All' : 'Expand All'}
              </button>
              <button className="btn btn-soft btn-sm" onClick={publishAll}>
                 <Icon name="check" size={16} /> Publish All
              </button>
              <button className="btn btn-primary btn-sm" onClick={() => setModal({ type: 'addModule' })}>
                 <Icon name="plus" size={16} /> Add Module
              </button>
           </div>
        )}
      </div>

      {modal && (
        <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.55)', backdropFilter: 'blur(6px)', display: 'grid', placeItems: 'center', zIndex: 9999, padding: '20px' }} onClick={() => setModal(null)}>
          <div className="card enter" style={{ width: modal.type === 'addLesson' ? 520 : 400, maxWidth: '100%', background: 'var(--bg)', boxShadow: '0 24px 64px rgba(0,0,0,.35)', borderRadius: 16, overflow: 'hidden' }} onClick={e => e.stopPropagation()}>

            {/* Modal header */}
            <div style={{ padding: '20px 24px 0', display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
              <h3 style={{ margin: 0, fontSize: 20, fontWeight: 800 }}>
                {modal.type === 'renameModule' && 'Rename Module'}
                {modal.type === 'addModule' && 'Create Module'}
                {modal.type === 'addLesson' && 'New Lesson'}
              </h3>
              <button type="button" onClick={() => setModal(null)} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', padding: 4, display: 'flex', borderRadius: 6 }}>
                <Icon name="close" size={20} />
              </button>
            </div>

            <form onSubmit={submitModal} style={{ padding: '20px 24px 24px', display: 'flex', flexDirection: 'column', gap: 18 }}>

              {/* Error banner */}
              {modal.error && (
                <div style={{ padding: '10px 14px', background: '#FEF2F2', color: '#B91C1C', border: '1px solid #FCA5A5', borderRadius: 8, fontSize: 13, fontWeight: 500 }}>
                  {modal.error}
                </div>
              )}

              {/* Title field */}
              <div>
                <label style={{ display: 'block', marginBottom: 7, fontSize: 13, fontWeight: 700, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '.6px' }}>
                  {modal.type === 'addLesson' ? 'Lesson title' : 'Title'}
                </label>
                <input
                  name="title" autoFocus required defaultValue={modal.currentTitle || ''}
                  placeholder={modal.type === 'addLesson' ? 'e.g. Introduction to VR Design…' : 'Enter title…'}
                  style={{ width: '100%', padding: '10px 14px', border: '1.5px solid var(--border)', borderRadius: 10, background: 'var(--surface-1)', color: 'var(--text)', fontSize: 15, fontFamily: 'var(--font-body)', outline: 'none', boxSizing: 'border-box' }}
                  onFocus={e => e.target.style.borderColor = 'var(--lime)'}
                  onBlur={e => e.target.style.borderColor = 'var(--border)'}
                />
              </div>

              {/* Lesson template picker */}
              {modal.type === 'addLesson' && (
                <div>
                  <label style={{ display: 'block', marginBottom: 10, fontSize: 13, fontWeight: 700, color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '.6px' }}>
                    Start from template
                  </label>
                  <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8 }}>
                    {LESSON_TEMPLATES.map(t => {
                      const isSel = (modal.selectedTemplate || 'blank') === t.id;
                      return (
                        <button key={t.id} type="button"
                          onClick={() => setModal({ ...modal, selectedTemplate: t.id })}
                          style={{
                            display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 5,
                            padding: '11px 13px', border: `2px solid ${isSel ? 'var(--lime)' : 'var(--border)'}`,
                            borderRadius: 10, background: isSel ? 'rgba(143,191,46,.08)' : 'var(--surface-1)',
                            cursor: 'pointer', textAlign: 'left', transition: 'border-color .12s, background .12s',
                          }}>
                          <div style={{ display: 'flex', alignItems: 'center', gap: 7 }}>
                            <Icon name={t.icon} size={15} style={{ color: isSel ? 'var(--lime-strong)' : 'var(--text-muted)', flexShrink: 0 }} />
                            <span style={{ fontWeight: 700, fontSize: 13, color: isSel ? 'var(--lime-strong)' : 'var(--text)', whiteSpace: 'nowrap' }}>{t.label}</span>
                          </div>
                          <span style={{ fontSize: 11, color: 'var(--text-muted)', lineHeight: 1.35 }}>{t.desc}</span>
                        </button>
                      );
                    })}
                  </div>
                </div>
              )}

              {/* Actions */}
              <div style={{ display: 'flex', gap: 10, justifyContent: 'flex-end', paddingTop: 4 }}>
                <button type="button" className="btn btn-ghost" onClick={() => setModal(null)}>Cancel</button>
                <button type="submit" className="btn btn-primary" disabled={!!modal.saving}>
                  {modal.saving ? 'Creating…' : modal.type === 'addLesson' ? 'Create Lesson' : 'Save'}
                </button>
              </div>
            </form>
          </div>
        </div>
      )}

      {/* Modules List */}
      {loadingModules && (
        <div style={{ padding: '32px 0', textAlign: 'center', color: 'var(--text-muted)', fontSize: 14 }}>
          <div className="skeleton" style={{ height: 72, borderRadius: 10, marginBottom: 10 }} />
          <div className="skeleton" style={{ height: 72, borderRadius: 10 }} />
        </div>
      )}
      <div className="stack" style={{ gap: 16, opacity: loadingModules ? 0 : 1, transition: 'opacity .25s' }}>
        {modules.map((mod, mIdx) => {
          const isOpen = openModules.includes(mod.id);
          const dc = mod.lessons ? mod.lessons.filter(l => l.status === 'completed').length : 0;
          // Sequential module gate: all lessons in previous visible module must be complete
          let gated = false;
          if (isEdLearner && mIdx > 0) {
            const prevVisibleMod = modules.slice(0, mIdx).filter(m => (m.lessons || []).length > 0).slice(-1)[0];
            if (prevVisibleMod) {
              const prevComplete = (prevVisibleMod.lessons || []).every(l =>
                l.status === 'completed' || completedIds.has(l.id) || window.EDSTUTIA._completedIds?.has(l.id)
              );
              gated = !prevComplete;
            }
          }
          
          return (
            <div key={mod.id} 
              draggable={isEditor}
              onDragStart={(e) => handleDragStart(e, mIdx)}
              onDragOver={(e) => handleDragOver(e, mIdx)}
              onDragLeave={() => setDragOverIdx(null)}
              onDrop={(e) => handleDrop(e, mIdx)}
              onDragEnd={() => { setDraggedIdx(null); setDragOverIdx(null); }}
              style={{
                opacity: draggedIdx === mIdx ? 0.4 : gated ? 0.72 : 1,
                borderTop: dragOverIdx === mIdx && draggedIdx > mIdx ? '4px solid var(--lime)' : undefined,
                borderBottom: dragOverIdx === mIdx && draggedIdx < mIdx ? '4px solid var(--lime)' : undefined,
                transition: 'opacity 0.2s',
                position: 'relative',
                border: '1px solid ' + (gated ? 'var(--border)' : 'var(--border)'),
                borderRadius: 8,
                background: 'var(--bg)'
              }}>

              {/* Module Header */}
              <div style={{ background: gated ? 'var(--surface-2)' : 'var(--surface-1)', borderRadius: isOpen ? '8px 8px 0 0' : 8, padding: '12px 16px', display: 'flex', alignItems: 'center', gap: 12 }}>

                {isEditor && <div style={{ color: 'var(--text-muted)', cursor: 'grab', display: 'flex' }}><Icon name="drag" size={16} /></div>}

                <button onClick={(e) => gated ? e.stopPropagation() : toggleModule(e, mod.id)} style={{ background: 'none', border: 'none', color: 'inherit', cursor: gated ? 'default' : 'pointer', padding: 0, display: 'flex' }}>
                  {gated
                    ? <span style={{ fontSize: 16, lineHeight: 1 }}>🔒</span>
                    : <Icon name="chevdown" size={18} style={{ transform: isOpen ? 'rotate(0deg)' : 'rotate(-90deg)', transition: 'transform 0.15s', color: 'var(--text-muted)' }} />}
                </button>
                
                <div style={{ flex: 1, display: 'flex', flexDirection: 'column', gap: 2 }}>
                   <h3 style={{ fontSize: 16, margin: 0 }}>{mod.title}</h3>
                   {mod.goal && <span style={{ fontSize: 12, color: 'var(--text-muted)' }}>Prerequisite: {mod.goal}</span>}
                </div>
                
                {isEditor ? (
                   <div className="row gap-10" style={{ alignItems: 'center' }}>
                      <button title="Publish/Unpublish" onClick={(e) => togglePublish(e, true, mod)} style={{ background: 'none', border: 'none', cursor: 'pointer', display: 'flex', color: mod.status === 'published' ? 'var(--lime-strong)' : 'var(--text-muted)' }}>
                        <Icon name={mod.status === 'published' ? 'check' : 'slash'} size={18} />
                      </button>
                      <button title="Add Content" onClick={(e) => { e.stopPropagation(); setModal({ type: 'addLesson', moduleId: mod.id }); }} style={{ background: 'none', border: 'none', cursor: 'pointer', display: 'flex', color: 'var(--text-muted)' }}>
                        <Icon name="plus" size={18} />
                      </button>
                      <div style={{ position: 'relative' }}>
                        <button title="Options" onClick={(e) => { e.stopPropagation(); setMenuOpen(menuOpen === mod.id ? null : mod.id); }} style={{ background: 'none', border: 'none', cursor: 'pointer', display: 'flex', color: 'var(--text-muted)' }}>
                          <Icon name="more" size={18} />
                        </button>
                        {menuOpen === mod.id && (
                           <div className="card" style={{ position: 'absolute', right: 0, top: '100%', zIndex: 100, minWidth: 160, padding: 8, display: 'flex', flexDirection: 'column', gap: 4 }}>
                              <button onClick={(e) => { e.stopPropagation(); setModal({ type: 'renameModule', id: mod.id, currentTitle: mod.title }); setMenuOpen(null); }} className="btn btn-ghost btn-sm" style={{ justifyContent: 'flex-start' }}><Icon name="pen" size={14} /> Edit</button>
                              <button onClick={(e) => { e.stopPropagation(); duplicateItem(true, mod, null); setMenuOpen(null); }} className="btn btn-ghost btn-sm" style={{ justifyContent: 'flex-start' }}><Icon name="layers" size={14} /> Duplicate</button>
                              <button onClick={(e) => { e.stopPropagation(); deleteItem(true, mod.id); setMenuOpen(null); }} className="btn btn-ghost btn-sm" style={{ justifyContent: 'flex-start', color: '#ff4d4d' }}><Icon name="trash" size={14} /> Delete</button>
                           </div>
                        )}
                      </div>
                   </div>
                ) : (
                   <span style={{ fontSize: 13, color: 'var(--text-muted)', fontWeight: 600 }}>{dc} / {mod.lessons?.length || 0} Complete</span>
                )}
              </div>

              {/* Sequential module gate banner */}
              {gated && (() => {
                const prevMod = modules.slice(0, mIdx).filter(m => (m.lessons || []).length > 0).slice(-1)[0];
                const remaining = prevMod ? (prevMod.lessons || []).filter(l => l.status !== 'completed' && !completedIds.has(l.id)).length : 0;
                return (
                  <div style={{ margin: '0 16px 14px', padding: '12px 16px', background: 'rgba(234,179,8,.08)', border: '1px solid rgba(234,179,8,.35)', borderRadius: 10, display: 'flex', alignItems: 'center', gap: 12 }}>
                    <span style={{ fontSize: 20, flex: 'none' }}>🔒</span>
                    <div>
                      <p style={{ margin: 0, fontWeight: 700, fontSize: 14, color: 'var(--text)' }}>Complete all lessons in <strong>{prevMod?.title || 'the previous module'}</strong> to unlock this module</p>
                      <p style={{ margin: '3px 0 0', fontSize: 13, color: 'var(--text-muted)' }}>
                        {remaining > 0 ? `${remaining} lesson${remaining > 1 ? 's' : ''} remaining` : 'Almost there — finish the last lesson to continue.'}
                      </p>
                    </div>
                  </div>
                );
              })()}

              {/* Module Items (Lessons) */}
              {isOpen && !gated && (
                <div style={{ padding: '8px 0', display: 'flex', flexDirection: 'column' }}>
                  {mod.lessons && mod.lessons.map((l, i) => {
                    const locked = l.status === 'locked' && !isEditor;
                    const isQuiz = l.type === 'QUIZ';
                    const isVideo = l.type === 'VIDEO';
                    return (
                      <div key={l.id}
                        draggable={isEditor}
                        onDragStart={isEditor ? e => lessonDragStart(e, l.id, mod.id) : undefined}
                        onDragOver={isEditor ? e => lessonDragOver(e, l.id) : undefined}
                        onDragLeave={isEditor ? lessonDragLeave : undefined}
                        onDrop={isEditor ? e => lessonDrop(e, l.id) : undefined}
                        onDragEnd={isEditor ? lessonDragEnd : undefined}
                        style={{ padding: '10px 16px', display: 'flex', alignItems: 'center', gap: 12,
                          borderTop: i > 0 ? '1px solid var(--border)' : 'none',
                          marginLeft: 32,
                          opacity: locked ? 0.6 : lessonDrag.from === l.id ? 0.35 : 1,
                          background: lessonDrag.over === l.id && lessonDrag.from !== l.id ? 'var(--lime-soft)' : 'transparent',
                          borderLeft: lessonDrag.over === l.id && lessonDrag.from !== l.id ? '3px solid var(--lime)' : '3px solid transparent',
                          transition: 'background .1s, border-color .1s, opacity .1s',
                          cursor: isEditor ? 'grab' : 'default',
                        }}>

                        {isEditor && <div style={{ color: 'var(--text-muted)', cursor: 'grab', display: 'flex' }}><Icon name="drag" size={16} /></div>}
                        
                        <div style={{ display: 'flex', color: l.status === 'completed' ? 'var(--lime-strong)' : 'var(--text-muted)' }}>
                           <Icon name={isQuiz ? 'quiz' : isVideo ? 'play' : 'document'} size={18} />
                        </div>
                        
                        {renamingLesson?.id === l.id ? (
                          <div className="row gap-8" style={{ flex: 1, minWidth: 0 }} onClick={e => e.stopPropagation()}>
                            <input
                              autoFocus
                              value={renamingLesson.value}
                              onChange={e => setRenamingLesson(r => ({ ...r, value: e.target.value }))}
                              onKeyDown={e => { if (e.key === 'Enter') saveLessonRename(); if (e.key === 'Escape') setRenamingLesson(null); }}
                              style={{ flex: 1, minWidth: 0, fontWeight: 600, fontSize: 15, padding: '4px 10px', borderRadius: 8, border: '2px solid var(--lime)', outline: 'none', background: 'var(--bg)', color: 'var(--text)' }}
                            />
                            <button className="btn btn-primary btn-sm" onClick={saveLessonRename}>Save</button>
                            <button className="btn btn-ghost btn-sm" onClick={() => setRenamingLesson(null)}>Cancel</button>
                          </div>
                        ) : (
                          <button disabled={locked} onClick={() => go({ name: 'lesson', id: l.id })} style={{ background: 'none', border: 'none', color: 'inherit', cursor: locked ? 'not-allowed' : 'pointer', padding: 0, fontSize: 15, flex: 1, textAlign: 'left', fontWeight: 500, display: 'flex', alignItems: 'center' }}>
                            {l.title}
                            {l.status === 'draft' && <span className="pill" style={{ background: '#e67e22', color: '#fff', marginLeft: 8, fontSize: 10, padding: '2px 6px' }}>DRAFT</span>}
                          </button>
                        )}
                        
                        {!isEditor && l.status === 'completed' && <div style={{ display: 'flex', color: 'var(--lime-strong)', marginRight: 16 }}><Icon name="check" size={16} /></div>}
                        
                        <span style={{ fontSize: 12, color: 'var(--text-muted)', marginRight: 16 }}>
                          {isQuiz ? 'Score at least 80%' : 'Must view'}
                        </span>
                      
                        {isEditor && (
                           <div className="row gap-10" style={{ alignItems: 'center' }}>
                              <button title="Publish/Unpublish" onClick={(e) => togglePublish(e, false, l, mod.id)} style={{ background: 'none', border: 'none', cursor: 'pointer', display: 'flex', color: l.status === 'published' ? 'var(--lime-strong)' : 'var(--text-muted)' }}>
                                <Icon name={l.status === 'published' ? 'check' : 'slash'} size={18} />
                              </button>
                              <div style={{ position: 'relative' }}>
                                <button title="Options" onClick={(e) => { e.stopPropagation(); setMenuOpen(menuOpen === l.id ? null : l.id); }} style={{ background: 'none', border: 'none', cursor: 'pointer', display: 'flex', color: 'var(--text-muted)' }}>
                                  <Icon name="more" size={18} />
                                </button>
                                {menuOpen === l.id && (
                                   <div className="card" style={{ position: 'absolute', right: 0, top: '100%', zIndex: 100, minWidth: 160, padding: 8, display: 'flex', flexDirection: 'column', gap: 4 }}>
                                      <button onClick={(e) => { e.stopPropagation(); go({ name: 'editor', id: l.id }); setMenuOpen(null); }} className="btn btn-ghost btn-sm" style={{ justifyContent: 'flex-start' }}><Icon name="pen" size={14} /> Edit blocks</button>
                                      <button onClick={(e) => { e.stopPropagation(); startLessonRename(l); }} className="btn btn-ghost btn-sm" style={{ justifyContent: 'flex-start' }}><Icon name="pen" size={14} /> Rename</button>
                                      <button onClick={(e) => { e.stopPropagation(); duplicateItem(false, l, mod.id); setMenuOpen(null); }} className="btn btn-ghost btn-sm" style={{ justifyContent: 'flex-start' }}><Icon name="layers" size={14} /> Duplicate</button>
                                      <button onClick={(e) => { e.stopPropagation(); deleteItem(false, l.id, mod.id); setMenuOpen(null); }} className="btn btn-ghost btn-sm" style={{ justifyContent: 'flex-start', color: '#ff4d4d' }}><Icon name="trash" size={14} /> Remove</button>
                                   </div>
                                )}
                              </div>
                           </div>
                        )}
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          );
        })}
      </div>
    </div>
  );
}

Object.assign(window, { LessonPage, Curriculum });
