/* ============================================================
   CURIO — Conversational AI course creator
   ============================================================ */

/* ---------- Reorder helper ---------- */
function reorderArray(arr, fromIdx, toIdx) {
  const a = arr.slice();
  const [item] = a.splice(fromIdx, 1);
  // Adjust toIdx if removing the item shifts subsequent indices
  const adjusted = toIdx > fromIdx ? toIdx - 1 : toIdx;
  a.splice(adjusted, 0, item);
  return a;
}

/* ---------- Course generation templates ---------- */
const MOD_TEMPLATES = [
  { t: 'Foundations of {S}', ls: ['Why {S} matters', 'Core concepts & vocabulary', 'Building your mental model'] },
  { t: 'Getting Hands-On', ls: ['Your first exercise', 'Common beginner mistakes', 'Practice checkpoint'] },
  { t: 'Core Techniques', ls: ['The essential toolkit', 'A step-by-step walkthrough', 'When to use what'] },
  { t: 'Going Deeper', ls: ['Intermediate strategies', 'A real-world case study', 'Avoiding the pitfalls'] },
  { t: 'Applied {S}', ls: ['A complete worked example', 'Adapting it to your context', 'Module project'] },
  { t: 'Advanced Patterns', ls: ['Pro-level methods', 'Edge cases & nuance', 'Quality & polish'] },
  { t: 'Putting It Together', ls: ['Build your own project', 'Review & reflection', 'Where to go next'] },
  { t: 'Mastery & Next Steps', ls: ['The final capstone', 'Self-assessment', 'Continuing the journey'] },
];
const shortTopic = (t) => t.replace(/^(intro(duction)? to|the|a|an|learn|mastering|basics of)\s+/i, '').replace(/\s+for .+$/i, '').trim() || t;
const DURS = ['6m', '8m', '7m', '9m', '11m', '10m', '7m', '12m', '8m'];
function buildCourse(topic, n, cat) {
  const S = shortTopic(topic);
  const modules = MOD_TEMPLATES.slice(0, n).map((m, mi) => {
    const lessons = m.ls.map((l, li) => ({ title: l.replace(/\{S\}/g, S), type: 'lesson', dur: DURS[(mi * 3 + li) % DURS.length] }));
    lessons.push({ title: `Module ${mi + 1} quiz`, type: 'quiz', dur: '5m' });
    return { title: m.t.replace(/\{S\}/g, S.charAt(0).toUpperCase() + S.slice(1)), lessons };
  });
  return { title: topic, cat: cat || 'data', modules, level: 'Beginner', hours: (n * 0.8).toFixed(1) };
}

/* sample written lesson / quiz / notes for preview */
const SAMPLE_LESSON = {
  title: 'Core concepts & vocabulary',
  body: [
    { h: 'The big idea' },
    { p: 'Before we touch any tools, let’s build a shared vocabulary. Every field has a handful of words that unlock everything else — get these right and the rest of the course will feel intuitive rather than intimidating.' },
    { h: 'Three terms to know' },
    { list: ['**Primitive** — the smallest useful unit you’ll work with. Everything is composed from these.', '**Pattern** — a repeatable way of combining primitives to solve a recurring problem.', '**Constraint** — the rules that make a solution valid. Constraints aren’t limits; they’re what make good choices obvious.'] },
    { p: 'Keep these in mind as you go. We’ll return to each one with concrete examples in the next lesson.' },
    { callout: 'Tip: Don’t memorize definitions. Instead, try to spot each term “in the wild” as you work through the exercises.' },
  ],
};
const SAMPLE_QUIZ = {
  q: 'Which of the following best describes a “constraint” as introduced in this module?',
  options: ['A bug that prevents your work from running', 'A rule that defines whether a solution is valid', 'The smallest unit you can work with', 'A type of advanced technique'],
  correct: 1,
  why: 'A constraint is a rule that makes a solution valid. Far from being a limitation, constraints narrow your options so the right choice becomes clear.',
};
const SAMPLE_NOTES = [
  'Open with a relatable example of the topic before defining anything.',
  'Emphasize that vocabulary is a tool for thinking, not a test.',
  'Pause after the three terms — ask learners to name one example of each.',
  'Transition: “Now that we share a language, let’s use it.”',
];

/* ---------- Chat message ---------- */
function ChatMsg({ m, onChip }) {
  if (m.role === 'user') return (
    <div className="row" style={{ justifyContent: 'flex-end', gap: 10 }}>
      <div style={{ maxWidth: '78%', background: 'var(--ink)', color: '#fff', padding: '11px 15px', borderRadius: '14px 14px 4px 14px', fontSize: 14.5, lineHeight: 1.5 }}>{m.text}</div>
    </div>
  );
  return (
    <div className="row" style={{ gap: 10, alignItems: 'flex-start' }}>
      <span className="logo-mark" style={{ width: 28, height: 28, flexShrink: 0 }}><Spark size={15} /></span>
      <div style={{ maxWidth: '82%' }}>
        <div style={{ background: 'var(--surface)', border: '1px solid var(--line)', padding: '12px 15px', borderRadius: '4px 14px 14px 14px', fontSize: 14.5, lineHeight: 1.55, boxShadow: 'var(--sh-xs)' }}>
          {m.typing ? <TypingDots /> : m.text}
        </div>
        {m.actions && m.actions.length > 0 && !m.typing && (
          <div className="row" style={{ flexWrap: 'wrap', gap: 6, marginTop: 8 }}>
            {m.actions.map((a, i) => (
              <span key={i} className="chip" style={{ background: 'var(--brand-tint)', borderColor: 'var(--brand-tint-2)', color: 'var(--brand-ink)', cursor: 'default' }}>
                <Check size={11} /> {describeAction(a)}
              </span>
            ))}
          </div>
        )}
        {m.errors && m.errors.length > 0 && !m.typing && (
          <div className="muted" style={{ marginTop: 6, fontSize: 12, color: 'var(--rose, #c84a4a)', lineHeight: 1.45 }}>
            {m.errors.map((e, i) => (
              <div key={i}>{e.action ? `${e.action}: ` : ''}{e.message}</div>
            ))}
          </div>
        )}
        {m.chips && !m.typing && (
          <div className="row" style={{ flexWrap: 'wrap', gap: 8, marginTop: 10 }}>
            {m.chips.map(ch => <button key={ch} className="chip" style={{ borderColor: 'var(--brand-tint-2)', color: 'var(--brand-ink)' }} onClick={() => onChip(ch)}>{ch}</button>)}
          </div>
        )}
      </div>
    </div>
  );
}

function describeAction(a) {
  switch (a.type) {
    case 'patchCourseTitle': return 'Updated course title';
    case 'patchCourseDescription': return 'Updated course description';
    case 'patchModule': return `Updated Module ${a.moduleOrder}`;
    case 'regenerateModule': return `Regenerated Module ${a.moduleOrder}`;
    case 'addLessonToModule': return `Added lesson: ${a.title}`;
    case 'regenerateLesson': return `Regenerated Lesson ${a.moduleOrder}.${a.lessonOrder}`;
    case 'improveLesson': return `Improved Lesson ${a.moduleOrder}.${a.lessonOrder}`;
    case 'addQuizQuestion': return `Added a question to Module ${a.moduleOrder}`;
    case 'regenerateQuiz': return `Regenerated Module ${a.moduleOrder} quiz`;
    default: return a.type;
  }
}

