/* ============================================================
   CURIO — Dedicated full-page lesson editor (route: 'lesson')
   ============================================================ */

const isRealLessonId = (id) => typeof id === 'string' && /^c[a-z0-9]{20,}$/i.test(id);

/* ---------- Toast UI Editor style overrides (injected once) ---------- */
(function () {
  if (typeof document === 'undefined') return;
  if (document.getElementById('tui-editor-overrides')) return;
  const s = document.createElement('style');
  s.id = 'tui-editor-overrides';
  s.textContent = `.toastui-editor-defaultUI{border:none!important;border-radius:0!important}.toastui-editor-toolbar{background:var(--surface-2)!important}`;
  document.head.appendChild(s);
})();

/* ---------- WysiwygEditor — Toast UI markdown-native editor ---------- */
function WysiwygEditor({ value, onChange, height = 540 }) {
  const containerRef = useRef(null);
  const editorRef = useRef(null);
  const lastEmittedRef = useRef(value || '');

  useEffect(() => {
    let cancelled = false;
    let pollId = null;
    let attempts = 0;

    const mount = () => {
      if (cancelled || editorRef.current || !containerRef.current) return;
      const ed = new window.toastui.Editor({
        el: containerRef.current,
        height: typeof height === 'number' ? height + 'px' : height,
        initialEditType: 'wysiwyg',
        previewStyle: 'vertical',
        usageStatistics: false,
        theme: 'light',
        initialValue: value || '',
        toolbarItems: [
          ['heading', 'bold', 'italic', 'strike'],
          ['hr', 'quote'],
          ['ul', 'ol', 'task', 'indent', 'outdent'],
          ['table', 'image', 'link'],
          ['code', 'codeblock'],
        ],
        autofocus: false,
        events: {
          change: () => {
            if (!editorRef.current) return;
            const md = editorRef.current.getMarkdown();
            if (md === lastEmittedRef.current) return;
            lastEmittedRef.current = md;
            onChange(md);
          },
        },
      });
      editorRef.current = ed;
      // Expose the live editor instance so external code (image picker, etc.) can insert at cursor.
      window.__lessonEditor = ed;

      // Ctrl/Cmd+Y → redo (Windows convention; Toast handles Ctrl+Z and Ctrl+Shift+Z natively).
      const onKey = (e) => {
        const mod = e.ctrlKey || e.metaKey;
        if (!mod) return;
        const k = e.key.toLowerCase();
        if (k === 'y') {
          e.preventDefault();
          try { ed.exec('redo'); } catch (err) {}
        }
      };
      containerRef.current.addEventListener('keydown', onKey);
      editorRef.current.__onKey = onKey;
    };

    if (window.toastui && window.toastui.Editor) {
      mount();
    } else {
      pollId = setInterval(() => {
        attempts += 1;
        if (window.toastui && window.toastui.Editor) {
          clearInterval(pollId);
          pollId = null;
          mount();
        } else if (attempts > 80) {
          // ~4s elapsed — give up; surface a hint
          clearInterval(pollId);
          pollId = null;
          if (containerRef.current && !editorRef.current) {
            containerRef.current.innerHTML = '<div style="padding:16px;color:var(--muted);font-size:13px">Editor failed to load. Check your connection (Toast UI CDN may be blocked) and refresh.</div>';
          }
        }
      }, 50);
    }

    return () => {
      cancelled = true;
      if (pollId) clearInterval(pollId);
      const ed = editorRef.current;
      if (ed) {
        try { containerRef.current && containerRef.current.removeEventListener('keydown', ed.__onKey); } catch (e) {}
        if (window.__lessonEditor === ed) window.__lessonEditor = null;
        try { ed.destroy(); } catch (e) {}
      }
      editorRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // When `value` changes externally (e.g. AI regenerate replaced the body),
  // push it in WITHOUT triggering an emit loop.
  useEffect(() => {
    const ed = editorRef.current;
    if (!ed) return;
    if (value === lastEmittedRef.current) return;
    lastEmittedRef.current = value || '';
    try { ed.setMarkdown(value || '', false); } catch (e) {}
  }, [value]);

  return (
    <div
      ref={containerRef}
      style={{ border: '1px solid var(--line)', borderRadius: 12, overflow: 'hidden', background: 'var(--surface)' }}
    />
  );
}

/* ---------- Small utilities ---------- */
function lp_wordCount(s) {
  return (s || '').trim().split(/\s+/).filter(Boolean).length;
}
function lp_readMinutes(s) {
  return Math.max(1, Math.round(lp_wordCount(s) / 180));
}
function lp_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 lp_findLesson(course, lessonId) {
  if (!lessonId) return null;
  const flat = lp_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 };
}

/* ============================================================
   LessonPage — the full-page route
   ============================================================ */
function LessonPage({ go, params, user }) {
  const { id: lessonId, courseId } = params || {};
  const [course, setCourse] = useState(null);
  const [loading, setLoading] = useState(true);
  const [savedAt, setSavedAt] = useState(0);
  const [error, setError] = useState(null);
  const [reloadTick, setReloadTick] = useState(0);

  const goBack = () => {
    if (courseId) go('creator', { editId: courseId });
    else go('library');
  };

  useEffect(() => {
    let cancelled = false;
    if (!courseId) { setLoading(false); return; }
    setLoading(true);
    window.API.getCourse(courseId)
      .then(c => { if (!cancelled) { setCourse(c); setLoading(false); } })
      .catch(e => { if (!cancelled) { setError(e?.message || 'Failed to load course'); setLoading(false); } });
    return () => { cancelled = true; };
  }, [courseId, reloadTick]);

  // 2.5s "Saved ✓" indicator
  useEffect(() => {
    if (!savedAt) return;
    const id = setTimeout(() => setSavedAt(0), 2500);
    return () => clearTimeout(id);
  }, [savedAt]);

  const markSaved = () => setSavedAt(Date.now());
  const refresh = React.useCallback(async () => {
    if (!courseId) return null;
    try {
      const c = await window.API.getCourse(courseId);
      setCourse(c);
      return c;
    } catch (e) {
      setError(e?.message || 'Refresh failed');
      return null;
    }
  }, [courseId]);

  const navigateToLesson = (newId) => {
    if (newId) go('lesson', { id: newId, courseId });
    else goBack();
  };

  // Loading skeleton
  if (loading) {
    return (
      <div data-app-scroll className="scroll" style={{ height: '100vh', overflowY: 'auto', background: 'var(--paper)' }} data-screen-label="Lesson editor">
        <Topbar go={go} title="Loading lesson…" back={goBack} user={user} />
        <div style={{ maxWidth: 1280, margin: '0 auto', padding: '32px' }}>
          <div className="card" style={{ padding: 28 }}>
            <div className="skeleton" style={{ height: 12, width: '30%', marginBottom: 14 }} />
            <div className="skeleton" style={{ height: 32, width: '70%', marginBottom: 18 }} />
            <div className="skeleton" style={{ height: 12, width: '40%', marginBottom: 24 }} />
            <div className="skeleton" style={{ height: 320, marginBottom: 14 }} />
            <div className="skeleton" style={{ height: 120 }} />
          </div>
        </div>
      </div>
    );
  }

  if (!course) {
    return (
      <div data-app-scroll className="scroll" style={{ height: '100vh', overflowY: 'auto', background: 'var(--paper)' }}>
        <Topbar go={go} title="Lesson not found" back={goBack} user={user} />
        <div style={{ maxWidth: 720, margin: '64px auto', padding: 32 }}>
          <div className="card" style={{ padding: 36, 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: 18, letterSpacing: '-0.01em', marginBottom: 8 }}>We couldn't load this course</h3>
            <p className="muted" style={{ fontSize: 14, lineHeight: 1.55, maxWidth: 420, margin: '0 auto 18px' }}>
              {error || 'The course or lesson may have moved or been deleted.'}
            </p>
            <Btn variant="brand" size="sm" onClick={goBack}>Back to course</Btn>
          </div>
        </div>
      </div>
    );
  }

  const found = lp_findLesson(course, lessonId);

  if (!found) {
    return (
      <div data-app-scroll className="scroll" style={{ height: '100vh', overflowY: 'auto', background: 'var(--paper)' }}>
        <Topbar go={go} title={course.title} sub="Lesson not found" back={goBack} user={user} />
        <div style={{ maxWidth: 720, margin: '64px auto', padding: 32 }}>
          <div className="card" style={{ padding: 36, 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: 18, letterSpacing: '-0.01em', marginBottom: 8 }}>Lesson not found</h3>
            <p className="muted" style={{ fontSize: 14, lineHeight: 1.55, maxWidth: 420, margin: '0 auto 18px' }}>
              This lesson may have been deleted. Open another lesson from the course outline.
            </p>
            <Btn variant="brand" size="sm" onClick={goBack}>Back to course</Btn>
          </div>
        </div>
      </div>
    );
  }

  const showSaved = savedAt > 0 && (Date.now() - savedAt < 2500);
  const sub = `Module ${found.moduleIndex + 1} · Lesson ${found.lessonIndex + 1} / ${found.total}`;

  return (
    <div style={{ height: '100vh', display: 'flex', flexDirection: 'column', background: 'var(--paper)', minHeight: 0 }} data-screen-label="Lesson editor">
      <Topbar go={go} title={course.title} sub={sub} back={goBack} user={user}>
        {showSaved && <span className="mono fade-in" style={{ fontSize: 12, color: 'var(--green)' }}>Saved ✓</span>}
        <Btn
          variant="outline"
          size="sm"
          icon={<Eye size={14} />}
          onClick={() => courseId && found?.lesson?.id && window.open('/preview/' + courseId + '?lesson=' + encodeURIComponent(found.lesson.id), '_blank')}
        >Preview</Btn>
      </Topbar>

      <LessonPageInner
        key={found.lesson.id}
        go={go}
        course={course}
        courseId={courseId}
        found={found}
        markSaved={markSaved}
        refresh={refresh}
        navigateToLesson={navigateToLesson}
      />
    </div>
  );
}

/* ============================================================
   LessonPageInner — body + sticky action rail
   ============================================================ */
function LessonPageInner({ go, course, courseId, found, markSaved, refresh, navigateToLesson }) {
  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 [imageDrawerOpen, setImageDrawerOpen] = useState(false);
  const bodyRef = useRef(null);
  const saveTimerRef = useRef(null);

  // Debounced body autosave (the WYSIWYG editor emits on every keystroke).
  const queueBodySave = (md) => {
    if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
    saveTimerRef.current = setTimeout(async () => {
      saveTimerRef.current = null;
      try {
        await window.API.patchLesson(lesson.id, { body: md });
        lesson.body = md;
        markSaved && markSaved();
      } catch (e) {
        setError(e?.message || 'Save failed');
      }
    }, 1200);
  };
  useEffect(() => () => { if (saveTimerRef.current) clearTimeout(saveTimerRef.current); }, []);

  // Rehydrate when upstream lesson changes (e.g. after regenerate / refresh)
  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 (guidance) => {
    setBusy('regen');
    setError(null);
    try {
      const { lesson: updated } = await window.API.regenerateLesson(lesson.id, {
        guidance: (guidance || '').trim() || undefined,
      });
      setTitleDraft(updated.title);
      setBody(updated.body || '');
      setSlideNotes(updated.slideNotes || '');
      lesson.title = updated.title;
      lesson.body = updated.body || '';
      lesson.slideNotes = updated.slideNotes || '';
      markSaved && markSaved();
      await (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;
      markSaved && markSaved();
      await (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).trim() || 'image';
    const imgMd = `![${alt}](${img.url})`;
    const ed = window.__lessonEditor;
    // Always append to the markdown — the most reliable path across Toast modes.
    // Read the current markdown from the editor when possible (it stays in sync with the cursor),
    // otherwise fall back to local body state.
    let currentMd;
    try { currentMd = ed && typeof ed.getMarkdown === 'function' ? ed.getMarkdown() : body; }
    catch (e) { currentMd = body; }
    const sep = !currentMd || currentMd.endsWith('\n\n') ? '' : currentMd.endsWith('\n') ? '\n' : '\n\n';
    const nextBody = currentMd + sep + imgMd + '\n';
    try {
      if (ed && typeof ed.setMarkdown === 'function') ed.setMarkdown(nextBody, false);
    } catch (e) { /* swallow — the React state below still wins */ }
    setBody(nextBody);
    setImageDrawerOpen(false);
    queueBodySave(nextBody);
  };

  const onAddBelow = async () => {
    if (!moduleId) return;
    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 && created.id) {
        go('lesson', { id: created.id, courseId });
      }
      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 {
      const neighborId = (next && next.lesson.id) || (prev && prev.lesson.id) || null;
      await window.API.deleteLesson(lesson.id);
      markSaved && markSaved();
      navigateToLesson(neighborId);
    } catch (e) {
      setError(e?.message || 'Delete failed');
      setBusy(null);
    }
  };

  const words = lp_wordCount(body);
  const minutes = lp_readMinutes(body);
  const isShortBody = (body || '').trim().length < 200;

  // Apply a lesson update returned from the chat backend
  const applyLessonUpdate = React.useCallback((updated) => {
    if (!updated) return;
    if (updated.title !== undefined) {
      setTitleDraft(updated.title);
      lesson.title = updated.title;
    }
    if (updated.body !== undefined) {
      setBody(updated.body || '');
      lesson.body = updated.body || '';
    }
    if (updated.slideNotes !== undefined) {
      setSlideNotes(updated.slideNotes || '');
      lesson.slideNotes = updated.slideNotes || '';
    }
    markSaved && markSaved();
  }, [lesson, markSaved]);

  return (
    <div style={{ flex: 1, minHeight: 0, display: 'grid', gridTemplateColumns: '380px minmax(0,1fr)', overflow: 'hidden' }}>
      {/* LEFT — chat panel */}
      <LessonChatPanel
        lessonId={lesson.id}
        lessonTitleHint={lesson.title}
        bodyIsShort={isShortBody}
        onLessonUpdated={applyLessonUpdate}
      />

      {/* RIGHT — editor + action rail (scrolls independently) */}
      <div data-app-scroll className="scroll" style={{ minHeight: 0, overflowY: 'auto' }}>
        <div style={{ maxWidth: 1320, margin: '0 auto', padding: '24px 32px 96px', display: 'grid', gridTemplateColumns: 'minmax(0,1fr) 320px', gap: 28 }}>
          {/* Editor column */}
          <div style={{ minWidth: 0 }}>
            {/* Breadcrumb / location */}
            <div className="row" style={{ gap: 10, flexWrap: 'wrap', minWidth: 0, marginBottom: 10 }}>
              <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: 360 }}>{moduleTitle}</span>
            </div>

            {/* Title (large contentEditable) */}
            <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: 34, fontWeight: 800, letterSpacing: '-0.025em', lineHeight: 1.1, marginBottom: 10, outline: 'none', wordBreak: 'normal', overflowWrap: 'break-word' }}
            >{titleDraft}</div>

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

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

            {/* Body */}
            <div className="card" style={{ padding: 18, marginBottom: 18 }}>
              <div className="row" style={{ justifyContent: 'space-between', marginBottom: 8 }}>
                <span className="mono" style={{ fontSize: 10.5, letterSpacing: '.12em', textTransform: 'uppercase', color: 'var(--faint)' }}>Body</span>
                <span className="muted" style={{ fontSize: 11.5 }}>WYSIWYG · markdown supported</span>
              </div>
              <WysiwygEditor
                value={body}
                onChange={(md) => {
                  setBody(md);
                  queueBodySave(md);
                }}
                height={540}
              />
            </div>

            {/* Slide notes */}
            <div className="card" style={{ padding: 18 }}>
              <div className="row" style={{ justifyContent: 'space-between', marginBottom: 8 }}>
                <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={8}
                style={{ width: '100%', resize: 'vertical', fontSize: 14, lineHeight: 1.55, padding: 14 }}
                placeholder="One bullet per line. Conversational — what you'd actually say while presenting."
              />
            </div>
          </div>

          {/* Action rail */}
          <div style={{ minWidth: 0 }}>
            <div style={{ position: 'sticky', top: 16, display: 'flex', flexDirection: 'column', gap: 14 }}>
              <GenerateContentPanel
                isShortBody={isShortBody}
                busy={busy === 'regen'}
                onRegenerate={onRegenerate}
              />
              <ImprovePanel
                busy={busy === 'improve'}
                defaultTone={course && course._raw && course._raw.tone ? course._raw.tone : 'professional'}
                onSubmit={onImprove}
              />
              <ImagesPanel
                lessonTitle={lesson.title}
                onOpen={() => setImageDrawerOpen(true)}
              />
              <StructurePanel
                prev={prev}
                next={next}
                busy={busy === 'addBelow'}
                onAddBelow={onAddBelow}
                onPrev={() => prev && navigateToLesson(prev.lesson.id)}
                onNext={() => next && navigateToLesson(next.lesson.id)}
              />
              <DangerPanel
                busy={busy === 'delete'}
                onDelete={onDelete}
              />
            </div>
          </div>
        </div>
      </div>

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

/* ============================================================
   LessonChatPanel — left-side conversational assistant
   ============================================================ */
function LessonChatPanel({ lessonId, lessonTitleHint, bodyIsShort, onLessonUpdated }) {
  const seedMessage = bodyIsShort
    ? { role: 'ai', text: 'Tell me what to build for this lesson — full content from scratch, a specific section, examples, or shorter/longer.' }
    : { role: 'ai', text: 'I can rewrite the body, tighten a section, change tone, add examples — just tell me what to change.' };
  const [messages, setMessages] = useState([seedMessage]);
  const [input, setInput] = useState('');
  const [sending, setSending] = useState(false);
  const scrollRef = useRef(null);

  // Reset chat when the lesson changes
  useEffect(() => {
    setMessages([seedMessage]);
    setInput('');
    setSending(false);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [lessonId]);

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

  const submit = async () => {
    const val = (input || '').trim();
    if (!val || sending) return;
    setInput('');
    setSending(true);
    setMessages(m => [...m, { role: 'user', text: val }, { role: 'ai', typing: true }]);
    // Build short history from prior visible messages
    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.chatLesson(lessonId, val, history);
      const text = res.message || '';
      const edit = res.edit || null;
      if (res.lesson) onLessonUpdated && onLessonUpdated(res.lesson);
      setMessages(m => m.map((x, i) => i === m.length - 1 && x.typing ? { role: 'ai', text, edit, error: res.error } : 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));
    } finally {
      setSending(false);
    }
  };

  return (
    <div style={{ display: 'flex', flexDirection: 'column', borderRight: '1px solid var(--line)', background: 'var(--surface)', minWidth: 0, minHeight: 0, height: '100%' }}>
      <div className="row" style={{ height: 56, padding: '0 16px', borderBottom: '1px solid var(--line)', gap: 11, flexShrink: 0 }}>
        <span className="logo-mark" style={{ width: 28, height: 28 }}><Spark size={15} /></span>
        <div className="grow" style={{ minWidth: 0 }}>
          <div style={{ fontSize: 13.5, fontWeight: 600 }}>Lesson Assistant</div>
          <div className="row" style={{ gap: 5, fontSize: 11, color: 'var(--muted)' }}><span className="live-dot" /> Online</div>
        </div>
      </div>

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

      <div style={{ padding: 12, borderTop: '1px solid var(--line)', flexShrink: 0 }}>
        <div className="row" style={{ gap: 8, background: 'var(--surface)', border: '1px solid var(--line-strong)', borderRadius: 'var(--r-md)', padding: 7, boxShadow: 'var(--sh-xs)' }}>
          <input
            className="grow"
            style={{ border: 'none', outline: 'none', background: 'transparent', fontSize: 14 }}
            placeholder={bodyIsShort ? 'Draft the lesson, add examples, set the tone…' : 'Ask the assistant…'}
            value={input}
            onChange={e => setInput(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter') submit(); }}
            disabled={sending}
          />
          <button
            className="btn btn-icon btn-brand btn-sm"
            onClick={submit}
            disabled={!input.trim() || sending}
            style={{ opacity: (input.trim() && !sending) ? 1 : .5 }}
          >
            {sending ? <span className="spinner" style={{ width: 13, height: 13 }} /> : <Send size={15} />}
          </button>
        </div>
      </div>
    </div>
  );
}

function LessonChatMsg({ m }) {
  if (m.role === 'user') return (
    <div className="row" style={{ justifyContent: 'flex-end', gap: 10 }}>
      <div style={{ maxWidth: '82%', background: 'var(--ink)', color: '#fff', padding: '10px 13px', borderRadius: '14px 14px 4px 14px', fontSize: 13.8, lineHeight: 1.5 }}>{m.text}</div>
    </div>
  );
  return (
    <div className="row" style={{ gap: 9, alignItems: 'flex-start' }}>
      <span className="logo-mark" style={{ width: 24, height: 24, flexShrink: 0 }}><Spark size={13} /></span>
      <div style={{ maxWidth: '88%', minWidth: 0 }}>
        <div style={{ background: 'var(--surface)', border: '1px solid var(--line)', padding: '10px 13px', borderRadius: '4px 14px 14px 14px', fontSize: 13.8, lineHeight: 1.55, boxShadow: 'var(--sh-xs)', wordBreak: 'break-word' }}>
          {m.typing ? <TypingDots /> : m.text}
        </div>
        {m.edit && !m.typing && (
          <div className="row" style={{ flexWrap: 'wrap', gap: 6, marginTop: 6 }}>
            {m.edit.title !== undefined && <span className="chip" style={{ background: 'var(--brand-tint)', borderColor: 'var(--brand-tint-2)', color: 'var(--brand-ink)', cursor: 'default' }}><Check size={11} /> Updated title</span>}
            {m.edit.body !== undefined && <span className="chip" style={{ background: 'var(--brand-tint)', borderColor: 'var(--brand-tint-2)', color: 'var(--brand-ink)', cursor: 'default' }}><Check size={11} /> {m.edit.mode === 'append' ? 'Appended to body' : 'Updated body'}</span>}
            {m.edit.slideNotes !== undefined && <span className="chip" style={{ background: 'var(--brand-tint)', borderColor: 'var(--brand-tint-2)', color: 'var(--brand-ink)', cursor: 'default' }}><Check size={11} /> Updated slide notes</span>}
          </div>
        )}
        {m.error && !m.typing && (
          <div className="muted" style={{ marginTop: 6, fontSize: 11.5, color: 'var(--rose, #c84a4a)', lineHeight: 1.45 }}>{m.error}</div>
        )}
      </div>
    </div>
  );
}

/* ============================================================
   Rail panels
   ============================================================ */

/* ---- Generate content (hero CTA when body is short) ---- */
function GenerateContentPanel({ isShortBody, busy, onRegenerate }) {
  const [guidance, setGuidance] = useState('');
  const [open, setOpen] = useState(isShortBody);

  // When the body becomes short again (e.g. after clearing), expand by default.
  useEffect(() => { if (isShortBody) setOpen(true); }, [isShortBody]);

  const heroMode = isShortBody;
  const heading = heroMode ? 'Generate content' : 'Regenerate';
  const label = heroMode ? 'Generate lesson content' : 'Regenerate lesson';

  return (
    <div className="card" style={{ padding: 16, background: heroMode ? 'var(--brand-tint)' : 'var(--surface)', border: heroMode ? '1px solid var(--brand-tint-2, var(--line))' : '1px solid var(--line)' }}>
      <div className="row" style={{ justifyContent: 'space-between', marginBottom: 10 }}>
        <div className="row" style={{ gap: 8 }}>
          <span className="center" style={{ width: 26, height: 26, borderRadius: 8, background: heroMode ? 'var(--brand)' : 'var(--brand-tint)', color: heroMode ? '#fff' : 'var(--brand-ink)', flexShrink: 0 }}>
            <Sparkles size={14} />
          </span>
          <div style={{ fontSize: 13.5, fontWeight: 700, letterSpacing: '-0.01em' }}>{heading}</div>
        </div>
        {!heroMode && (
          <button className="btn btn-icon btn-ghost btn-sm" onClick={() => setOpen(o => !o)} title={open ? 'Collapse' : 'Expand'}>
            <ChevD size={15} style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .2s' }} />
          </button>
        )}
      </div>

      {open && (
        <>
          <div className="mono" style={{ fontSize: 10.5, letterSpacing: '.12em', textTransform: 'uppercase', color: 'var(--faint)', marginBottom: 6 }}>
            {heroMode ? 'What should this lesson cover? (optional)' : 'Optional guidance'}
          </div>
          <textarea
            className="field"
            value={guidance}
            onChange={e => setGuidance(e.target.value)}
            rows={heroMode ? 4 : 3}
            placeholder={heroMode
              ? 'e.g. Cover the three core concepts with a worked example for beginners.'
              : 'e.g. Add a real-world example for designers, and make it slightly shorter.'}
            style={{ width: '100%', resize: 'vertical', fontSize: 13.5, lineHeight: 1.5, padding: 10, background: 'var(--surface)' }}
          />
          <button
            className={heroMode ? 'btn btn-brand btn-block' : 'btn btn-soft btn-block'}
            style={{ marginTop: 10, height: heroMode ? 44 : 38 }}
            onClick={() => onRegenerate(guidance)}
            disabled={busy}
          >
            {busy
              ? <span className="spinner" style={{ width: 14, height: 14 }} />
              : <Sparkles size={15} />}
            {label}
          </button>
          {heroMode && (
            <div className="muted" style={{ fontSize: 11.5, marginTop: 8, lineHeight: 1.4 }}>
              Coursedy drafts a full lesson with sub-headings, examples, and slide notes.
            </div>
          )}
        </>
      )}
    </div>
  );
}

/* ---- Improve panel (collapsed by default) ---- */
function ImprovePanel({ busy, defaultTone, onSubmit }) {
  const [open, setOpen] = useState(false);
  const [tone, setTone] = useState(defaultTone || 'professional');
  const [readingLevel, setReadingLevel] = useState('intermediate');
  const [addExamples, setAddExamples] = useState(true);
  const [addSummary, setAddSummary] = useState(false);

  return (
    <div className="card" style={{ padding: 16 }}>
      <button
        className="row"
        style={{ width: '100%', justifyContent: 'space-between', textAlign: 'left' }}
        onClick={() => setOpen(o => !o)}
      >
        <div className="row" style={{ gap: 8 }}>
          <span className="center" style={{ width: 26, height: 26, borderRadius: 8, background: 'var(--brand-tint)', color: 'var(--brand-ink)', flexShrink: 0 }}>
            <Wand size={14} />
          </span>
          <div style={{ fontSize: 13.5, fontWeight: 700, letterSpacing: '-0.01em' }}>Improve with AI</div>
        </div>
        <ChevD size={15} style={{ color: 'var(--faint)', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .2s' }} />
      </button>

      {open && (
        <div className="fade-in" style={{ marginTop: 12 }}>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 5, fontSize: 12.5, marginBottom: 10 }}>
            <span className="muted">Tone</span>
            <select className="field" value={tone} onChange={e => setTone(e.target.value)} style={{ height: 36, padding: '0 10px', fontSize: 13.5 }}>
              <option value="conversational">Conversational</option>
              <option value="professional">Professional</option>
              <option value="academic">Academic</option>
              <option value="motivational">Motivational</option>
            </select>
          </label>
          <label style={{ display: 'flex', flexDirection: 'column', gap: 5, fontSize: 12.5, marginBottom: 12 }}>
            <span className="muted">Reading level</span>
            <select className="field" value={readingLevel} onChange={e => setReadingLevel(e.target.value)} style={{ height: 36, padding: '0 10px', fontSize: 13.5 }}>
              <option value="intro">Intro</option>
              <option value="intermediate">Intermediate</option>
              <option value="advanced">Advanced</option>
            </select>
          </label>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10, marginBottom: 12 }}>
            <label className="row" style={{ gap: 10, fontSize: 13, cursor: 'pointer' }}>
              <Toggle on={addExamples} onClick={() => setAddExamples(v => !v)} />
              <span>Add worked examples</span>
            </label>
            <label className="row" style={{ gap: 10, fontSize: 13, cursor: 'pointer' }}>
              <Toggle on={addSummary} onClick={() => setAddSummary(v => !v)} />
              <span>Add summary section</span>
            </label>
          </div>
          <button
            className="btn btn-brand btn-block"
            onClick={() => onSubmit({ tone, readingLevel, addExamples, addSummary })}
            disabled={busy}
          >
            {busy ? <span className="spinner" style={{ width: 13, height: 13 }} /> : <Wand size={14} />}
            Improve
          </button>
        </div>
      )}
    </div>
  );
}

/* ---- Images panel ---- */
function ImagesPanel({ lessonTitle, onOpen }) {
  return (
    <div className="card" style={{ padding: 16 }}>
      <div className="row" style={{ gap: 8, marginBottom: 10 }}>
        <span className="center" style={{ width: 26, height: 26, borderRadius: 8, background: 'var(--brand-tint)', color: 'var(--brand-ink)', flexShrink: 0 }}>
          <Search size={14} />
        </span>
        <div style={{ fontSize: 13.5, fontWeight: 700, letterSpacing: '-0.01em' }}>Images</div>
      </div>
      <button className="btn btn-soft btn-block" onClick={onOpen}>
        <Search size={14} /> Search stock photos
      </button>
      <div className="muted" style={{ fontSize: 11.5, marginTop: 8, lineHeight: 1.4 }}>
        Inserts <span className="mono">![alt](url)</span> at your cursor.
      </div>
    </div>
  );
}

/* ---- Structure panel ---- */
function StructurePanel({ prev, next, busy, onAddBelow, onPrev, onNext }) {
  return (
    <div className="card" style={{ padding: 16 }}>
      <div className="row" style={{ gap: 8, marginBottom: 10 }}>
        <span className="center" style={{ width: 26, height: 26, borderRadius: 8, background: 'var(--brand-tint)', color: 'var(--brand-ink)', flexShrink: 0 }}>
          <List size={14} />
        </span>
        <div style={{ fontSize: 13.5, fontWeight: 700, letterSpacing: '-0.01em' }}>Structure</div>
      </div>
      <button className="btn btn-soft btn-block" onClick={onAddBelow} disabled={busy} style={{ marginBottom: 8 }}>
        {busy ? <span className="spinner dark" style={{ width: 13, height: 13 }} /> : <Plus size={14} />}
        Add lesson below
      </button>
      <div className="row" style={{ gap: 8 }}>
        <button
          className="btn btn-ghost btn-sm"
          style={{ flex: 1, opacity: prev ? 1 : 0.4 }}
          onClick={onPrev}
          disabled={!prev}
          title={prev ? prev.lesson.title : 'No previous lesson'}
        >
          <ChevL size={14} /> Previous
        </button>
        <button
          className="btn btn-ghost btn-sm"
          style={{ flex: 1, opacity: next ? 1 : 0.4 }}
          onClick={onNext}
          disabled={!next}
          title={next ? next.lesson.title : 'No next lesson'}
        >
          Next <ChevR size={14} />
        </button>
      </div>
    </div>
  );
}

/* ---- Danger panel (collapsed delete) ---- */
function DangerPanel({ busy, onDelete }) {
  const [open, setOpen] = useState(false);
  return (
    <div className="card" style={{ padding: 16 }}>
      <button
        className="row"
        style={{ width: '100%', justifyContent: 'space-between', textAlign: 'left' }}
        onClick={() => setOpen(o => !o)}
      >
        <div className="row" style={{ gap: 8 }}>
          <span className="center" style={{ width: 26, height: 26, borderRadius: 8, background: 'var(--rose-tint, #fde9ee)', color: '#9c1f3c', flexShrink: 0 }}>
            <Trash size={14} />
          </span>
          <div style={{ fontSize: 13.5, fontWeight: 700, letterSpacing: '-0.01em' }}>Delete lesson</div>
        </div>
        <ChevD size={15} style={{ color: 'var(--faint)', transform: open ? 'rotate(180deg)' : 'none', transition: 'transform .2s' }} />
      </button>
      {open && (
        <div className="fade-in" style={{ marginTop: 12 }}>
          <div className="muted" style={{ fontSize: 12.5, lineHeight: 1.5, marginBottom: 10 }}>
            Permanently removes this lesson from the course. This cannot be undone.
          </div>
          <button
            className="btn btn-block"
            style={{ background: '#9c1f3c', color: '#fff', height: 38 }}
            onClick={onDelete}
            disabled={busy}
          >
            {busy ? <span className="spinner" style={{ width: 13, height: 13 }} /> : <Trash size={14} />}
            Delete this lesson
          </button>
        </div>
      )}
    </div>
  );
}

/* ============================================================
   ImagePickerDrawer — local copy of the search drawer pattern
   ============================================================ */
function ImagePickerDrawer({ 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>
  );
}

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