/* ---------- Creator root ---------- */
function Creator({ go, params = {}, user }) {
  const [stage, setStage] = useState('topic'); // topic, audience, depth, tone, generating, done
  const [messages, setMessages] = useState([{ role: 'ai', text: 'Hi! I’m Coursedy. Tell me what you’d like to teach, and I’ll build a complete course — modules, lessons, quizzes, and slide notes. What’s the topic?' }]);
  const [input, setInput] = useState(params.topic || '');
  const [topic, setTopic] = useState('');
  const [depthN, setDepthN] = useState(5);
  const [course, setCourse] = useState(null);
  const [built, setBuilt] = useState(0);
  const [tab, setTab] = useState('outline');
  const [selectedLessonId, setSelectedLessonId] = useState(null);
  const [audience, setAudience] = useState('');
  const [difficulty, setDifficulty] = useState('beginner');
  const [tone, setTone] = useState('professional');
  const [error, setError] = useState(null);
  const [draftId, setDraftId] = useState(null);
  const [savedAt, setSavedAt] = useState(0);
  const [loadingEdit, setLoadingEdit] = useState(!!params.editId);
  const [editTick, setEditTick] = useState(0);
  const tickerRef = useRef(null);
  const scrollRef = useRef(null);

  const markSaved = () => { setSavedAt(Date.now()); setEditTick(t => t + 1); };
  const refresh = React.useCallback(async () => {
    if (!course?.id || !(window.isRealCourseId && window.isRealCourseId(course.id))) return null;
    try {
      const c = await window.API.getCourse(course.id);
      setCourse(c);
      setBuilt(c.modules.length);
      setEditTick(t => t + 1);
      return c;
    } catch {
      return null;
    }
  }, [course?.id]);
  useEffect(() => {
    if (!savedAt) return;
    const id = setTimeout(() => setSavedAt(0), 2200);
    return () => clearTimeout(id);
  }, [savedAt]);

  useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [messages, stage]);

  const pushAI = (text, extra = {}, delay = 600) => {
    setMessages(m => [...m, { role: 'ai', typing: true }]);
    setTimeout(() => setMessages(m => m.map((x, i) => i === m.length - 1 ? { role: 'ai', text, ...extra } : x)), delay);
  };

  const startGen = async (overrides = {}) => {
    const tone_ = overrides.tone || tone;
    const difficulty_ = overrides.difficulty || difficulty;
    const audience_ = overrides.audience || audience;
    const catGuess = window._catGuess || guessCat(topic);
    // placeholder skeleton course so the existing animation runs while we wait
    const skeleton = {
      title: topic,
      cat: catGuess,
      level: ({ beginner: 'Beginner', intermediate: 'Intermediate', advanced: 'Advanced' })[difficulty_] || 'Beginner',
      hours: (depthN * 0.8).toFixed(1),
      modules: Array.from({ length: depthN }, (_, i) => ({ title: '…', lessons: [] })),
    };
    setStage('generating'); setCourse(skeleton); setBuilt(0); setTab('outline'); setError(null);

    // ticker that walks the skeleton up to depthN-1 while we wait on the API
    if (tickerRef.current) clearInterval(tickerRef.current);
    let idx = 0;
    tickerRef.current = setInterval(() => {
      idx += 1;
      if (idx >= depthN) {
        clearInterval(tickerRef.current);
        tickerRef.current = null;
        return;
      }
      setBuilt(idx);
    }, 1500);

    try {
      const real = await window.API.generateCourse({
        courseId: draftId || undefined,
        topic,
        audience: audience_ || 'general learners',
        difficulty: difficulty_,
        tone: tone_,
        moduleCount: depthN,
        includeQuizzes: true,
      });
      setCourse(real);
      setBuilt(real.modules.length);
      setStage('done');
      try { localStorage.setItem('coursedy_last_course', real.id); } catch {}
      pushAI('Your course is ready. I drafted ' + real.modules.length + ' modules with full lessons, a quiz per module, and instructor notes. Everything on the right is editable — refine anything, then export.', { done: true }, 400);
    } catch (e) {
      setError(e?.message || 'Generation failed');
      setStage('topic');
      setCourse(null);
      setBuilt(0);
      if (e?.code === 'PLAN_LIMIT_EXCEEDED') {
        pushAI((e.message || 'Free plan limit reached.') + ' Open the Pricing page to upgrade.');
        if (window.confirm((e.message || 'Free plan limit reached.') + '\n\nGo to the upgrade page now?')) go('upgrade');
      } else {
        pushAI('Sorry — generation failed: ' + (e?.message || 'unknown error') + '. Try again or adjust your inputs.');
      }
    } finally {
      if (tickerRef.current) { clearInterval(tickerRef.current); tickerRef.current = null; }
    }
  };

  useEffect(() => () => { if (tickerRef.current) clearInterval(tickerRef.current); }, []);

  // Expose setCourse for child-component optimistic updates (e.g. Add module button in BuilderPanel).
  useEffect(() => {
    window.__setCourseFromBuilder = setCourse;
    return () => { if (window.__setCourseFromBuilder === setCourse) window.__setCourseFromBuilder = null; };
  }, []);

  // URL is now the source of truth — load the course when editId is set, reset to fresh state when it isn't.
  useEffect(() => {
    let cancelled = false;
    if (params.editId) {
      setLoadingEdit(true);
      window.API.getCourse(params.editId).then(c => {
        if (cancelled) return;
        setCourse(c);
        setBuilt(c.modules.length);
        setStage('done');
        setTopic(c.title);
        setDraftId(c.id);
        setLoadingEdit(false);
        try { localStorage.setItem('coursedy_last_course', c.id); } catch {}
        setMessages([
          { role: 'ai', text: 'Editing “' + c.title + '”. Tell me what to build — add a chapter, regenerate a module, improve a lesson, anything.', done: true },
        ]);
      }).catch(e => {
        if (cancelled) return;
        setLoadingEdit(false);
        setMessages([
          { role: 'ai', text: 'Could not load this course (id ' + params.editId + '): ' + (e?.message || 'unknown error') + '. Try refreshing or pick another course from the library.' },
        ]);
      });
      return () => { cancelled = true; };
    }
    // No editId → fresh new-course state. Clear any prior loaded course so the chat starts clean.
    setLoadingEdit(false);
    setCourse(null);
    setBuilt(0);
    setStage('topic');
    setTopic('');
    setDraftId(null);
    setAudience('');
    setDifficulty('beginner');
    setTone('professional');
    setError(null);
    setInput(params.topic || '');
    setMessages([
      { role: 'ai', text: params.topic
          ? 'Got it — “' + params.topic + '”. Press Enter or Send to start.'
          : 'Hi! I’m Coursedy. Tell me what you’d like to teach, and I’ll build a complete course — modules, lessons, quizzes, and slide notes. What’s the topic?' },
    ]);
    return () => { cancelled = true; };
  }, [params.editId, params.topic]);

  const submitTopic = (t) => {
    const val = (t || input).trim(); if (!val) return;
    setTopic(val); setInput('');
    setMessages(m => [...m, { role: 'user', text: val }]);
    const catGuess = guessCat(val);
    setTimeout(() => pushAI(`Love it — “${val}”. Who is this course for?`, { chips: ['Total beginners', 'Some experience', 'Professionals'] }), 350);
    setStage('audience');
    window._catGuess = catGuess;
    // Fire-and-forget: create a draft row so progress is persisted.
    window.API.createDraft({ topic: val }).then(c => {
      if (!c || !c.id) return;
      setDraftId(c.id);
      try { localStorage.setItem('coursedy_last_course', c.id); } catch {}
    }).catch(e => {
      if (e?.code === 'PLAN_LIMIT_EXCEEDED') {
        pushAI((e.message || 'Free plan limit reached.') + ' Visit Pricing to upgrade — every other lesson and edit stays unlocked on your existing course.');
        if (window.confirm((e.message || 'Free plan limit reached.') + '\n\nGo to the upgrade page now?')) go('upgrade');
      } else {
        // Don't block the chat if draft creation fails for other reasons.
        setError(e?.message || 'Could not save draft');
      }
    });
  };

  const submitRefine = async () => {
    const val = input.trim(); if (!val) return;
    if (!course?.id || !(window.isRealCourseId && window.isRealCourseId(course.id))) {
      setMessages(m => [...m, { role: 'user', text: val }]);
      setInput('');
      pushAI('I can only refine saved courses. Finish generating first, then ask me anything.', {}, 200);
      return;
    }
    setMessages(m => [...m, { role: 'user', text: val }]);
    setInput('');
    setMessages(m => [...m, { role: 'ai', typing: true }]);
    const history = messages
      .filter(x => !x.typing && (x.role === 'user' || x.role === 'ai'))
      .slice(-8)
      .map(x => ({ role: x.role === 'ai' ? 'assistant' : 'user', content: x.text || '' }))
      .filter(x => x.content);
    try {
      const res = await window.API.chatRefine(course.id, val, history);
      const text = res.message || res.reply || '';
      const actions = Array.isArray(res.actions) ? res.actions : [];
      const errors = Array.isArray(res.errors) ? res.errors : [];
      // Swap local course tree with the refreshed one if it came back.
      if (res.course && window.backendToUi) {
        try {
          const ui = window.backendToUi(res.course);
          setCourse(ui);
          setBuilt(ui.modules.length);
          setEditTick(t => t + 1);
          if (actions.length > 0) markSaved();
        } catch {}
      }
      setMessages(m => m.map((x, i) => i === m.length - 1 && x.typing ? { role: 'ai', text, actions, errors } : x));
    } catch (e) {
      setMessages(m => m.map((x, i) => i === m.length - 1 && x.typing ? { role: 'ai', text: 'Sorry — ' + (e?.message || 'something went wrong') + '.' } : x));
    }
  };

  const onChip = (ch) => {
    setMessages(m => [...m, { role: 'user', text: ch }]);
    if (stage === 'audience') {
      const diff = ch === 'Total beginners' ? 'beginner'
        : ch === 'Some experience' ? 'intermediate'
        : ch === 'Professionals' ? 'advanced'
        : 'beginner';
      setAudience(ch);
      setDifficulty(diff);
      setStage('depth');
      setTimeout(() => pushAI('Got it. How deep should we go?', { chips: ['Quick — 3 modules', 'Standard — 5 modules', 'Comprehensive — 8 modules'] }), 350);
      if (draftId) {
        window.API.patchCourse(draftId, { audience: ch, difficulty: diff }).catch(() => {});
      }
    } else if (stage === 'depth') {
      const n = ch.includes('3') ? 3 : ch.includes('8') ? 8 : 5;
      setDepthN(n); setStage('tone');
      setTimeout(() => pushAI('Last thing — what tone fits your audience?', { chips: ['Friendly & casual', 'Clear & professional', 'Academic'] }), 350);
      if (draftId) {
        window.API.patchCourse(draftId, { moduleCount: n }).catch(() => {});
      }
    } else if (stage === 'tone') {
      const tn = ch === 'Friendly & casual' ? 'conversational'
        : ch === 'Clear & professional' ? 'professional'
        : ch === 'Academic' ? 'academic'
        : 'professional';
      setTone(tn);
      setTimeout(() => pushAI('Perfect. Generating your course now — watch it build on the right.', {}, 500), 200);
      setTimeout(() => startGen({ tone: tn }), 1300);
    }
  };

  return (
    <div style={{ display: 'grid', gridTemplateColumns: '440px 1fr', height: '100vh', overflow: 'hidden' }} data-screen-label="Course creator">
      {/* LEFT — chat */}
      <div style={{ display: 'flex', flexDirection: 'column', borderRight: '1px solid var(--line)', background: 'var(--surface)', minWidth: 0, height: '100vh', minHeight: 0, overflow: 'hidden' }}>
        <div className="row" style={{ height: 64, padding: '0 20px', borderBottom: '1px solid var(--line)', gap: 11 }}>
          <button className="btn btn-icon btn-ghost btn-sm" onClick={() => go('library')}><ChevL size={18} /></button>
          <span className="logo-mark" style={{ width: 30, height: 30 }}><Spark size={16} /></span>
          <div className="grow">
            <div style={{ fontSize: 14.5, fontWeight: 600 }}>Course Assistant</div>
            <div className="row" style={{ gap: 5, fontSize: 11.5, color: 'var(--muted)' }}><span className="live-dot" /> Online</div>
          </div>
          <button className="btn btn-icon btn-ghost btn-sm" onClick={() => { try { localStorage.removeItem('coursedy_last_course'); } catch {} setStage('topic'); setCourse(null); setTopic(''); setBuilt(0); setError(null); setDraftId(null); setMessages([{ role: 'ai', text: 'Fresh start! What would you like to teach?' }]); }}><Refresh size={16} /></button>
        </div>

        <div ref={scrollRef} className="scroll" style={{ flex: 1, minHeight: 0, overflowY: 'auto', padding: 20, display: 'flex', flexDirection: 'column', gap: 16 }}>
          {messages.map((m, i) => <ChatMsg key={i} m={m} onChip={onChip} />)}
        </div>

        {/* input */}
        <div style={{ padding: 16, borderTop: '1px solid var(--line)', position: 'relative' }}>
          {stage === 'topic' && messages.length <= 1 && (
            <div className="row" style={{ gap: 8, marginBottom: 10, padding: '8px 12px', background: 'var(--brand-tint)', color: 'var(--brand-ink)', borderRadius: 999, fontSize: 12.5, fontWeight: 600 }}>
              <Sparkles size={13} />
              <span>Start by typing your course topic below</span>
            </div>
          )}
          <div className="row" style={{
            gap: 8,
            background: 'var(--surface)',
            border: stage === 'topic' && messages.length <= 1 ? '1.5px solid var(--brand)' : '1px solid var(--line-strong)',
            borderRadius: 'var(--r-md)',
            padding: 7,
            boxShadow: stage === 'topic' && messages.length <= 1 ? '0 0 0 4px var(--brand-tint), var(--sh-sm)' : 'var(--sh-xs)',
            animation: stage === 'topic' && messages.length <= 1 ? 'creatorInputPulse 2.4s ease-in-out infinite' : 'none',
          }}>
            <button className="btn btn-icon btn-ghost btn-sm"><Paperclip size={16} /></button>
            <input className="grow" style={{ border: 'none', outline: 'none', background: 'transparent', fontSize: 14.5 }}
              autoFocus={stage === 'topic' || stage === 'done'}
              placeholder={stage === 'topic' ? 'Type a topic — e.g. Intro to Watercolor Painting' : stage === 'done' ? 'Ask Coursedy to refine — e.g. “work on chapter 1”' : 'Message Coursedy…'}
              value={input} onChange={e => setInput(e.target.value)} onKeyDown={e => { if (e.key === 'Enter') { if (stage === 'topic') submitTopic(); else if (stage === 'done') submitRefine(); } }} />
            <button className="btn btn-icon btn-brand btn-sm" onClick={() => { if (stage === 'topic') submitTopic(); else if (stage === 'done') submitRefine(); }} disabled={!input.trim() || stage === 'generating'} style={{ opacity: (input.trim() && stage !== 'generating') ? 1 : .5 }}><Send size={15} /></button>
          </div>
          <div className="row" style={{ justifyContent: 'center', gap: 5, marginTop: 9, fontSize: 11, color: 'var(--faint)' }}>
            <Lock size={11} /> Your content stays private to your workspace
          </div>
        </div>
      </div>

      {/* RIGHT — builder */}
      <BuilderPanel
        stage={stage}
        course={course}
        built={built}
        tab={tab}
        setTab={setTab}
        selectedLessonId={selectedLessonId}
        setSelectedLessonId={setSelectedLessonId}
        refresh={refresh}
        go={go}
        depthN={depthN}
        savedAt={savedAt}
        markSaved={markSaved}
        editTick={editTick}
        loadingEdit={loadingEdit}
        editMode={!!params.editId}
      />
    </div>
  );
}

function guessCat(t) {
  const s = t.toLowerCase();
  const map = { tech: ['code', 'python', 'program', 'develop', 'software', 'web', 'cyber', 'security', 'hacking'], data: ['data', 'ai', 'machine', 'analyt', 'sql', 'prompt'], design: ['design', 'color', 'ui', 'ux', 'figma', 'paint', 'watercolor', 'draw'], business: ['business', 'startup', 'pricing', 'negotiat', 'sales', 'manage', 'product'], marketing: ['marketing', 'content', 'seo', 'brand', 'copywriting', 'social'], finance: ['finance', 'money', 'invest', 'tax', 'budget', 'account'], wellness: ['health', 'sleep', 'mindful', 'fitness', 'nutrition', 'wellness', 'yoga'], creative: ['music', 'photo', 'video', 'writing', 'art', 'creative', 'garden'] };
  for (const [k, words] of Object.entries(map)) if (words.some(w => s.includes(w))) return k;
  return 'data';
}

/* ---------- relative timestamp ---------- */
const ago = (d) => {
  const t = (Date.now() - new Date(d).getTime()) / 1000;
  if (t < 60) return 'just now';
  if (t < 3600) return Math.floor(t / 60) + 'm ago';
  if (t < 86400) return Math.floor(t / 3600) + 'h ago';
  if (t < 7 * 86400) return Math.floor(t / 86400) + 'd ago';
  return new Date(d).toLocaleDateString();
};

/* ---------- Builder panel (right) ---------- */
function BuilderPanel({ stage, course, built, tab, setTab, selectedLessonId, setSelectedLessonId, refresh, go, depthN, savedAt, markSaved, editTick, loadingEdit, editMode }) {
  const [historyOpen, setHistoryOpen] = useState(false);
  const [history, setHistory] = useState(null);
  const [historyLoading, setHistoryLoading] = useState(false);

  const realId = course && window.isRealCourseId && window.isRealCourseId(course.id);

  const onSelectLesson = React.useCallback((lessonId) => {
    setSelectedLessonId && setSelectedLessonId(lessonId);
    if (lessonId) setTab('lesson');
  }, [setSelectedLessonId, setTab]);

  const onAddLessonToModule = React.useCallback(async (moduleId) => {
    if (!moduleId || !realId) return;
    // Refuse to act on a still-syncing temp module id.
    if (typeof moduleId === 'string' && moduleId.startsWith('tmp-')) return;
    const tempId = 'tmp-l-' + Date.now();
    const placeholder = { id: tempId, title: 'Untitled lesson', type: 'lesson', dur: '3m', body: '', slideNotes: '', order: 999 };
    const setC = window.__setCourseFromBuilder;
    if (!setC) return;
    // Optimistic: insert placeholder before any quiz row.
    setC(c => {
      if (!c) return c;
      return {
        ...c,
        modules: c.modules.map(m => m.id !== moduleId ? m : ({
          ...m,
          lessons: [
            ...m.lessons.filter(l => l.type !== 'quiz'),
            placeholder,
            ...m.lessons.filter(l => l.type === 'quiz'),
          ],
        })),
      };
    });
    try {
      const { lesson } = await window.API.addLesson(moduleId, { title: 'Untitled lesson' });
      setC(c => {
        if (!c) return c;
        return {
          ...c,
          modules: c.modules.map(m => m.id !== moduleId ? m : ({
            ...m,
            lessons: m.lessons.map(l => l.id === tempId ? {
              id: lesson.id,
              title: lesson.title,
              type: 'lesson',
              dur: '3m',
              body: lesson.body || '',
              slideNotes: lesson.slideNotes || '',
              order: lesson.order,
            } : l),
          })),
        };
      });
      markSaved && markSaved();
    } catch (e) {
      // Revert
      setC(c => {
        if (!c) return c;
        return {
          ...c,
          modules: c.modules.map(m => m.id !== moduleId ? m : ({
            ...m,
            lessons: m.lessons.filter(l => l.id !== tempId),
          })),
        };
      });
      if (e?.code === 'PLAN_LIMIT_EXCEEDED') {
        if (window.confirm((e.message || 'Free plan limit reached.') + '\n\nGo to the upgrade page now?')) {
          window.__go && window.__go('upgrade');
        }
      } else {
        console.error('[addLesson] failed', e);
      }
    }
  }, [realId, markSaved]);

  const loadHistory = React.useCallback(() => {
    if (!realId || !course?.id) return;
    setHistoryLoading(true);
    window.API.getCourseHistory(course.id, 100)
      .then(d => setHistory(d.edits || []))
      .catch(() => setHistory([]))
      .finally(() => setHistoryLoading(false));
  }, [realId, course?.id]);

  useEffect(() => {
    if (historyOpen) loadHistory();
  }, [historyOpen, loadHistory]);

  useEffect(() => {
    if (historyOpen) loadHistory();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [editTick]);

  if (loadingEdit) {
    return (
      <div data-app-scroll className="scroll" style={{ background: 'var(--paper)', overflowY: 'auto', minWidth: 0 }}>
        <div style={{ maxWidth: 760, margin: '0 auto', padding: '24px 28px 80px' }}>
          <div className="row" style={{ gap: 10, marginBottom: 18, alignItems: 'center' }}>
            <span className="spinner dark" style={{ width: 14, height: 14 }} />
            <span className="mono muted" style={{ fontSize: 12 }}>Loading course…</span>
          </div>
          {[0,1,2,3].map(i => (
            <div key={i} className="card" style={{ padding: 16, marginBottom: 12 }}>
              <div className="row" style={{ gap: 10, marginBottom: 10 }}>
                <div className="skeleton" style={{ width: 28, height: 28, borderRadius: 8 }} />
                <div className="skeleton" style={{ height: 15, width: '45%' }} />
              </div>
              <div className="skeleton" style={{ height: 11, width: '70%', marginLeft: 38, marginBottom: 6 }} />
              <div className="skeleton" style={{ height: 11, width: '60%', marginLeft: 38 }} />
            </div>
          ))}
        </div>
      </div>
    );
  }
  if (editMode && !course) {
    return (
      <div className="center" style={{ background: 'var(--paper)', flexDirection: 'column', padding: 32 }}>
        <div className="card" style={{ padding: 24, maxWidth: 420, textAlign: 'center' }}>
          <div style={{ fontSize: 16, fontWeight: 600, marginBottom: 8 }}>Course unavailable</div>
          <div className="muted" style={{ fontSize: 14, lineHeight: 1.5, marginBottom: 16 }}>This course couldn't be loaded. It may have been deleted or moved.</div>
          <Btn variant="outline" size="sm" onClick={() => go('library')}>Back to library</Btn>
        </div>
      </div>
    );
  }
  if (!editMode && (stage === 'topic' || stage === 'audience' || stage === 'depth' || stage === 'tone')) {
    return <PreGenPanel stage={stage} go={go} />;
  }

  const c = catOf(course.cat);
  const totalLessons = course.modules.reduce((a, m) => a + m.lessons.length, 0);
  const generating = stage === 'generating';
  const showSaved = savedAt > 0 && (Date.now() - savedAt < 2500);

  return (
    <div data-app-scroll className="scroll" style={{ background: 'var(--paper)', overflowY: 'auto', minWidth: 0 }}>
      {/* header */}
      <div style={{ position: 'sticky', top: 0, zIndex: 10, background: 'rgba(250,250,248,.85)', backdropFilter: 'blur(12px)', borderBottom: '1px solid var(--line)' }}>
        <div className="row" style={{ padding: '14px 28px', gap: 16, flexWrap: 'wrap' }}>
          <div style={{ flex: '1 1 260px', minWidth: 0 }}>
            <div className="row" style={{ gap: 9 }}>
              {generating ? <span className="row" style={{ gap: 7, fontSize: 12, color: 'var(--brand)', fontWeight: 600 }}><span className="spinner dark" style={{ width: 13, height: 13 }} /> Generating…</span>
                : <span className="badge badge-green"><Check size={12} /> Draft ready</span>}
              <span className="mono muted" style={{ fontSize: 11.5 }}>{course.modules.length} modules · {totalLessons} lessons</span>
              {showSaved && <span className="mono fade-in" style={{ fontSize: 11, color: 'var(--green)' }}>Saved ✓</span>}
            </div>
            <div
              contentEditable={!generating}
              suppressContentEditableWarning
              onBlur={(e) => {
                const t = e.currentTarget.innerText.trim();
                if (!t || t === course.title) return;
                if (!window.isRealCourseId || !window.isRealCourseId(course.id)) return;
                window.API.patchCourse(course.id, { title: t })
                  .then(() => { course.title = t; markSaved && markSaved(); })
                  .catch(() => {});
              }}
              style={{ fontSize: 21, fontWeight: 700, letterSpacing: '-0.02em', marginTop: 6, outline: 'none', wordBreak: 'normal', overflowWrap: 'break-word' }}>{course.title}</div>
          </div>
          {stage === 'done' && (
            <div className="row" style={{ gap: 8, flexShrink: 0 }}>
              <Btn variant="outline" size="sm" icon={<Eye size={15} />} onClick={() => {
                if (course?.id) window.open('/preview/' + course.id, '_blank');
              }}>Preview</Btn>
              <ExportMenu courseId={course?.id} disabled={!course} />
              {realId && (
                <button
                  className="btn btn-icon btn-ghost btn-sm"
                  title="Edit history"
                  aria-label="Edit history"
                  onClick={() => setHistoryOpen(o => !o)}
                  style={historyOpen ? { background: 'var(--brand-tint)', color: 'var(--brand-ink)' } : undefined}
                >
                  <Clock size={16} />
                </button>
              )}
              <Btn variant="brand" size="sm" icon={<Check size={15} />} onClick={() => go('library')}>Done</Btn>
            </div>
          )}
        </div>
        {stage === 'done' && (
          <div className="row" style={{ padding: '0 28px', gap: 2, borderTop: '1px solid var(--line-2)' }}>
            {[['outline', 'Outline'], ['quiz', 'Quiz'], ['notes', 'Slide notes']].map(([id, label]) => (
              <button key={id} onClick={() => setTab(id)} style={{ padding: '11px 13px', fontSize: 13.5, fontWeight: 600, color: tab === id ? 'var(--ink)' : 'var(--muted)', borderBottom: tab === id ? '2px solid var(--brand)' : '2px solid transparent', marginBottom: -1 }}>{label}</button>
            ))}
          </div>
        )}
      </div>

      <div style={{ maxWidth: 760, margin: '0 auto', padding: '24px 28px 80px' }}>
        {generating && (
          <div className="row" style={{ gap: 10, marginBottom: 18 }}>
            <Progress value={(built / course.modules.length) * 100} />
            <span className="mono" style={{ fontSize: 12, color: 'var(--brand)', whiteSpace: 'nowrap' }}>{built}/{course.modules.length}</span>
          </div>
        )}

        {(tab === 'outline' || generating) && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            <ModuleList
              modules={course.modules.slice(0, generating ? built : course.modules.length)}
              editable={stage === 'done' && realId}
              markSaved={markSaved}
              onSelectLesson={onSelectLesson}
              onAddLesson={onAddLessonToModule}
              selectedLessonId={selectedLessonId}
              go={go}
              courseId={course.id}
              courseIsReal={realId}
              refresh={refresh}
            />
            {generating && built < course.modules.length && (
              <div className="card" style={{ padding: 16, display: 'flex', flexDirection: 'column', gap: 10 }}>
                <div className="row" style={{ gap: 10 }}><div className="skeleton" style={{ width: 28, height: 28, borderRadius: 8 }} /><div className="skeleton" style={{ height: 15, width: '45%' }} /></div>
                <div className="skeleton" style={{ height: 12, width: '70%', marginLeft: 38 }} />
                <div className="skeleton" style={{ height: 12, width: '60%', marginLeft: 38 }} />
              </div>
            )}
            {stage === 'done' && course?.id && window.isRealCourseId && window.isRealCourseId(course.id) && (
              <button
                className="btn btn-soft"
                style={{ alignSelf: 'flex-start', marginTop: 4 }}
                onClick={async () => {
                  const tempId = 'tmp-m-' + Date.now();
                  const placeholder = { id: tempId, title: 'Untitled module', summary: '', learningObjectives: [], lessons: [] };
                  // Optimistic append.
                  if (window.__setCourseFromBuilder) window.__setCourseFromBuilder(c => c ? ({ ...c, modules: [...c.modules, placeholder] }) : c);
                  try {
                    const { module } = await window.API.addModule(course.id);
                    if (window.__setCourseFromBuilder) window.__setCourseFromBuilder(c => {
                      if (!c) return c;
                      return {
                        ...c,
                        modules: c.modules.map(m => m.id === tempId ? {
                          id: module.id,
                          title: module.title,
                          summary: module.summary,
                          learningObjectives: module.learningObjectives || [],
                          lessons: [],
                        } : m),
                      };
                    });
                    if (markSaved) markSaved();
                  } catch (e) {
                    if (window.__setCourseFromBuilder) window.__setCourseFromBuilder(c => c ? ({ ...c, modules: c.modules.filter(m => m.id !== tempId) }) : c);
                    if (e?.code === 'PLAN_LIMIT_EXCEEDED') {
                      if (window.confirm((e.message || 'Free plan limit reached.') + '\n\nGo to the upgrade page now?')) {
                        go && go('upgrade');
                      }
                    } else {
                      console.error('addModule failed', e);
                    }
                  }
                }}
              ><Plus size={15} /> Add module</button>
            )}
          </div>
        )}

        {stage === 'done' && tab === 'lesson' && (
          <LessonEditor
            course={course}
            selectedLessonId={selectedLessonId}
            onSelectLesson={onSelectLesson}
            markSaved={markSaved}
            refresh={refresh}
          />
        )}
        {stage === 'done' && tab === 'quiz' && <QuizPreview />}
        {stage === 'done' && tab === 'notes' && <NotesPreview />}
      </div>

      {historyOpen && realId && (
        <HistoryDrawer
          history={history}
          loading={historyLoading}
          onClose={() => setHistoryOpen(false)}
          onRefresh={loadHistory}
        />
      )}
    </div>
  );
}

function HistoryDrawer({ history, loading, onClose, onRefresh }) {
  return (
    <div
      style={{
        position: 'fixed',
        right: 0,
        top: 0,
        width: 400,
        height: '100vh',
        background: 'var(--surface)',
        borderLeft: '1px solid var(--line)',
        zIndex: 30,
        padding: 24,
        overflowY: 'auto',
        boxShadow: 'var(--sh-md)',
      }}
      className="scroll fade-in"
    >
      <div className="row" style={{ justifyContent: 'space-between', marginBottom: 6, gap: 8 }}>
        <div className="row" style={{ gap: 9 }}>
          <span className="center" style={{ width: 28, height: 28, borderRadius: 8, background: 'var(--brand-tint)', color: 'var(--brand-ink)', flexShrink: 0 }}><Clock size={14} /></span>
          <div>
            <div style={{ fontSize: 14.5, fontWeight: 700, letterSpacing: '-0.01em' }}>Edit history</div>
            <div className="muted mono" style={{ fontSize: 11 }}>{history ? history.length : 0} edits</div>
          </div>
        </div>
        <div className="row" style={{ gap: 4 }}>
          <button className="btn btn-icon btn-ghost btn-sm" title="Refresh" aria-label="Refresh" onClick={onRefresh}><Refresh size={15} /></button>
          <button className="btn btn-icon btn-ghost btn-sm" title="Close" aria-label="Close" onClick={onClose}><X size={16} /></button>
        </div>
      </div>

      <div style={{ marginTop: 16, display: 'flex', flexDirection: 'column', gap: 10 }}>
        {loading && history == null && (
          <>
            {[0, 1, 2, 3].map(i => (
              <div key={i} className="card" style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
                <div className="skeleton" style={{ height: 11, width: '40%' }} />
                <div className="skeleton" style={{ height: 12, width: '80%' }} />
                <div className="skeleton" style={{ height: 11, width: '70%' }} />
              </div>
            ))}
          </>
        )}

        {!loading && history && history.length === 0 && (
          <div className="card" style={{ padding: '18px 16px' }}>
            <div style={{ fontSize: 13.5, fontWeight: 600, marginBottom: 4 }}>No edits yet</div>
            <div className="muted" style={{ fontSize: 12.5, lineHeight: 1.5 }}>
              Refine anything inline — your changes are tracked here.
            </div>
          </div>
        )}

        {history && history.length > 0 && history.map(e => {
          const before = (e.beforeValue || '').slice(0, 200);
          const after = (e.afterValue || '').slice(0, 200);
          return (
            <div key={e.id} className="card" style={{ padding: 12, display: 'flex', flexDirection: 'column', gap: 6 }}>
              <div className="row" style={{ justifyContent: 'space-between', gap: 8 }}>
                <span className="mono muted" style={{ fontSize: 10.5, letterSpacing: '.08em', textTransform: 'uppercase' }}>{ago(e.editedAt)}</span>
                <span className="pill" style={{ height: 18, fontSize: 10.5, padding: '0 8px' }}>{e.entityType}</span>
              </div>
              <div style={{ fontSize: 13, fontWeight: 600, letterSpacing: '-0.005em', wordBreak: 'break-word' }}>
                {e.entityLabel} <span className="muted" style={{ fontWeight: 500 }}>· {e.field}</span>
              </div>
              <div className="muted mono" style={{ fontSize: 11.5, lineHeight: 1.5, wordBreak: 'break-word' }}>
                <div><b style={{ color: 'var(--rose, #c84a4a)' }}>Before:</b> {before || <span style={{ opacity: .6 }}>(empty)</span>}</div>
                <div style={{ marginTop: 3 }}><b style={{ color: 'var(--green, #2f8a4a)' }}>After:</b> {after || <span style={{ opacity: .6 }}>(empty)</span>}</div>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

/* ---------- ExportMenu — dropdown next to the Export button ---------- */
const EXPORT_OPTIONS = [
  { group: 'Documents', items: [
    { format: 'docx',     label: 'Word (.docx)',      desc: 'Editable Microsoft Word document' },
    { format: 'pdf',      label: 'PDF (.pdf)',         desc: 'Portable document for sharing & printing' },
    { format: 'html',     label: 'HTML (.html)',       desc: 'Self-contained web page' },
    { format: 'markdown', label: 'Markdown (.md)',     desc: 'Plain-text outline for any editor' },
    { format: 'epub',     label: 'ePub (.epub)',       desc: 'eBook for Kindle, iBooks & e-readers' },
  ]},
  { group: 'Data', items: [
    { format: 'json',     label: 'JSON (.json)',       desc: 'Full structured course data' },
    { format: 'csv',      label: 'Quiz CSV (.csv)',    desc: 'Udemy-style practice test import' },
  ]},
  { group: 'LMS', items: [
    { format: 'scorm',    label: 'SCORM 1.2 (.zip)',   desc: 'Standards-compliant LMS package' },
  ]},
];

function ExportMenu({ courseId, disabled }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    if (!open) return;
    const onDown = (e) => {
      if (!ref.current || !ref.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener('mousedown', onDown);
    return () => document.removeEventListener('mousedown', onDown);
  }, [open]);

  const onPick = (format) => {
    setOpen(false);
    if (!courseId) return;
    window.API.exportCourse(courseId, format);
  };

  return (
    <span ref={ref} style={{ position: 'relative', display: 'inline-flex' }}>
      <Btn
        variant="outline"
        size="sm"
        icon={<Download size={15} />}
        iconR={<ChevD size={13} />}
        disabled={disabled}
        onClick={() => setOpen(o => !o)}
      >
        Export
      </Btn>
      {open && (
        <div className="card fade-in" style={{
          position: 'absolute',
          top: '100%',
          right: 0,
          marginTop: 6,
          minWidth: 270,
          padding: 6,
          zIndex: 30,
          boxShadow: 'var(--sh-md)',
          background: 'var(--surface)',
        }}>
          {EXPORT_OPTIONS.map((grp, gi) => (
            <div key={grp.group}>
              <div style={{
                fontSize: 10.5, fontWeight: 700, letterSpacing: '.06em',
                textTransform: 'uppercase', color: 'var(--muted)',
                padding: gi === 0 ? '6px 10px 4px' : '10px 10px 4px',
              }}>{grp.group}</div>
              {grp.items.map((it) => (
                <button
                  key={it.format}
                  className="nav-item"
                  style={{
                    width: '100%', padding: '6px 10px', fontSize: 13,
                    color: 'var(--ink-2)', textAlign: 'left',
                    display: 'flex', flexDirection: 'column', alignItems: 'flex-start',
                    gap: 1, lineHeight: 1.3, height: 'auto',
                  }}
                  onClick={() => onPick(it.format)}
                >
                  <span style={{ fontWeight: 600, color: 'var(--ink)' }}>{it.label}</span>
                  <span style={{ fontSize: 11.5, color: 'var(--muted)' }}>{it.desc}</span>
                </button>
              ))}
              {gi < EXPORT_OPTIONS.length - 1 && (
                <div style={{ height: 1, background: 'var(--line-2)', margin: '6px 4px' }} />
              )}
            </div>
          ))}
        </div>
      )}
    </span>
  );
}

/* ---------- KebabMenu — three-dot popover with action items ---------- */
function KebabMenu({ items, size = 16 }) {
  const [open, setOpen] = useState(false);
  const ref = useRef(null);
  useEffect(() => {
    if (!open) return;
    const onDown = (e) => {
      if (!ref.current || !ref.current.contains(e.target)) setOpen(false);
    };
    document.addEventListener('mousedown', onDown);
    return () => document.removeEventListener('mousedown', onDown);
  }, [open]);
  return (
    <span ref={ref} style={{ position: 'relative', display: 'inline-flex' }} onClick={e => e.stopPropagation()}>
      <button
        className="btn btn-icon btn-ghost btn-sm"
        onClick={(e) => { e.stopPropagation(); setOpen(o => !o); }}
      >
        <Dots size={size} />
      </button>
      {open && (
        <div className="card fade-in" style={{
          position: 'absolute',
          top: '100%',
          right: 0,
          marginTop: 4,
          minWidth: 168,
          padding: 4,
          zIndex: 25,
          boxShadow: 'var(--sh-md)',
          background: 'var(--surface)',
        }}>
          {items.filter(Boolean).map((it, idx) => (
            <button
              key={idx}
              className="nav-item"
              disabled={!!it.disabled}
              style={{
                width: '100%', height: 32, padding: '0 10px', fontSize: 13,
                color: it.danger ? 'var(--rose)' : 'var(--ink-2)',
                opacity: it.disabled ? 0.45 : 1,
                cursor: it.disabled ? 'not-allowed' : 'pointer',
                textAlign: 'left',
              }}
              onClick={(e) => { e.stopPropagation(); if (it.disabled) return; setOpen(false); it.onClick && it.onClick(); }}
            >
              {it.icon}
              <span>{it.label}</span>
            </button>
          ))}
        </div>
      )}
    </span>
  );
}

/* ---------- ModuleList: handles module-level drag-and-drop ---------- */
function ModuleList({ modules, editable, markSaved, onSelectLesson, onAddLesson, selectedLessonId, go, courseId, courseIsReal, refresh }) {
  const [draggingId, setDraggingId] = useState(null);
  const [overIdx, setOverIdx] = useState(-1);
  const [error, setError] = useState(null);
  const dragEnabled = !!editable && !!courseIsReal && !!courseId;

  const onModuleDragStart = (id) => {
    setDraggingId(id);
  };
  const onModuleDragEnd = () => {
    setDraggingId(null);
    setOverIdx(-1);
  };
  const onZoneDragOver = (e, idx) => {
    if (!draggingId) return;
    e.preventDefault();
    if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
    if (overIdx !== idx) setOverIdx(idx);
  };
  const onZoneDrop = async (e, idx) => {
    e.preventDefault();
    const id = (e.dataTransfer && e.dataTransfer.getData('text/module-id')) || draggingId;
    setDraggingId(null);
    setOverIdx(-1);
    if (!id) return;
    const fromIdx = modules.findIndex(mm => mm.id === id);
    if (fromIdx < 0) return;
    if (fromIdx === idx || fromIdx + 1 === idx) return;
    const newOrder = reorderArray(modules.map(mm => mm.id), fromIdx, idx);
    // Optimistic mutation
    const reordered = newOrder.map(mid => modules.find(mm => mm.id === mid));
    // Mutate the source array in place so React keeps continuity until refresh.
    modules.splice(0, modules.length, ...reordered);
    try {
      await window.API.reorderModules(courseId, newOrder);
      markSaved && markSaved();
      refresh && refresh();
    } catch (err) {
      setError(err?.message || 'Could not reorder modules');
    }
  };

  const onMoveModule = async (fromIdx, dir) => {
    const toIdx = dir === 'up' ? fromIdx - 1 : fromIdx + 1;
    if (toIdx < 0 || toIdx >= modules.length) return;
    const ids = modules.map(mm => mm.id);
    [ids[fromIdx], ids[toIdx]] = [ids[toIdx], ids[fromIdx]];
    // Optimistic: swap in shared course state.
    if (window.__setCourseFromBuilder) {
      window.__setCourseFromBuilder(c => {
        if (!c) return c;
        const next = c.modules.slice();
        [next[fromIdx], next[toIdx]] = [next[toIdx], next[fromIdx]];
        return { ...c, modules: next };
      });
    }
    try {
      await window.API.reorderModules(courseId, ids);
      markSaved && markSaved();
    } catch (err) {
      setError(err?.message || 'Could not reorder');
    }
  };

  const onDeleteModule = async (idx) => {
    const target = modules[idx];
    if (!target || !target.id) return;
    if (typeof target.id === 'string' && target.id.startsWith('tmp-')) return;
    if (!window.confirm(`Delete module "${target.title}"? All its lessons and quiz will be removed.`)) return;
    // Optimistic remove
    if (window.__setCourseFromBuilder) {
      window.__setCourseFromBuilder(c => c ? ({ ...c, modules: c.modules.filter(m => m.id !== target.id) }) : c);
    }
    try {
      await window.API.deleteModule(target.id);
      markSaved && markSaved();
    } catch (err) {
      // Revert by refreshing the tree from server.
      if (refresh) refresh();
      setError(err?.message || 'Could not delete module');
    }
  };

  const indicatorAt = (idx) => (
    dragEnabled && draggingId && overIdx === idx ? (
      <div style={{ height: 2, background: 'var(--brand)', borderRadius: 2, margin: '2px 0' }} />
    ) : (
      <div style={{ height: 6 }} />
    )
  );

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 0 }}>
      {error && (
        <div className="card fade-in" style={{ marginBottom: 8, padding: '8px 12px', background: 'var(--rose-tint, #fde9ee)', color: '#9c1f3c', fontSize: 12.5 }}>{error}</div>
      )}
      {modules.map((m, i) => (
        <React.Fragment key={m.id || i}>
          <div
            onDragOver={(e) => onZoneDragOver(e, i)}
            onDrop={(e) => onZoneDrop(e, i)}
            onDragLeave={() => { if (overIdx === i) setOverIdx(-1); }}
          >
            {indicatorAt(i)}
          </div>
          <div style={{ opacity: draggingId === m.id ? 0.5 : 1, transition: 'opacity .15s var(--ease)' }}>
            <ModuleCard
              m={m}
              i={i}
              total={modules.length}
              editable={editable}
              markSaved={markSaved}
              onSelectLesson={onSelectLesson}
              onAddLesson={onAddLesson}
              selectedLessonId={selectedLessonId}
              go={go}
              courseId={courseId}
              courseIsReal={courseIsReal}
              refresh={refresh}
              dragEnabled={dragEnabled}
              onModuleDragStart={onModuleDragStart}
              onModuleDragEnd={onModuleDragEnd}
              onMoveModule={onMoveModule}
              onDeleteModule={onDeleteModule}
            />
          </div>
        </React.Fragment>
      ))}
      <div
        onDragOver={(e) => onZoneDragOver(e, modules.length)}
        onDrop={(e) => onZoneDrop(e, modules.length)}
        onDragLeave={() => { if (overIdx === modules.length) setOverIdx(-1); }}
      >
        {indicatorAt(modules.length)}
      </div>
    </div>
  );
}

function ModuleCard({ m, i, total, editable, markSaved, onSelectLesson, onAddLesson, selectedLessonId, go, courseId, courseIsReal, refresh, dragEnabled, onModuleDragStart, onModuleDragEnd, onMoveModule, onDeleteModule }) {
  const [open, setOpen] = useState(true);
  const [lessonDraggingIdx, setLessonDraggingIdx] = useState(-1);
  const [lessonOverIdx, setLessonOverIdx] = useState(-1);
  const [lessonError, setLessonError] = useState(null);
  const lessonDragEnabled = !!editable && !!courseIsReal && !!m.id;

  const orderedLessonRows = m.lessons; // includes the optional quiz pseudo-row at the end
  const lessonOnlyIds = m.lessons.filter(l => l.type === 'lesson' && l.id).map(l => l.id);

  const onLessonDragStart = (e, j, l) => {
    if (!lessonDragEnabled || l.type !== 'lesson') return;
    setLessonDraggingIdx(j);
    if (e.dataTransfer) {
      try {
        e.dataTransfer.setData('text/lesson-id', l.id);
        e.dataTransfer.setData('text/from-module', m.id || '');
        e.dataTransfer.effectAllowed = 'move';
      } catch (err) {}
    }
  };
  const onLessonDragEnd = () => {
    setLessonDraggingIdx(-1);
    setLessonOverIdx(-1);
  };
  const onLessonDragOver = (e, j) => {
    if (lessonDraggingIdx < 0) return;
    e.preventDefault();
    if (e.dataTransfer) e.dataTransfer.dropEffect = 'move';
    // figure out whether mouse is above or below the row midpoint
    const rect = e.currentTarget.getBoundingClientRect();
    const offset = e.clientY - rect.top;
    const insertIdx = offset < rect.height / 2 ? j : j + 1;
    if (lessonOverIdx !== insertIdx) setLessonOverIdx(insertIdx);
  };
  const onLessonDrop = async (e) => {
    e.preventDefault();
    const fromModule = e.dataTransfer && e.dataTransfer.getData('text/from-module');
    const lessonId = e.dataTransfer && e.dataTransfer.getData('text/lesson-id');
    const targetIdx = lessonOverIdx;
    setLessonDraggingIdx(-1);
    setLessonOverIdx(-1);
    // v1: cross-module drop disabled
    if (fromModule && fromModule !== (m.id || '')) return;
    if (!lessonId || targetIdx < 0) return;
    const fromIdx = lessonOnlyIds.indexOf(lessonId);
    if (fromIdx < 0) return;
    // targetIdx is an index into the full lessons array (including quiz). Translate to lesson-only index.
    let translated = 0;
    for (let k = 0; k < targetIdx && k < m.lessons.length; k++) {
      if (m.lessons[k].type === 'lesson') translated += 1;
    }
    if (fromIdx === translated || fromIdx + 1 === translated) return;
    const newIds = reorderArray(lessonOnlyIds, fromIdx, translated);
    // optimistic mutate
    const lessonNodes = m.lessons.filter(l => l.type === 'lesson');
    const newLessonNodes = newIds.map(id => lessonNodes.find(l => l.id === id));
    const quizNodes = m.lessons.filter(l => l.type !== 'lesson');
    m.lessons.splice(0, m.lessons.length, ...newLessonNodes, ...quizNodes);
    try {
      await window.API.reorderLessons(m.id, newIds);
      markSaved && markSaved();
      refresh && refresh();
    } catch (err) {
      setLessonError(err?.message || 'Could not reorder lessons');
    }
  };

  return (
    <div className="card fade-up" style={{ overflow: 'hidden' }}>
      <div className="row" style={{ padding: '13px 16px', gap: 12 }}>
        {dragEnabled && (
          <span
            draggable={true}
            onDragStart={(e) => {
              if (e.dataTransfer) {
                try {
                  e.dataTransfer.setData('text/module-id', m.id || '');
                  e.dataTransfer.effectAllowed = 'move';
                } catch (err) {}
              }
              onModuleDragStart && onModuleDragStart(m.id);
            }}
            onDragEnd={() => onModuleDragEnd && onModuleDragEnd()}
            title="Drag to reorder"
            className="center"
            style={{ width: 16, height: 22, cursor: 'grab', color: 'var(--faint)', flexShrink: 0 }}
          >
            <Dots size={14} />
          </span>
        )}
        <span className="center" style={{ width: 28, height: 28, borderRadius: 8, background: 'var(--brand-tint)', color: 'var(--brand-ink)', fontSize: 13, fontWeight: 700, flexShrink: 0 }}>{i + 1}</span>
        <div
          className="grow"
          contentEditable={editable}
          suppressContentEditableWarning
          onBlur={(e) => {
            const t = e.currentTarget.innerText.trim();
            if (!t || t === m.title || !m.id) return;
            window.API.patchModule(m.id, { title: t })
              .then(() => { m.title = t; markSaved && markSaved(); })
              .catch(() => {});
          }}
          style={{ fontSize: 15, fontWeight: 600, letterSpacing: '-0.01em', outline: 'none' }}>{m.title}</div>
        <span className="muted mono" style={{ fontSize: 11.5 }}>{m.lessons.length} lessons</span>
        {editable && onMoveModule && onDeleteModule && (
          <KebabMenu items={[
            { label: 'Move up', icon: <ChevD size={13} style={{ transform: 'rotate(180deg)' }} />, disabled: i === 0, onClick: () => onMoveModule(i, 'up') },
            { label: 'Move down', icon: <ChevD size={13} />, disabled: i >= (total || 0) - 1, onClick: () => onMoveModule(i, 'down') },
            { label: 'Delete', icon: <X size={13} />, danger: true, onClick: () => onDeleteModule(i) },
          ]} />
        )}
        <button className="btn btn-icon btn-ghost btn-sm" onClick={() => setOpen(!open)}><ChevD size={16} style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .2s' }} /></button>
      </div>
      {open && (
        <div style={{ borderTop: '1px solid var(--line)' }}>
          {lessonError && (
            <div className="fade-in" style={{ padding: '8px 16px', background: 'var(--rose-tint, #fde9ee)', color: '#9c1f3c', fontSize: 12 }}>{lessonError}</div>
          )}
          {orderedLessonRows.map((l, j) => {
            const isLesson = l.type === 'lesson';
            const isSelected = isLesson && selectedLessonId && l.id === selectedLessonId;
            const draggableRow = lessonDragEnabled && isLesson;
            const showIndicatorAbove = lessonDragEnabled && lessonDraggingIdx >= 0 && lessonOverIdx === j;
            return (
              <React.Fragment key={l.id || j}>
                {showIndicatorAbove && (
                  <div style={{ height: 2, background: 'var(--brand)', margin: '0 16px' }} />
                )}
                <div
                  className="row"
                  draggable={draggableRow}
                  onDragStart={(e) => onLessonDragStart(e, j, l)}
                  onDragEnd={onLessonDragEnd}
                  onDragOver={(e) => onLessonDragOver(e, j)}
                  onDrop={onLessonDrop}
                  style={{
                    padding: '10px 16px 10px 20px',
                    gap: 11,
                    borderBottom: j < orderedLessonRows.length - 1 ? '1px solid var(--line-2)' : 'none',
                    cursor: isLesson ? 'pointer' : 'default',
                    background: isSelected ? 'var(--brand-tint)' : 'transparent',
                    transition: 'background .12s var(--ease)',
                    opacity: draggableRow && lessonDraggingIdx === j ? 0.5 : 1,
                  }}
                  onMouseEnter={e => { if (!isSelected) e.currentTarget.style.background = 'var(--surface-2)'; }}
                  onMouseLeave={e => { if (!isSelected) e.currentTarget.style.background = 'transparent'; }}
                  onClick={(e) => {
                    // Don't fire when clicking inside the editable title.
                    if (e.target && (e.target.getAttribute && e.target.getAttribute('contenteditable') === 'true')) return;
                    if (l.type === 'quiz' && l.id) {
                      // Quiz row → navigate to quiz editor (use the module id, not the quiz id, in the URL).
                      if (go && courseIsReal && courseId && m.id) {
                        go('quiz', { id: m.id, courseId });
                      }
                      return;
                    }
                    if (!isLesson || !l.id) return;
                    // Real course + real lesson → navigate to the dedicated full-page editor.
                    const lessonReal = window.isRealLessonId ? window.isRealLessonId(l.id) : (typeof l.id === 'string' && /^c[a-z0-9]{20,}$/i.test(l.id));
                    if (go && courseIsReal && courseId && lessonReal) {
                      go('lesson', { id: l.id, courseId });
                      return;
                    }
                    if (onSelectLesson) onSelectLesson(l.id);
                  }}
                >
                  <LessonIcon type={l.type} />
                  <span
                    className="grow"
                    contentEditable={editable && isLesson && !!l.id}
                    suppressContentEditableWarning
                    onClick={(e) => { if (editable && isLesson) e.stopPropagation(); }}
                    onMouseDown={(e) => { if (editable && isLesson) e.stopPropagation(); }}
                    onDragStart={(e) => { if (editable && isLesson) e.preventDefault(); }}
                    onBlur={(e) => {
                      if (!isLesson || !l.id) return;
                      const t = e.currentTarget.innerText.trim();
                      if (!t || t === l.title) return;
                      window.API.patchLesson(l.id, { title: t })
                        .then(() => { l.title = t; markSaved && markSaved(); })
                        .catch(() => {});
                    }}
                    onKeyDown={(e) => {
                      if (e.key === 'Enter') { e.preventDefault(); e.currentTarget.blur(); }
                    }}
                    style={{ fontSize: 13.8, color: isSelected ? 'var(--brand-ink)' : 'inherit', fontWeight: isSelected ? 600 : 400, outline: 'none', cursor: editable && isLesson ? 'text' : 'inherit' }}>{l.title}</span>
                  <span className="badge badge-gray" style={{ textTransform: 'capitalize' }}>{l.type}</span>
                  <span className="muted mono" style={{ fontSize: 11.5, width: 30, textAlign: 'right' }}>{l.dur}</span>
                  {editable && isLesson && l.id && !(typeof l.id === 'string' && l.id.startsWith('tmp-')) && (() => {
                    const lessonOnly = m.lessons.filter(x => x.type === 'lesson');
                    const myIdx = lessonOnly.findIndex(x => x.id === l.id);
                    const moveLesson = async (dir) => {
                      const toIdx = dir === 'up' ? myIdx - 1 : myIdx + 1;
                      if (toIdx < 0 || toIdx >= lessonOnly.length) return;
                      const ids = lessonOnly.map(x => x.id);
                      [ids[myIdx], ids[toIdx]] = [ids[toIdx], ids[myIdx]];
                      if (window.__setCourseFromBuilder) {
                        window.__setCourseFromBuilder(c => {
                          if (!c) return c;
                          return {
                            ...c,
                            modules: c.modules.map(mm => mm.id !== m.id ? mm : ({
                              ...mm,
                              lessons: [
                                ...ids.map(id => mm.lessons.find(x => x.id === id)),
                                ...mm.lessons.filter(x => x.type !== 'lesson'),
                              ],
                            })),
                          };
                        });
                      }
                      try { await window.API.reorderLessons(m.id, ids); markSaved && markSaved(); }
                      catch (e) { setLessonError(e?.message || 'Reorder failed'); }
                    };
                    const removeLesson = async () => {
                      if (!window.confirm(`Delete lesson "${l.title}"?`)) return;
                      if (window.__setCourseFromBuilder) {
                        window.__setCourseFromBuilder(c => c ? ({
                          ...c,
                          modules: c.modules.map(mm => mm.id !== m.id ? mm : ({ ...mm, lessons: mm.lessons.filter(x => x.id !== l.id) })),
                        }) : c);
                      }
                      try { await window.API.deleteLesson(l.id); markSaved && markSaved(); }
                      catch (e) { setLessonError(e?.message || 'Delete failed'); if (refresh) refresh(); }
                    };
                    return (
                      <KebabMenu items={[
                        { label: 'Move up', icon: <ChevD size={13} style={{ transform: 'rotate(180deg)' }} />, disabled: myIdx === 0, onClick: () => moveLesson('up') },
                        { label: 'Move down', icon: <ChevD size={13} />, disabled: myIdx >= lessonOnly.length - 1, onClick: () => moveLesson('down') },
                        { label: 'Delete', icon: <X size={13} />, danger: true, onClick: removeLesson },
                      ]} size={14} />
                    );
                  })()}
                </div>
              </React.Fragment>
            );
          })}
          {/* trailing drop indicator after the last lesson row */}
          {lessonDragEnabled && lessonDraggingIdx >= 0 && lessonOverIdx === orderedLessonRows.length && (
            <div style={{ height: 2, background: 'var(--brand)', margin: '0 16px' }} />
          )}
          {/* invisible trailing dropzone for the very end (matches lesson-only insert at tail) */}
          {lessonDragEnabled && (
            <div
              onDragOver={(e) => { if (lessonDraggingIdx < 0) return; e.preventDefault(); if (e.dataTransfer) e.dataTransfer.dropEffect = 'move'; if (lessonOverIdx !== orderedLessonRows.length) setLessonOverIdx(orderedLessonRows.length); }}
              onDrop={onLessonDrop}
              style={{ height: 6 }}
            />
          )}
          {editable && (() => {
            const moduleSyncing = typeof m.id === 'string' && m.id.startsWith('tmp-');
            return (
              <button
                className="btn btn-ghost btn-sm"
                style={{ margin: '8px 14px', color: 'var(--brand)', opacity: moduleSyncing ? 0.5 : 1 }}
                disabled={moduleSyncing}
                title={moduleSyncing ? 'Saving module… try again in a moment' : undefined}
                onClick={() => !moduleSyncing && onAddLesson && m.id && onAddLesson(m.id)}
              >
                <Plus size={14} /> Add lesson
              </button>
            );
          })()}
        </div>
      )}
    </div>
  );
}

function EditBadge() {
  return <span className="badge" style={{ background: 'var(--brand-tint)', color: 'var(--brand)' }}><Edit size={11} /> Editable</span>;
}

/* ---------- Lesson editor (the real one) ---------- */

function flattenLessons(course) {
  const flat = [];
  (course?.modules || []).forEach((m, mi) => {
    (m.lessons || []).forEach((l, li) => {
      if (l.type !== 'lesson') return;
      flat.push({ lesson: l, moduleIndex: mi, lessonIndex: li, moduleId: m.id, moduleTitle: m.title });
    });
  });
  return flat;
}

function findLesson(course, lessonId) {
  if (!lessonId) return null;
  const flat = flattenLessons(course);
  const i = flat.findIndex(x => x.lesson.id === lessonId);
  if (i < 0) return null;
  return { ...flat[i], flatIndex: i, total: flat.length, flat };
}

function wordCount(s) {
  return (s || '').trim().split(/\s+/).filter(Boolean).length;
}
function readMinutes(s) {
  return Math.max(1, Math.round(wordCount(s) / 180));
}

function LessonEditor({ course, selectedLessonId, onSelectLesson, markSaved, refresh }) {
  const found = findLesson(course, selectedLessonId);

  if (!found) {
    return (
      <div className="card fade-in" style={{ padding: '36px 32px', textAlign: 'center' }}>
        <div className="center" style={{ width: 56, height: 56, borderRadius: 14, background: 'var(--brand-tint)', color: 'var(--brand-ink)', margin: '0 auto 16px' }}>
          <File size={22} />
        </div>
        <h3 style={{ fontSize: 17, letterSpacing: '-0.01em', marginBottom: 6 }}>No lesson selected</h3>
        <p className="muted" style={{ fontSize: 14, lineHeight: 1.55, maxWidth: 380, margin: '0 auto' }}>
          Pick a lesson from the outline to open the editor. You can rewrite the body, regenerate it with AI, or insert images.
        </p>
        <button className="btn btn-soft btn-sm" style={{ marginTop: 18 }} onClick={() => {
          const first = flattenLessons(course)[0];
          if (first) onSelectLesson(first.lesson.id);
        }}>
          <List size={14} /> Open the first lesson
        </button>
      </div>
    );
  }

  return (
    <LessonEditorInner
      key={found.lesson.id}
      course={course}
      found={found}
      onSelectLesson={onSelectLesson}
      markSaved={markSaved}
      refresh={refresh}
    />
  );
}

function LessonEditorInner({ course, found, onSelectLesson, markSaved, refresh }) {
  const { lesson, moduleIndex, lessonIndex, moduleId, moduleTitle, flatIndex, total, flat } = found;
  const [titleDraft, setTitleDraft] = useState(lesson.title);
  const [body, setBody] = useState(lesson.body || '');
  const [slideNotes, setSlideNotes] = useState(lesson.slideNotes || '');
  const [busy, setBusy] = useState(null); // 'regen' | 'improve' | 'delete' | 'addBelow'
  const [error, setError] = useState(null);
  const [regenOpen, setRegenOpen] = useState(false);
  const [regenGuidance, setRegenGuidance] = useState('');
  const [improveOpen, setImproveOpen] = useState(false);
  const [imageDrawerOpen, setImageDrawerOpen] = useState(false);
  const bodyRef = useRef(null);

  // Keep local state aligned if upstream course refreshes the same lesson.
  useEffect(() => {
    setTitleDraft(lesson.title);
    setBody(lesson.body || '');
    setSlideNotes(lesson.slideNotes || '');
    setError(null);
  }, [lesson.id, lesson.title, lesson.body, lesson.slideNotes]);

  const saveField = async (field, value, prev) => {
    if (value === prev) return;
    try {
      await window.API.patchLesson(lesson.id, { [field]: value });
      lesson[field] = value;
      markSaved && markSaved();
    } catch (e) {
      setError(e?.message || 'Save failed');
    }
  };

  const prev = flatIndex > 0 ? flat[flatIndex - 1] : null;
  const next = flatIndex < total - 1 ? flat[flatIndex + 1] : null;

  const onRegenerate = async () => {
    setBusy('regen');
    setError(null);
    try {
      const { lesson: updated } = await window.API.regenerateLesson(lesson.id, {
        guidance: regenGuidance.trim() || undefined,
      });
      setTitleDraft(updated.title);
      setBody(updated.body || '');
      setSlideNotes(updated.slideNotes || '');
      lesson.title = updated.title;
      lesson.body = updated.body || '';
      lesson.slideNotes = updated.slideNotes || '';
      setRegenOpen(false);
      setRegenGuidance('');
      markSaved && markSaved();
      refresh && refresh();
    } catch (e) {
      setError(e?.message || 'Regeneration failed');
    } finally {
      setBusy(null);
    }
  };

  const onImprove = async ({ tone, readingLevel, addExamples, addSummary }) => {
    setBusy('improve');
    setError(null);
    try {
      const { improved } = await window.API.improveLesson({
        lessonText: body && body.trim() ? body : (titleDraft + '\n\n' + (slideNotes || '')),
        audience: (course && course.author && course.author.name) || (course && course.description) || 'general learners',
        tone,
        readingLevel,
        addExamples,
        addSummary,
      });
      await window.API.patchLesson(lesson.id, { title: improved.title, body: improved.body });
      setTitleDraft(improved.title);
      setBody(improved.body);
      lesson.title = improved.title;
      lesson.body = improved.body;
      setImproveOpen(false);
      markSaved && markSaved();
      refresh && refresh();
    } catch (e) {
      setError(e?.message || 'Improve failed');
    } finally {
      setBusy(null);
    }
  };

  const insertImage = async (img) => {
    const alt = (img.title || 'image').replace(/[\[\]]/g, '').slice(0, 140);
    const md = `![${alt}](${img.url})`;
    let nextBody;
    const ta = bodyRef.current;
    if (ta && document.activeElement === ta) {
      const start = ta.selectionStart || 0;
      const end = ta.selectionEnd || 0;
      const before = body.slice(0, start);
      const after = body.slice(end);
      const sep = before && !before.endsWith('\n') ? '\n\n' : '';
      nextBody = before + sep + md + (after.startsWith('\n') ? '' : '\n') + after;
    } else {
      const sep = !body || body.endsWith('\n') ? '' : '\n\n';
      nextBody = body + sep + md + '\n';
    }
    setBody(nextBody);
    setImageDrawerOpen(false);
    try {
      await window.API.patchLesson(lesson.id, { body: nextBody });
      lesson.body = nextBody;
      markSaved && markSaved();
    } catch (e) {
      setError(e?.message || 'Image insert failed');
    }
  };

  const onAddBelow = async () => {
    setBusy('addBelow');
    setError(null);
    try {
      const { lesson: created } = await window.API.addLesson(moduleId, {
        title: 'Untitled lesson',
        body: '',
        slideNotes: '',
        afterOrder: lesson.order != null ? lesson.order : lessonIndex,
      });
      await (refresh && refresh());
      if (created) onSelectLesson(created.id);
      markSaved && markSaved();
    } catch (e) {
      setError(e?.message || 'Add lesson failed');
    } finally {
      setBusy(null);
    }
  };

  const onDelete = async () => {
    if (!confirm(`Delete lesson “${lesson.title}”? This cannot be undone.`)) return;
    setBusy('delete');
    setError(null);
    try {
      await window.API.deleteLesson(lesson.id);
      const neighborId = (next && next.lesson.id) || (prev && prev.lesson.id) || null;
      await (refresh && refresh());
      onSelectLesson(neighborId);
      markSaved && markSaved();
    } catch (e) {
      setError(e?.message || 'Delete failed');
      setBusy(null);
    }
  };

  const words = wordCount(body);
  const minutes = readMinutes(body);

  return (
    <div className="card fade-in" style={{ padding: '28px 32px' }}>
      {/* Breadcrumb + prev/next */}
      <div className="row" style={{ justifyContent: 'space-between', marginBottom: 14, gap: 12, flexWrap: 'wrap' }}>
        <div className="row" style={{ gap: 10, flexWrap: 'wrap', minWidth: 0 }}>
          <span className="mono" style={{ fontSize: 11, letterSpacing: '.1em', textTransform: 'uppercase', color: 'var(--brand)' }}>
            Module {moduleIndex + 1} · Lesson {lessonIndex + 1} <span className="muted" style={{ letterSpacing: 0, textTransform: 'none' }}>/ {total}</span>
          </span>
          <span className="muted" style={{ fontSize: 12.5, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', maxWidth: 280 }}>{moduleTitle}</span>
        </div>
        <div className="row" style={{ gap: 6 }}>
          <button
            className="btn btn-icon btn-ghost btn-sm"
            title="Previous lesson"
            disabled={!prev}
            style={{ opacity: prev ? 1 : 0.4 }}
            onClick={() => prev && onSelectLesson(prev.lesson.id)}
          ><ChevL size={16} /></button>
          <button
            className="btn btn-icon btn-ghost btn-sm"
            title="Next lesson"
            disabled={!next}
            style={{ opacity: next ? 1 : 0.4 }}
            onClick={() => next && onSelectLesson(next.lesson.id)}
          ><ChevR size={16} /></button>
        </div>
      </div>

      {/* Title */}
      <div
        contentEditable
        suppressContentEditableWarning
        onBlur={(e) => {
          const t = e.currentTarget.innerText.trim();
          if (!t || t === lesson.title) { setTitleDraft(lesson.title); return; }
          setTitleDraft(t);
          saveField('title', t, lesson.title);
        }}
        style={{ fontSize: 26, fontWeight: 700, letterSpacing: '-0.02em', marginBottom: 8, outline: 'none', wordBreak: 'normal', overflowWrap: 'break-word' }}
      >{titleDraft}</div>

      <div className="row muted" style={{ gap: 14, fontSize: 12.5, marginBottom: 18, flexWrap: 'wrap' }}>
        <span className="row" style={{ gap: 5 }}><Clock size={13} /> {minutes} min read</span>
        <span className="row" style={{ gap: 5 }}><File size={13} /> {words} words</span>
      </div>

      {/* Toolbar */}
      <div className="row" style={{ gap: 8, flexWrap: 'wrap', marginBottom: 16, position: 'relative' }}>
        <button
          className="btn btn-soft btn-sm"
          onClick={() => { setRegenOpen(v => !v); setImproveOpen(false); }}
          disabled={busy === 'regen'}
        >
          {busy === 'regen' ? <span className="spinner dark" style={{ width: 13, height: 13 }} /> : <Refresh size={14} />}
          Regenerate
        </button>
        <button
          className="btn btn-ghost btn-sm"
          onClick={() => { setImproveOpen(v => !v); setRegenOpen(false); }}
          disabled={busy === 'improve'}
        >
          {busy === 'improve' ? <span className="spinner dark" style={{ width: 13, height: 13 }} /> : <Wand size={14} />}
          Improve
        </button>
        <button
          className="btn btn-ghost btn-sm"
          onClick={() => setImageDrawerOpen(true)}
        >
          <Sparkles size={14} /> Add image
        </button>
        <button
          className="btn btn-ghost btn-sm"
          onClick={onAddBelow}
          disabled={busy === 'addBelow'}
        >
          {busy === 'addBelow' ? <span className="spinner dark" style={{ width: 13, height: 13 }} /> : <Plus size={14} />}
          Add lesson below
        </button>
        <span style={{ flex: 1 }} />
        <button
          className="btn btn-ghost btn-sm"
          style={{ color: 'var(--rose, #c84a4a)' }}
          onClick={onDelete}
          disabled={busy === 'delete'}
          title="Delete lesson"
        >
          <Trash size={14} /> Delete
        </button>
      </div>

      {regenOpen && (
        <div className="fade-in" style={{ marginBottom: 16, padding: 14, background: 'var(--surface-2)', borderRadius: 'var(--r-sm)', border: '1px solid var(--line)' }}>
          <div className="mono" style={{ fontSize: 10.5, letterSpacing: '.12em', textTransform: 'uppercase', color: 'var(--faint)', marginBottom: 8 }}>Optional guidance</div>
          <textarea
            className="field"
            placeholder="What should change? e.g. “Add a real-world example for designers, and make it slightly shorter.”"
            rows={2}
            value={regenGuidance}
            onChange={e => setRegenGuidance(e.target.value)}
            style={{ width: '100%', resize: 'vertical', fontSize: 14, padding: 10 }}
          />
          <div className="row" style={{ gap: 8, marginTop: 10, justifyContent: 'flex-end' }}>
            <button className="btn btn-ghost btn-sm" onClick={() => { setRegenOpen(false); setRegenGuidance(''); }}>Cancel</button>
            <button className="btn btn-brand btn-sm" onClick={onRegenerate} disabled={busy === 'regen'}>
              {busy === 'regen' ? <span className="spinner" style={{ width: 13, height: 13 }} /> : <Sparkles size={14} />}
              Regenerate lesson
            </button>
          </div>
        </div>
      )}

      {improveOpen && (
        <ImprovePopover
          onCancel={() => setImproveOpen(false)}
          onSubmit={onImprove}
          busy={busy === 'improve'}
          defaultTone={course && course._raw && course._raw.tone ? course._raw.tone : 'professional'}
        />
      )}

      {error && (
        <div className="fade-in" style={{ marginBottom: 14, padding: '10px 12px', background: 'var(--rose-tint, #fde9ee)', color: '#9c1f3c', borderRadius: 'var(--r-sm)', fontSize: 13 }}>
          {error}
        </div>
      )}

      {/* Body */}
      <div style={{ marginBottom: 18 }}>
        <div className="row" style={{ justifyContent: 'space-between', marginBottom: 6 }}>
          <span className="mono" style={{ fontSize: 10.5, letterSpacing: '.12em', textTransform: 'uppercase', color: 'var(--faint)' }}>Body</span>
          <span className="muted" style={{ fontSize: 11.5 }}>Markdown supported</span>
        </div>
        <textarea
          ref={bodyRef}
          className="field mono"
          value={body}
          onChange={e => setBody(e.target.value)}
          onBlur={() => saveField('body', body, lesson.body || '')}
          rows={16}
          style={{ width: '100%', resize: 'vertical', fontSize: 13.5, lineHeight: 1.6, padding: 14, fontFamily: 'var(--mono)' }}
          placeholder="Write the lesson body in markdown — use ## sub-headings, **bold**, and lists."
        />
      </div>

      {/* Slide notes */}
      <div>
        <div className="row" style={{ justifyContent: 'space-between', marginBottom: 6 }}>
          <span className="mono" style={{ fontSize: 10.5, letterSpacing: '.12em', textTransform: 'uppercase', color: 'var(--faint)' }}>Slide notes</span>
          <span className="muted" style={{ fontSize: 11.5 }}>3-5 talking points</span>
        </div>
        <textarea
          className="field"
          value={slideNotes}
          onChange={e => setSlideNotes(e.target.value)}
          onBlur={() => saveField('slideNotes', slideNotes, lesson.slideNotes || '')}
          rows={6}
          style={{ width: '100%', resize: 'vertical', fontSize: 14, lineHeight: 1.55, padding: 12 }}
          placeholder="One bullet per line. Conversational — what you'd actually say while presenting."
        />
      </div>

      {imageDrawerOpen && (
        <ImageSearchDrawer
          initialQuery={lesson.title}
          onClose={() => setImageDrawerOpen(false)}
          onInsert={insertImage}
        />
      )}
    </div>
  );
}

/* ---------- Improve popover ---------- */
function ImprovePopover({ onCancel, onSubmit, busy, defaultTone }) {
  const [tone, setTone] = useState(defaultTone || 'professional');
  const [readingLevel, setReadingLevel] = useState('intermediate');
  const [addExamples, setAddExamples] = useState(true);
  const [addSummary, setAddSummary] = useState(false);
  return (
    <div className="fade-in" style={{ marginBottom: 16, padding: 14, background: 'var(--surface-2)', borderRadius: 'var(--r-sm)', border: '1px solid var(--line)' }}>
      <div className="mono" style={{ fontSize: 10.5, letterSpacing: '.12em', textTransform: 'uppercase', color: 'var(--faint)', marginBottom: 10 }}>Improve lesson</div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10, marginBottom: 12 }}>
        <label style={{ display: 'flex', flexDirection: 'column', gap: 5, fontSize: 12.5 }}>
          <span className="muted">Tone</span>
          <select className="field" value={tone} onChange={e => setTone(e.target.value)} style={{ height: 38, padding: '0 10px', fontSize: 13.5 }}>
            <option value="professional">Professional</option>
            <option value="conversational">Conversational</option>
            <option value="academic">Academic</option>
            <option value="motivational">Motivational</option>
          </select>
        </label>
        <label style={{ display: 'flex', flexDirection: 'column', gap: 5, fontSize: 12.5 }}>
          <span className="muted">Reading level</span>
          <select className="field" value={readingLevel} onChange={e => setReadingLevel(e.target.value)} style={{ height: 38, padding: '0 10px', fontSize: 13.5 }}>
            <option value="intro">Intro</option>
            <option value="intermediate">Intermediate</option>
            <option value="advanced">Advanced</option>
          </select>
        </label>
      </div>
      <div className="row" style={{ gap: 16, marginBottom: 14, flexWrap: 'wrap' }}>
        <label className="row" style={{ gap: 8, fontSize: 13, cursor: 'pointer' }}>
          <Toggle on={addExamples} onClick={() => setAddExamples(v => !v)} />
          <span>Add worked examples</span>
        </label>
        <label className="row" style={{ gap: 8, fontSize: 13, cursor: 'pointer' }}>
          <Toggle on={addSummary} onClick={() => setAddSummary(v => !v)} />
          <span>Add summary section</span>
        </label>
      </div>
      <div className="row" style={{ gap: 8, justifyContent: 'flex-end' }}>
        <button className="btn btn-ghost btn-sm" onClick={onCancel}>Cancel</button>
        <button className="btn btn-brand btn-sm" onClick={() => onSubmit({ tone, readingLevel, addExamples, addSummary })} disabled={busy}>
          {busy ? <span className="spinner" style={{ width: 13, height: 13 }} /> : <Wand size={14} />}
          Improve
        </button>
      </div>
    </div>
  );
}

/* ---------- Image search drawer ---------- */
function ImageSearchDrawer({ initialQuery, onClose, onInsert }) {
  const [query, setQuery] = useState(initialQuery || '');
  const [images, setImages] = useState(null);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const inputRef = useRef(null);

  const runSearch = React.useCallback(async (q) => {
    const term = (q == null ? query : q).trim();
    if (!term) return;
    setLoading(true);
    setError(null);
    try {
      const data = await window.API.searchImages(term, 12);
      setImages(data.images || []);
    } catch (e) {
      setError(e?.message || 'Search failed');
      setImages([]);
    } finally {
      setLoading(false);
    }
  }, [query]);

  useEffect(() => {
    if (initialQuery && initialQuery.trim()) runSearch(initialQuery);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  useEffect(() => {
    if (inputRef.current) inputRef.current.focus();
  }, []);

  return (
    <div
      className="scroll fade-in"
      style={{
        position: 'fixed',
        right: 0,
        top: 0,
        width: 420,
        height: '100vh',
        background: 'var(--surface)',
        borderLeft: '1px solid var(--line)',
        zIndex: 30,
        padding: 24,
        overflowY: 'auto',
        boxShadow: 'var(--sh-md)',
      }}
    >
      <div className="row" style={{ justifyContent: 'space-between', marginBottom: 14, gap: 8 }}>
        <div className="row" style={{ gap: 9 }}>
          <span className="center" style={{ width: 28, height: 28, borderRadius: 8, background: 'var(--brand-tint)', color: 'var(--brand-ink)', flexShrink: 0 }}><Search size={14} /></span>
          <div>
            <div style={{ fontSize: 14.5, fontWeight: 700, letterSpacing: '-0.01em' }}>Insert image</div>
            <div className="muted mono" style={{ fontSize: 11 }}>Powered by Google Images</div>
          </div>
        </div>
        <button className="btn btn-icon btn-ghost btn-sm" title="Close" aria-label="Close" onClick={onClose}><X size={16} /></button>
      </div>

      <div className="row" style={{ gap: 8, marginBottom: 16, background: 'var(--surface)', border: '1px solid var(--line-strong)', borderRadius: 'var(--r-sm)', padding: 6 }}>
        <Search size={15} style={{ color: 'var(--muted)', marginLeft: 6 }} />
        <input
          ref={inputRef}
          className="grow"
          style={{ border: 'none', outline: 'none', background: 'transparent', fontSize: 14 }}
          value={query}
          onChange={e => setQuery(e.target.value)}
          onKeyDown={e => { if (e.key === 'Enter') runSearch(); }}
          placeholder="Search for an image…"
        />
        <button className="btn btn-brand btn-sm" onClick={() => runSearch()} disabled={loading || !query.trim()}>
          {loading ? <span className="spinner" style={{ width: 13, height: 13 }} /> : 'Search'}
        </button>
      </div>

      {error && (
        <div style={{ padding: '10px 12px', background: 'var(--rose-tint, #fde9ee)', color: '#9c1f3c', borderRadius: 'var(--r-sm)', fontSize: 13, marginBottom: 12 }}>
          {error}
        </div>
      )}

      {loading && images == null && (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
          {[0, 1, 2, 3, 4, 5].map(i => (
            <div key={i} className="skeleton" style={{ aspectRatio: '4/3', borderRadius: 10 }} />
          ))}
        </div>
      )}

      {!loading && images && images.length === 0 && (
        <div className="card" style={{ padding: '18px 16px' }}>
          <div style={{ fontSize: 13.5, fontWeight: 600, marginBottom: 4 }}>No images found</div>
          <div className="muted" style={{ fontSize: 12.5, lineHeight: 1.5 }}>Try different keywords — e.g. broader terms or a related topic.</div>
        </div>
      )}

      {images && images.length > 0 && (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
          {images.map((img, i) => (
            <button
              key={i}
              className="card card-hover"
              onClick={() => onInsert(img)}
              style={{ padding: 0, overflow: 'hidden', textAlign: 'left', cursor: 'pointer', display: 'flex', flexDirection: 'column' }}
              title={img.title || 'Insert image'}
            >
              <div style={{ aspectRatio: '4/3', background: 'var(--surface-2)', overflow: 'hidden' }}>
                <img
                  src={img.thumbUrl}
                  alt={img.title || ''}
                  loading="lazy"
                  referrerPolicy="no-referrer"
                  onError={e => { e.currentTarget.style.opacity = 0.3; }}
                  style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block' }}
                />
              </div>
              <div style={{ padding: '8px 10px 10px' }}>
                <div style={{ fontSize: 12, fontWeight: 600, lineHeight: 1.35, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
                  {img.title || 'Untitled'}
                </div>
                {img.source && (
                  <div className="muted mono" style={{ fontSize: 10.5, marginTop: 4, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{img.source}</div>
                )}
              </div>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function QuizPreview() {
  const [picked, setPicked] = useState(null);
  return (
    <div className="card fade-in" style={{ padding: '32px 36px' }}>
      <div className="row" style={{ justifyContent: 'space-between', marginBottom: 18 }}>
        <span className="mono" style={{ fontSize: 11, letterSpacing: '.1em', textTransform: 'uppercase', color: 'var(--amber)' }}>Module 1 · Quiz</span>
        <EditBadge />
      </div>
      <h2 style={{ fontSize: 19, lineHeight: 1.4, letterSpacing: '-0.01em' }} contentEditable suppressContentEditableWarning>{SAMPLE_QUIZ.q}</h2>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginTop: 20 }}>
        {SAMPLE_QUIZ.options.map((o, i) => {
          const isCorrect = i === SAMPLE_QUIZ.correct;
          const show = picked != null;
          const state = show && isCorrect ? 'correct' : show && picked === i && !isCorrect ? 'wrong' : 'idle';
          return (
            <button key={i} onClick={() => setPicked(i)} className="card" style={{ textAlign: 'left', padding: '13px 16px', display: 'flex', alignItems: 'center', gap: 12, cursor: 'pointer',
              border: state === 'correct' ? '1.5px solid var(--green)' : state === 'wrong' ? '1.5px solid var(--rose)' : '1px solid var(--line)',
              background: state === 'correct' ? 'var(--green-tint)' : state === 'wrong' ? 'var(--rose-tint)' : 'var(--surface)' }}>
              <span className="center" style={{ width: 24, height: 24, borderRadius: 50, fontSize: 12, fontWeight: 700, flexShrink: 0,
                background: state === 'correct' ? 'var(--green)' : state === 'wrong' ? 'var(--rose)' : 'var(--surface-2)', color: state === 'idle' ? 'var(--muted)' : '#fff' }}>
                {state === 'correct' ? <Check size={14} /> : state === 'wrong' ? <X size={14} /> : String.fromCharCode(65 + i)}
              </span>
              <span style={{ fontSize: 14.5 }}>{o}</span>
            </button>
          );
        })}
      </div>
      {picked != null && (
        <div className="fade-in" style={{ marginTop: 18, padding: '14px 16px', background: 'var(--surface-2)', borderRadius: 12, fontSize: 14, lineHeight: 1.6 }}>
          <b style={{ color: 'var(--green)' }}>Explanation. </b>{SAMPLE_QUIZ.why}
        </div>
      )}
    </div>
  );
}

function NotesPreview() {
  return (
    <div className="card fade-in" style={{ padding: '32px 36px' }}>
      <div className="row" style={{ justifyContent: 'space-between', marginBottom: 18 }}>
        <span className="mono" style={{ fontSize: 11, letterSpacing: '.1em', textTransform: 'uppercase', color: 'var(--brand)' }}>Module 1 · Lesson 2 · Slide notes</span>
        <EditBadge />
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 20 }}>
        <div style={{ aspectRatio: '16/10', borderRadius: 12, background: 'var(--ink)', position: 'relative', overflow: 'hidden', padding: 22, color: '#fff' }} className="noise">
          <div style={{ position: 'absolute', inset: 0, background: 'radial-gradient(70% 80% at 80% 10%, rgba(91,84,230,.5), transparent 60%)' }} />
          <div style={{ position: 'relative' }}>
            <div className="mono" style={{ fontSize: 10, letterSpacing: '.12em', opacity: .6 }}>SLIDE 4</div>
            <h3 style={{ color: '#fff', fontSize: 19, marginTop: 14, letterSpacing: '-0.01em' }}>Three terms to know</h3>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 14 }}>
              {['Primitive', 'Pattern', 'Constraint'].map(t => <div key={t} className="row" style={{ gap: 8, fontSize: 13 }}><span style={{ width: 5, height: 5, borderRadius: 50, background: '#7CA0F0' }} />{t}</div>)}
            </div>
          </div>
        </div>
        <div>
          <div className="mono" style={{ fontSize: 11, color: 'var(--faint)', letterSpacing: '.08em', textTransform: 'uppercase', marginBottom: 12 }}>Talking points</div>
          <div contentEditable suppressContentEditableWarning style={{ outline: 'none', display: 'flex', flexDirection: 'column', gap: 12 }}>
            {SAMPLE_NOTES.map((n, i) => (
              <div key={i} className="row" style={{ gap: 10, fontSize: 14, lineHeight: 1.55, alignItems: 'flex-start' }}>
                <span className="center" style={{ width: 19, height: 19, borderRadius: 50, background: 'var(--brand-tint)', color: 'var(--brand)', fontSize: 11, fontWeight: 700, flexShrink: 0, marginTop: 1 }}>{i + 1}</span>{n}
              </div>
            ))}
          </div>
        </div>
      </div>
    </div>
  );
}

/* ---------- Pre-generation panel: shows recent courses + intro ---------- */
function PreGenPanel({ stage, go }) {
  const [recents, setRecents] = useState(null);
  useEffect(() => {
    let cancelled = false;
    window.API.listCourses()
      .then(d => { if (!cancelled) setRecents(d.courses || []); })
      .catch(() => { if (!cancelled) setRecents([]); });
    return () => { cancelled = true; };
  }, []);

  const hero = (
    <div style={{ textAlign: 'center', maxWidth: 520, margin: '0 auto', padding: '40px 24px 24px' }}>
      <div className="center" style={{ width: 72, height: 72, borderRadius: 20, background: 'var(--ai-grad)', margin: '0 auto', boxShadow: 'var(--sh-brand)', animation: 'floaty 4s ease-in-out infinite' }}>
        <Sparkles size={32} style={{ color: '#fff' }} />
      </div>
      <h2 style={{ fontSize: 26, marginTop: 24, letterSpacing: '-0.02em', lineHeight: 1.15 }}>
        {stage === 'topic' ? 'What do you want to teach?' : 'Your course will appear here'}
      </h2>
      <p className="muted" style={{ fontSize: 15, marginTop: 12, lineHeight: 1.55 }}>
        {stage === 'topic'
          ? <>
              Type a topic in the chat on the left
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, marginLeft: 8, padding: '2px 8px', borderRadius: 99, background: 'var(--brand-tint)', color: 'var(--brand-ink)', fontWeight: 600, fontSize: 13 }}>
                <ArrowR size={13} style={{ transform: 'rotate(180deg)' }} /> Start here
              </span>
            </>
          : 'Just a few more details and I’ll start writing…'}
      </p>
    </div>
  );

  return (
    <div data-app-scroll className="scroll" style={{ background: 'var(--paper)', overflowY: 'auto', minWidth: 0, position: 'relative' }}>
      <div className="grid-bg" style={{ position: 'absolute', inset: 0, opacity: .35, pointerEvents: 'none', maskImage: 'radial-gradient(60% 50% at 50% 30%, #000, transparent 80%)', WebkitMaskImage: 'radial-gradient(60% 50% at 50% 30%, #000, transparent 80%)' }} />
      <div style={{ position: 'relative', maxWidth: 920, margin: '0 auto', padding: '24px 28px 64px' }}>
        {hero}

        <div style={{ marginTop: 28 }}>
          <div className="row" style={{ justifyContent: 'space-between', marginBottom: 14, padding: '0 4px' }}>
            <div className="row" style={{ gap: 10 }}>
              <h3 style={{ fontSize: 16, letterSpacing: '-0.01em' }}>Your courses</h3>
              {recents && <span className="pill" style={{ height: 22, fontSize: 12 }}>{recents.length}</span>}
            </div>
            {recents && recents.length > 0 && (
              <a className="row" style={{ gap: 4, fontSize: 13, fontWeight: 600, color: 'var(--brand)', cursor: 'pointer' }} onClick={() => go('library')}>
                Open library <ChevR size={14} />
              </a>
            )}
          </div>

          {recents == null ? (
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: 12 }}>
              {[0,1,2].map(i => (
                <div key={i} className="card" style={{ padding: 14, display: 'flex', flexDirection: 'column', gap: 9 }}>
                  <div className="skeleton" style={{ height: 14, width: '70%' }} />
                  <div className="skeleton" style={{ height: 11, width: '90%' }} />
                  <div className="skeleton" style={{ height: 11, width: '55%' }} />
                </div>
              ))}
            </div>
          ) : recents.length === 0 ? (
            <div className="card" style={{ padding: '22px 20px', display: 'flex', alignItems: 'center', gap: 14 }}>
              <span className="center" style={{ width: 38, height: 38, borderRadius: 11, background: 'var(--brand-tint)', color: 'var(--brand)', flexShrink: 0 }}><Spark size={18} /></span>
              <div className="grow" style={{ minWidth: 0 }}>
                <div style={{ fontSize: 14, fontWeight: 600 }}>No courses yet</div>
                <div className="muted" style={{ fontSize: 12.5, marginTop: 2 }}>Tell Coursedy what to teach on the left and your first course lands here.</div>
              </div>
            </div>
          ) : (
            <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: 12 }}>
              {recents.slice(0, 6).map(c => {
                const cat = (window.guessCat ? window.guessCat(c.title) : 'data');
                const grad = (window.catOf ? window.catOf(cat).grad : 'var(--ai-grad)');
                const level = ({ beginner: 'Beginner', intermediate: 'Intermediate', advanced: 'Advanced' })[c.difficulty] || 'Beginner';
                return (
                  <div key={c.id} className="card card-hover" style={{ padding: 14, cursor: 'pointer', display: 'flex', flexDirection: 'column', gap: 10 }} onClick={() => go('creator', { editId: c.id })}>
                    <div className="row" style={{ gap: 10 }}>
                      <span style={{ width: 36, height: 36, borderRadius: 9, background: grad, flexShrink: 0 }} />
                      <div className="grow" style={{ minWidth: 0 }}>
                        <div style={{ fontSize: 13.8, fontWeight: 600, letterSpacing: '-0.01em', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{c.title}</div>
                        <div className="muted" style={{ fontSize: 11.5, marginTop: 2 }}>{level} · {c.moduleTotal || c.moduleCount || 0} modules</div>
                      </div>
                    </div>
                    {c.description && (
                      <div className="muted" style={{ fontSize: 12.3, lineHeight: 1.45, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>{c.description}</div>
                    )}
                    <div className="row" style={{ gap: 6, marginTop: 'auto', fontSize: 11, color: 'var(--faint)' }}>
                      <Clock size={11} /> {new Date(c.createdAt).toLocaleDateString()}
                    </div>
                  </div>
                );
              })}
            </div>
          )}
        </div>

        <div className="row" style={{ justifyContent: 'center', gap: 18, marginTop: 28, fontSize: 12.5, color: 'var(--muted)' }}>
          {['Modules', 'Lessons', 'Quizzes', 'Notes'].map(x => <span key={x} className="row" style={{ gap: 6 }}><span style={{ width: 6, height: 6, borderRadius: 50, background: 'var(--brand)' }} />{x}</span>)}
        </div>
      </div>
    </div>
  );
}

Object.assign(window, { Creator, PreGenPanel, guessCat });
