// views.jsx — All page views

const { useState, useEffect, useRef, useCallback, useMemo } = React;

// ── DASHBOARD ─────────────────────────────────────────────────────────────────
const Dashboard = ({ user, sections, notes, deadlines, flashcards = [], setRoute, reminderSubs = [], onAcknowledgeReminder }) => {
  const totalNotes = notes.length;
  const totalFlashcards = flashcards.length;
  const upcoming = deadlines.filter(d => new Date(d.date) >= new Date()).sort((a, b) => new Date(a.date) - new Date(b.date)).slice(0, 3);
  const recentNotes = [...notes].sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt)).slice(0, 5);

  // Aktive Banner: Termin in Lead-Days-Reichweite, noch nicht acknowledged.
  const todayMs = (() => { const t = new Date(); t.setHours(0, 0, 0, 0); return t.getTime(); })();
  const banners = reminderSubs.filter(s => {
    if (s.acknowledgedAt) return false;
    if (!s.deadline) return false;
    const dms = new Date(s.deadline.date + 'T00:00:00').getTime();
    if (dms < todayMs) return false;
    const days = Math.round((dms - todayMs) / 86400000);
    return days <= s.leadDays;
  }).sort((a, b) => new Date(a.deadline.date) - new Date(b.deadline.date));

  return (
    <div style={{ padding: 32, maxWidth: 960, width: '100%', margin: '0 auto', boxSizing: 'border-box' }}>
      {/* Reminder-Banner */}
      {banners.length > 0 && (
        <div style={{ marginBottom: 24 }}>
          <div style={{ fontSize: 12, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 10, display: 'flex', alignItems: 'center', gap: 6 }}>
            <Icon name="bell" size={12} /> Erinnerungen
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {banners.map(s => {
              const dms = new Date(s.deadline.date + 'T00:00:00').getTime();
              const days = Math.round((dms - todayMs) / 86400000);
              const inText = days === 0 ? 'heute' : days === 1 ? 'morgen' : `in ${days} Tagen`;
              const lf = sections.find(l => l.id === s.deadline.sectionId);
              return (
                <div key={s.id} style={{
                  display: 'flex', alignItems: 'center', gap: 12, padding: '12px 14px',
                  background: 'var(--accent-dim)', border: '1px solid var(--accent)',
                  borderLeft: '4px solid var(--accent)',
                  borderRadius: 8,
                }}>
                  <Icon name="bell" size={16} color="var(--accent)" />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                      <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>{s.deadline.title}</span>
                      <TypeBadge type={s.deadline.type} />
                      {lf && <TagBadge label={lf.short} color={lf.color} />}
                    </div>
                    <div style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', marginTop: 2 }}>
                      {formatDateDe(s.deadline.date)} · {inText}
                    </div>
                  </div>
                  <button onClick={() => setRoute({ page: 'calendar' })} title="Im Kalender öffnen"
                    style={{ background: 'transparent', border: '1px solid var(--border)', borderRadius: 6, padding: '4px 10px', fontSize: 11, color: 'var(--text-muted)', cursor: 'pointer', fontFamily: 'var(--font-mono)' }}>
                    Öffnen
                  </button>
                  <button onClick={() => onAcknowledgeReminder && onAcknowledgeReminder(s.id)} title="Verstanden"
                    style={{ background: 'var(--accent)', color: '#fff', border: 'none', borderRadius: 6, padding: '4px 10px', fontSize: 11, cursor: 'pointer', fontFamily: 'var(--font-mono)' }}>
                    ✓ Verstanden
                  </button>
                </div>
              );
            })}
          </div>
        </div>
      )}

      {/* Header */}
      <div style={{ marginBottom: 32 }}>
        <div style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--accent)', marginBottom: 6 }}>
          Guten Morgen, {user?.name?.split(' ')[0]} —
        </div>
        <h1 style={{ margin: 0, fontSize: 26, fontWeight: 700, color: 'var(--text)', letterSpacing: '-0.03em' }}>
          Dein Knowledge Base
        </h1>
        <p style={{ margin: '6px 0 0', color: 'var(--text-muted)', fontSize: 14 }}>
          IT-System-Elektroniker · Lehrjahr 2
        </p>
      </div>

      {/* Stats Row */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 12, marginBottom: 28 }}>
        {[
          { label: 'Notizen', value: totalNotes, icon: 'book', color: '#a78bfa' },
          { label: 'Lernkarten', value: totalFlashcards, icon: 'flash', color: '#34d399' },
          { label: 'Lernfelder', value: sections.length, icon: 'grid', color: '#60a5fa' },
          { label: 'Termine', value: upcoming.length, icon: 'calendar', color: '#fb923c' },
        ].map(s => (
          <div key={s.label} style={{
            background: 'var(--surface)', border: '1px solid var(--border)',
            borderRadius: 10, padding: '16px 18px',
            display: 'flex', flexDirection: 'column', gap: 8,
          }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
              <span style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>{s.label}</span>
              <Icon name={s.icon} size={14} color={s.color} />
            </div>
            <div style={{ fontSize: 28, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-mono)', lineHeight: 1 }}>{s.value}</div>
          </div>
        ))}
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: '1fr 340px', gap: 20 }}>
        {/* LF List */}
        <div>
          <div style={{ fontSize: 12, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 12 }}>
            Lernfelder
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
            {sections.map(lf => {
              const nN = notes.filter(n => n.sectionId === lf.id).length;
              const nC = flashcards.filter(c => c.sectionId === lf.id).length;
              return (
                <button key={lf.id} onClick={() => setRoute({ page: 'section', sectionId: lf.id })}
                  style={{
                    display: 'flex', alignItems: 'center', gap: 12, padding: '10px 14px',
                    background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8,
                    cursor: 'pointer', textAlign: 'left', transition: 'all 0.15s',
                  }}
                  onMouseEnter={e => { e.currentTarget.style.borderColor = lf.color; e.currentTarget.style.background = 'var(--surface-hover)'; }}
                  onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.background = 'var(--surface)'; }}
                >
                  <span style={{ width: 8, height: 8, borderRadius: '50%', background: lf.color, flexShrink: 0 }} />
                  <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: lf.color, width: 54, flexShrink: 0, fontWeight: 600 }}>{lf.short}</span>
                  <span style={{ flex: 1, fontSize: 12, color: 'var(--text-muted)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{lf.label}</span>
                  <span style={{ fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-dim)', flexShrink: 0, whiteSpace: 'nowrap' }}>
                    {nN} {nN === 1 ? 'Notiz' : 'Notizen'} · {nC} {nC === 1 ? 'Karte' : 'Karten'}
                  </span>
                </button>
              );
            })}
          </div>
        </div>

        {/* Right column */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 20 }}>
          {/* Recent Notes */}
          <div>
            <div style={{ fontSize: 12, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 12 }}>
              Zuletzt bearbeitet
            </div>
            <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
              {recentNotes.map(note => {
                const lf = sections.find(l => l.id === note.sectionId);
                return (
                  <button key={note.id} onClick={() => setRoute({ page: 'note', noteId: note.id })}
                    style={{
                      display: 'flex', alignItems: 'center', gap: 10, padding: '8px 12px',
                      background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8,
                      cursor: 'pointer', textAlign: 'left', transition: 'all 0.15s',
                    }}
                    onMouseEnter={e => { e.currentTarget.style.borderColor = lf?.color || 'var(--accent)'; }}
                    onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; }}
                  >
                    <span style={{ width: 6, height: 6, borderRadius: '50%', background: lf?.color || '#9b5cf6', flexShrink: 0 }} />
                    <span style={{ flex: 1, fontSize: 12, color: 'var(--text)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', fontWeight: 500 }}>{note.title}</span>
                    <span style={{ fontSize: 10, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>{formatDateDe(note.updatedAt)}</span>
                  </button>
                );
              })}
            </div>
          </div>

          {/* Upcoming Deadlines */}
          <div>
            <div style={{ fontSize: 12, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 12 }}>
              Nächste Termine
            </div>
            {upcoming.length === 0 ? (
              <div style={{ color: 'var(--text-dim)', fontSize: 13, fontFamily: 'var(--font-mono)' }}>// Keine Termine</div>
            ) : (
              <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                {upcoming.map(d => (
                  <div key={d.id} style={{
                    padding: '10px 12px', background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8,
                  }}>
                    <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 4 }}>
                      <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)' }}>{d.title}</span>
                      <TypeBadge type={d.type} />
                    </div>
                    <div style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{formatDateDe(d.date)}</div>
                  </div>
                ))}
              </div>
            )}
          </div>
        </div>
      </div>
    </div>
  );
};

// ── SECTION VIEW ──────────────────────────────────────────────────────────────
const SectionView = ({ sectionId, sections, notes, flashcards, setRoute, userCan = () => true, currentUser, onTogglePin, onBulkDelete, onBulkPatch }) => {
  const lf = sections.find(l => l.id === sectionId);
  const lfNotes = notes.filter(n => n.sectionId === sectionId)
    .slice().sort((a, b) => (b.pinned ? 1 : 0) - (a.pinned ? 1 : 0) || new Date(b.updatedAt) - new Date(a.updatedAt));
  const lfCards = flashcards.filter(c => c.sectionId === sectionId);
  const [viewMode, setViewMode] = useState('list');
  const [selectMode, setSelectMode] = useState(false);
  const [selectedIds, setSelectedIds] = useState(() => new Set());
  const [bulkAction, setBulkAction] = useState(null); // null | 'tag' | 'move' | 'delete'
  const [bulkInput, setBulkInput] = useState('');
  const [chatOpen, setChatOpen] = useState(false);
  // Wenn man die Section wechselt: Auswahl resetten
  useEffect(() => { setSelectedIds(new Set()); setSelectMode(false); setChatOpen(false); }, [sectionId]);
  if (!lf) return <div style={{ padding: 32, color: 'var(--text-muted)' }}>Lernfeld nicht gefunden.</div>;

  const canBulk = userCan('notes:write') && (onBulkDelete || onBulkPatch);
  const toggleId = (id) => {
    setSelectedIds(prev => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id); else next.add(id);
      return next;
    });
  };
  const allSelected = lfNotes.length > 0 && lfNotes.every(n => selectedIds.has(n.id));
  const selectAll  = () => setSelectedIds(new Set(lfNotes.map(n => n.id)));
  const selectNone = () => setSelectedIds(new Set());
  const exitSelect = () => { setSelectMode(false); selectNone(); };
  const openBulk = (action) => { setBulkAction(action); setBulkInput(''); };
  const closeBulk = () => { setBulkAction(null); setBulkInput(''); };
  const submitBulk = async () => {
    const ids = Array.from(selectedIds);
    if (ids.length === 0) { closeBulk(); return; }
    if (bulkAction === 'delete') {
      await onBulkDelete?.(ids);
    } else if (bulkAction === 'tag') {
      const tag = bulkInput.trim();
      if (!tag) { closeBulk(); return; }
      await onBulkPatch?.(ids, { addTag: tag });
    } else if (bulkAction === 'move') {
      if (!bulkInput || bulkInput === sectionId) { closeBulk(); return; }
      await onBulkPatch?.(ids, { sectionId: bulkInput });
    }
    closeBulk();
    exitSelect();
  };

  return (
    <div style={{ padding: 32, maxWidth: 880, width: '100%', margin: '0 auto', boxSizing: 'border-box' }}>
      <Breadcrumb parts={['sections', lf.short.toLowerCase().replace(' ', '-')]} />
      {/* LF Header */}
      <div style={{
        marginTop: 16, marginBottom: 28, padding: '20px 24px',
        background: 'var(--surface)', border: `1px solid ${lf.color}40`, borderRadius: 12,
        borderLeft: `4px solid ${lf.color}`,
      }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', gap: 16, flexWrap: 'wrap' }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: lf.color, marginBottom: 4, fontWeight: 600 }}>{lf.short}</div>
            <h2 style={{ margin: '0 0 8px', fontSize: 20, fontWeight: 700, color: 'var(--text)', lineHeight: 1.3 }}>{lf.label}</h2>
            <p style={{ margin: 0, fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.6 }}>{lf.description}</p>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 16, marginTop: 14, fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>
          <span>{lfNotes.length} {lfNotes.length === 1 ? 'Notiz' : 'Notizen'}</span>
          <span>·</span>
          <span>{lfCards.length} {lfCards.length === 1 ? 'Lernkarte' : 'Lernkarten'}</span>
        </div>
      </div>

      {/* Actions */}
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 16, flexWrap: 'wrap', gap: 8 }}>
        <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
          {userCan('notes:write') && <Btn variant="primary" icon="plus" onClick={() => setRoute({ page: 'note-new', sectionId: lf.id })}>Neue Notiz</Btn>}
          {lfCards.length > 0 && (
            <Btn variant="secondary" icon="flash" onClick={() => setRoute({ page: 'flashcards', sectionId: lf.id })}>Lernkarten ({lfCards.length})</Btn>
          )}
          {userCan('flashcards:write') && (
            <Btn variant="ghost" icon="edit" onClick={() => setRoute({ page: 'flashcards-manage', sectionId: lf.id })}>Karten verwalten</Btn>
          )}
          {userCan('chat:read') && currentUser && (
            <Btn variant={chatOpen ? 'primary' : 'ghost'} icon="bell" onClick={() => setChatOpen(o => !o)}>
              {chatOpen ? 'Chat schließen' : 'Chat öffnen'}
            </Btn>
          )}
        </div>
        <div style={{ display: 'flex', gap: 4, alignItems: 'center' }}>
          {canBulk && lfNotes.length > 0 && (
            <button onClick={() => { setSelectMode(s => !s); if (selectMode) selectNone(); }}
              title={selectMode ? 'Auswahl beenden' : 'Mehrere auswählen'}
              style={{
                padding: '5px 10px', border: '1px solid var(--border)', borderRadius: 6,
                background: selectMode ? 'var(--accent-dim)' : 'transparent',
                color: selectMode ? 'var(--accent)' : 'var(--text-muted)',
                cursor: 'pointer', fontSize: 11, fontFamily: 'var(--font-mono)',
                display: 'flex', alignItems: 'center', gap: 5,
              }}>
              <Icon name="check" size={13} />
              {selectMode ? 'Auswahl beenden' : 'Auswählen'}
            </button>
          )}
          {['list', 'grid'].map(m => (
            <button key={m} onClick={() => setViewMode(m)} style={{
              padding: '5px 8px', border: '1px solid var(--border)', borderRadius: 6,
              background: viewMode === m ? 'var(--accent-dim)' : 'transparent',
              color: viewMode === m ? 'var(--accent)' : 'var(--text-muted)', cursor: 'pointer', display: 'flex',
            }}>
              <Icon name={m} size={14} />
            </button>
          ))}
        </div>
      </div>

      {/* Bulk Action Bar */}
      {selectMode && (
        <div style={{
          display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap',
          padding: '10px 14px', marginBottom: 14,
          background: 'var(--accent-dim)', border: '1px solid var(--accent)', borderRadius: 8,
          fontSize: 12, fontFamily: 'var(--font-mono)',
        }}>
          <span style={{ color: 'var(--accent)', fontWeight: 600 }}>{selectedIds.size} ausgewählt</span>
          <span style={{ color: 'var(--text-muted)' }}>von {lfNotes.length}</span>
          <button onClick={allSelected ? selectNone : selectAll}
            style={{ background: 'transparent', border: '1px solid var(--border)', borderRadius: 5, padding: '3px 8px', color: 'var(--text-muted)', fontSize: 11, fontFamily: 'inherit', cursor: 'pointer' }}>
            {allSelected ? 'Keine' : 'Alle'}
          </button>
          <div style={{ flex: 1 }} />
          <button onClick={() => openBulk('tag')} disabled={selectedIds.size === 0}
            style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 5, padding: '4px 10px', color: 'var(--text)', fontSize: 11, fontFamily: 'inherit', cursor: selectedIds.size === 0 ? 'not-allowed' : 'pointer', opacity: selectedIds.size === 0 ? 0.5 : 1 }}>
            + Tag
          </button>
          <button onClick={() => openBulk('move')} disabled={selectedIds.size === 0}
            style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 5, padding: '4px 10px', color: 'var(--text)', fontSize: 11, fontFamily: 'inherit', cursor: selectedIds.size === 0 ? 'not-allowed' : 'pointer', opacity: selectedIds.size === 0 ? 0.5 : 1 }}>
            → Verschieben
          </button>
          <button onClick={() => openBulk('delete')} disabled={selectedIds.size === 0}
            style={{ background: 'rgba(248,113,113,0.12)', border: '1px solid rgba(248,113,113,0.4)', borderRadius: 5, padding: '4px 10px', color: '#f87171', fontSize: 11, fontFamily: 'inherit', cursor: selectedIds.size === 0 ? 'not-allowed' : 'pointer', opacity: selectedIds.size === 0 ? 0.5 : 1 }}>
            🗑 Löschen
          </button>
          <button onClick={exitSelect}
            style={{ background: 'transparent', border: 'none', color: 'var(--text-muted)', fontSize: 11, fontFamily: 'inherit', cursor: 'pointer' }}
            title="Auswahl-Modus verlassen">
            Schließen
          </button>
        </div>
      )}

      {/* Notes */}
      {lfNotes.length === 0 ? (
        <EmptyState icon="book" title="Noch keine Notizen" subtitle="Erstelle deine erste Notiz für dieses Lernfeld."
          action={userCan('notes:write') ? <Btn variant="primary" icon="plus" onClick={() => setRoute({ page: 'note-new', sectionId: lf.id })}>Notiz erstellen</Btn> : null} />
      ) : (
        <div style={viewMode === 'grid' ? { display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(240px, 1fr))', gap: 12 } : { display: 'flex', flexDirection: 'column', gap: 8 }}>
          {lfNotes.map(note => {
            const isSelected = selectedIds.has(note.id);
            return (
            <button key={note.id} onClick={() => selectMode ? toggleId(note.id) : setRoute({ page: 'note', noteId: note.id })}
              style={{
                display: 'flex', flexDirection: viewMode === 'grid' ? 'column' : 'row',
                alignItems: viewMode === 'grid' ? 'flex-start' : 'center',
                gap: 12, padding: '12px 16px',
                background: selectMode && isSelected ? 'var(--accent-dim)' : 'var(--surface)',
                border: `1px solid ${selectMode && isSelected ? 'var(--accent)' : 'var(--border)'}`,
                borderRadius: 10,
                cursor: 'pointer', textAlign: 'left', transition: 'all 0.15s',
              }}
              onMouseEnter={e => { if (!(selectMode && isSelected)) { e.currentTarget.style.borderColor = lf.color; e.currentTarget.style.transform = 'translateY(-1px)'; } }}
              onMouseLeave={e => { if (!(selectMode && isSelected)) { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.transform = 'translateY(0)'; } }}
            >
              {selectMode && (
                <span aria-hidden style={{
                  flexShrink: 0, width: 18, height: 18, borderRadius: 4,
                  border: `1.5px solid ${isSelected ? 'var(--accent)' : 'var(--border)'}`,
                  background: isSelected ? 'var(--accent)' : 'transparent',
                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                  color: '#fff',
                }}>
                  {isSelected && <Icon name="check" size={12} />}
                </span>
              )}
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
                  {note.pinned && <Icon name="pin" size={12} color="var(--accent)" />}
                  <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{note.title}</span>
                  {note.isPrivate && <Icon name="lock" size={12} color="var(--text-dim)" />}
                </div>
                {viewMode === 'grid' && (
                  <div style={{ fontSize: 12, color: 'var(--text-muted)', marginBottom: 8, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical', overflow: 'hidden' }}>
                    {note.content.replace(/[#*`\[\]]/g, '').slice(0, 120)}...
                  </div>
                )}
                <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                  {note.tags.slice(0, 3).map(t => <TagBadge key={t} label={t} />)}
                </div>
              </div>
              <div style={{ flexShrink: 0, fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', textAlign: 'right' }}>
                <div>{formatDateDe(note.updatedAt)}</div>
                {note.connections?.length > 0 && <div style={{ color: 'var(--accent)', marginTop: 2 }}>{note.connections.length} Links</div>}
              </div>
              {userCan('notes:write') && onTogglePin && (
                <span
                  role="button" tabIndex={0}
                  onClick={(e) => { e.stopPropagation(); onTogglePin(note.id); }}
                  onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.stopPropagation(); onTogglePin(note.id); } }}
                  title={note.pinned ? 'Pin entfernen' : 'Anheften'}
                  style={{ flexShrink: 0, padding: 6, borderRadius: 6, cursor: 'pointer', display: 'flex', alignItems: 'center', color: note.pinned ? 'var(--accent)' : 'var(--text-dim)' }}
                  onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-2)'}
                  onMouseLeave={e => e.currentTarget.style.background = 'transparent'}
                >
                  <Icon name="pin" size={14} />
                </span>
              )}
            </button>
            );
          })}
        </div>
      )}

      {/* Bulk-Action-Modal */}
      <Modal open={bulkAction !== null} onClose={closeBulk}
        title={
          bulkAction === 'tag'    ? `Tag zu ${selectedIds.size} ${selectedIds.size === 1 ? 'Notiz' : 'Notizen'} hinzufügen`
        : bulkAction === 'move'   ? `${selectedIds.size} ${selectedIds.size === 1 ? 'Notiz' : 'Notizen'} verschieben`
        : bulkAction === 'delete' ? `${selectedIds.size} ${selectedIds.size === 1 ? 'Notiz' : 'Notizen'} löschen?`
        : ''
        }>
        {bulkAction === 'tag' && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            <Input value={bulkInput} onChange={e => setBulkInput(e.target.value)} placeholder="z. B. wichtig" icon="tag" />
            <div style={{ fontSize: 11, color: 'var(--text-muted)' }}>Der Tag wird hinzugefügt, falls er nicht schon vorhanden ist.</div>
            <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
              <Btn variant="ghost" onClick={closeBulk}>Abbrechen</Btn>
              <Btn variant="primary" icon="check" onClick={submitBulk} disabled={!bulkInput.trim()}>Hinzufügen</Btn>
            </div>
          </div>
        )}
        {bulkAction === 'move' && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            <select value={bulkInput} onChange={e => setBulkInput(e.target.value)}
              style={{ background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text)', fontSize: 13, padding: '8px 10px', fontFamily: 'var(--font-mono)' }}>
              <option value="">— Lernfeld wählen —</option>
              {sections.filter(s => s.id !== sectionId).map(s => (
                <option key={s.id} value={s.id}>{s.short} — {s.label}</option>
              ))}
            </select>
            <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
              <Btn variant="ghost" onClick={closeBulk}>Abbrechen</Btn>
              <Btn variant="primary" icon="check" onClick={submitBulk} disabled={!bulkInput}>Verschieben</Btn>
            </div>
          </div>
        )}
        {bulkAction === 'delete' && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
            <div style={{ fontSize: 13, color: 'var(--text-muted)' }}>
              Diese Aktion kann nicht rückgängig gemacht werden. Eigene und (als Admin) auch fremde Notizen werden gelöscht.
            </div>
            <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
              <Btn variant="ghost" onClick={closeBulk}>Abbrechen</Btn>
              <Btn variant="danger" icon="trash" onClick={submitBulk}>Endgültig löschen</Btn>
            </div>
          </div>
        )}
      </Modal>

      {/* Chat-Drawer */}
      {chatOpen && currentUser && userCan('chat:read') && window.ChatDrawer && (
        <window.ChatDrawer
          sectionId={lf.id} sectionLabel={lf.label} sectionShort={lf.short} sectionColor={lf.color}
          user={currentUser} notes={notes} setRoute={setRoute}
          userCan={userCan}
          onClose={() => setChatOpen(false)}
        />
      )}
    </div>
  );
};

// ── NOTE VIEW ─────────────────────────────────────────────────────────────────
const NoteView = ({ noteId, notes, sections, setRoute, onDelete, onUpdateConnections, userCan = () => true }) => {
  const note = notes.find(n => n.id === noteId);
  const lf = note ? sections.find(l => l.id === note.sectionId) : null;
  const connectedNotes = note ? notes.filter(n => note.connections?.includes(n.id)) : [];
  const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
  const [connSearch, setConnSearch] = useState('');
  const [showConnSearch, setShowConnSearch] = useState(false);

  useEffect(() => {
    if (window.hljs) {
      document.querySelectorAll('pre code').forEach(block => window.hljs.highlightElement(block));
    }
  }, [noteId]);

  if (!note) return <div style={{ padding: 32, color: 'var(--text-muted)' }}>Notiz nicht gefunden.</div>;

  const renderedMd = (note.format || 'markdown') === 'html'
    ? (note.content || '')
    : (window.marked ? window.marked.parse(note.content || '') : (note.content || ''));

  return (
    <div style={{ display: 'flex', height: '100%' }}>
      <div style={{ flex: 1, padding: 32, overflowY: 'auto', minWidth: 0 }}>
        <div style={{ maxWidth: 860, width: '100%', margin: '0 auto', boxSizing: 'border-box' }}>
        <Breadcrumb parts={['notizen', lf?.short?.toLowerCase().replace(' ', '-') || 'unbekannt', note.title.toLowerCase().replace(/ /g, '-')]} />

        <div style={{ marginTop: 16, marginBottom: 24, display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16 }}>
          <div style={{ flex: 1 }}>
            <h1 style={{ margin: '0 0 10px', fontSize: 26, fontWeight: 700, color: 'var(--text)', letterSpacing: '-0.03em', lineHeight: 1.2 }}>{note.title}</h1>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
              {lf && <button onClick={() => setRoute({ page: 'section', sectionId: lf.id })} style={{ background: 'none', border: 'none', cursor: 'pointer', padding: 0 }}><TagBadge label={lf.short} color={lf.color} /></button>}
              {note.tags.map(t => <TagBadge key={t} label={t} />)}
              {note.isPrivate && <TagBadge label="🔒 privat" />}
            </div>
          </div>
          <div style={{ display: 'flex', gap: 6, flexShrink: 0 }}>
            {userCan('notes:write') && <Btn variant="secondary" size="sm" icon="edit" onClick={() => setRoute({ page: 'note-edit', noteId: note.id })}>Bearbeiten</Btn>}
            {userCan('notes:delete') && <Btn variant="danger" size="sm" icon="trash" onClick={() => setShowDeleteConfirm(true)} />}
          </div>
        </div>

        <div style={{ fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)', marginBottom: 24, display: 'flex', gap: 16 }}>
          <span>Erstellt: {formatDateDe(note.createdAt)}</span>
          <span>·</span>
          <span>Bearbeitet: {formatDateDe(note.updatedAt)}</span>
        </div>

        {/* Rendered Markdown */}
        <div
          className="md-content"
          dangerouslySetInnerHTML={{ __html: renderedMd }}
        />
        </div>
      </div>

      {/* Connections Sidebar — always visible */}
      <div style={{
        width: 234, flexShrink: 0, borderLeft: '1px solid var(--border)',
        padding: '28px 14px 20px', background: 'var(--sidebar-bg)',
        display: 'flex', flexDirection: 'column', gap: 0, overflowY: 'auto',
      }}>
        {/* Header */}
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
            <Icon name="link" size={13} color="var(--accent)" />
            <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>
              Verknüpfungen
            </span>
            {connectedNotes.length > 0 && (
              <span style={{ fontSize: 10, padding: '1px 6px', borderRadius: 10, background: 'var(--accent-dim)', color: 'var(--accent)', fontFamily: 'var(--font-mono)' }}>
                {connectedNotes.length}
              </span>
            )}
          </div>
          <button
            onClick={() => { setShowConnSearch(!showConnSearch); setConnSearch(''); }}
            title="Verbindung hinzufügen"
            style={{
              width: 24, height: 24, borderRadius: 6, border: '1px solid var(--border)',
              background: showConnSearch ? 'var(--accent-dim)' : 'transparent',
              color: showConnSearch ? 'var(--accent)' : 'var(--text-muted)',
              cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center',
              transition: 'all 0.15s',
            }}
          >
            <Icon name="plus" size={13} />
          </button>
        </div>

        {/* Add connection search */}
        {showConnSearch && (
          <div style={{ marginBottom: 12 }}>
            <input
              autoFocus
              value={connSearch}
              onChange={e => setConnSearch(e.target.value)}
              placeholder="Notiz suchen…"
              style={{
                width: '100%', boxSizing: 'border-box', padding: '6px 10px',
                background: 'var(--surface-2)', border: '1px solid var(--accent)',
                borderRadius: 6, color: 'var(--text)', fontSize: 12,
                fontFamily: 'var(--font-sans)', outline: 'none',
                boxShadow: '0 0 0 3px var(--accent-dim)',
              }}
            />
            {/* Results */}
            {connSearch.trim().length > 0 && (() => {
              const available = notes.filter(n =>
                n.id !== note.id &&
                !note.connections?.includes(n.id) &&
                n.title.toLowerCase().includes(connSearch.toLowerCase())
              );
              return available.length > 0 ? (
                <div style={{ marginTop: 6, display: 'flex', flexDirection: 'column', gap: 3, maxHeight: 180, overflowY: 'auto' }}>
                  {available.map(n => {
                    const nLf = sections.find(l => l.id === n.sectionId);
                    return (
                      <button key={n.id}
                        onClick={() => {
                          onUpdateConnections(note.id, [...(note.connections || []), n.id]);
                          setConnSearch('');
                          setShowConnSearch(false);
                        }}
                        style={{
                          display: 'flex', alignItems: 'center', gap: 8, padding: '7px 10px',
                          background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 7,
                          cursor: 'pointer', textAlign: 'left', transition: 'all 0.12s',
                        }}
                        onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--accent)'; e.currentTarget.style.background = 'var(--accent-dim)'; }}
                        onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.background = 'var(--surface)'; }}
                      >
                        <span style={{ width: 6, height: 6, borderRadius: '50%', background: nLf?.color || '#9b5cf6', flexShrink: 0 }} />
                        <span style={{ flex: 1, fontSize: 12, color: 'var(--text)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{n.title}</span>
                        <Icon name="plus" size={11} color="var(--accent)" />
                      </button>
                    );
                  })}
                </div>
              ) : (
                <div style={{ marginTop: 8, fontSize: 11, color: 'var(--text-dim)', fontFamily: 'var(--font-mono)' }}>
                  // keine Ergebnisse
                </div>
              );
            })()}
          </div>
        )}

        {/* Connected notes list */}
        {connectedNotes.length === 0 && !showConnSearch ? (
          <div style={{ textAlign: 'center', padding: '24px 8px' }}>
            <Icon name="link" size={24} color="var(--text-dim)" />
            <div style={{ marginTop: 8, fontSize: 12, color: 'var(--text-dim)', lineHeight: 1.5 }}>
              Noch keine Verknüpfungen.<br />
              <button onClick={() => setShowConnSearch(true)} style={{ background: 'none', border: 'none', color: 'var(--accent)', cursor: 'pointer', fontSize: 12, textDecoration: 'underline', padding: 0 }}>
                Jetzt hinzufügen
              </button>
            </div>
          </div>
        ) : (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 5 }}>
            {connectedNotes.map(cn => {
              const cnLf = sections.find(l => l.id === cn.sectionId);
              return (
                <div key={cn.id} style={{
                  display: 'flex', alignItems: 'center', gap: 0,
                  background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8,
                  overflow: 'hidden', transition: 'border-color 0.15s',
                }}
                  onMouseEnter={e => e.currentTarget.style.borderColor = cnLf?.color || 'var(--accent)'}
                  onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--border)'}
                >
                  <button onClick={() => setRoute({ page: 'note', noteId: cn.id })}
                    style={{ flex: 1, display: 'flex', alignItems: 'center', gap: 8, padding: '8px 10px', background: 'none', border: 'none', cursor: 'pointer', textAlign: 'left' }}>
                    <span style={{ width: 6, height: 6, borderRadius: '50%', background: cnLf?.color || '#9b5cf6', flexShrink: 0 }} />
                    <span style={{ fontSize: 12, color: 'var(--text)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{cn.title}</span>
                  </button>
                  <button
                    onClick={() => onUpdateConnections(note.id, (note.connections || []).filter(id => id !== cn.id))}
                    title="Verbindung entfernen"
                    style={{ padding: '0 8px', height: '100%', background: 'none', border: 'none', borderLeft: '1px solid var(--border)', cursor: 'pointer', color: 'var(--text-dim)', display: 'flex', alignItems: 'center' }}
                    onMouseEnter={e => e.currentTarget.style.color = '#f87171'}
                    onMouseLeave={e => e.currentTarget.style.color = 'var(--text-dim)'}
                  >
                    <Icon name="x" size={12} />
                  </button>
                </div>
              );
            })}
          </div>
        )}

        {/* Open in graph */}
        {connectedNotes.length > 0 && (
          <button onClick={() => setRoute({ page: 'graph' })}
            style={{ marginTop: 16, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 6, padding: '7px', border: '1px dashed var(--border)', borderRadius: 8, background: 'transparent', color: 'var(--text-muted)', cursor: 'pointer', fontSize: 12, fontFamily: 'var(--font-sans)', transition: 'all 0.15s' }}
            onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--accent)'; e.currentTarget.style.color = 'var(--accent)'; }}
            onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.color = 'var(--text-muted)'; }}
          >
            <Icon name="link" size={13} /> Im Graph ansehen
          </button>
        )}
      </div>

      {/* Delete Confirm */}
      <Modal open={showDeleteConfirm} onClose={() => setShowDeleteConfirm(false)} title="Notiz löschen?">
        <p style={{ color: 'var(--text-muted)', fontSize: 13, margin: '0 0 20px' }}>Möchtest du <strong style={{ color: 'var(--text)' }}>„{note.title}"</strong> wirklich löschen? Dies kann nicht rückgängig gemacht werden.</p>
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <Btn variant="secondary" onClick={() => setShowDeleteConfirm(false)}>Abbrechen</Btn>
          <Btn variant="danger" icon="trash" onClick={() => { onDelete(note.id); setShowDeleteConfirm(false); setRoute({ page: 'section', sectionId: note.sectionId }); }}>Löschen</Btn>
        </div>
      </Modal>
    </div>
  );
};

// ── MARKDOWN EDITOR ───────────────────────────────────────────────────────────
const MarkdownEditor = ({ noteId, sectionId: defaultLfId, notes, sections, onSave, setRoute }) => {
  const existing = noteId ? notes.find(n => n.id === noteId) : null;
  const [title, setTitle] = useState(existing?.title || '');
  const [tags, setTags] = useState(existing?.tags?.join(', ') || '');
  const [selectedLf, setSelectedLf] = useState(existing?.sectionId || defaultLfId || 'lf03');
  const [isPrivate, setIsPrivate] = useState(existing?.isPrivate || false);
  const [saved, setSaved] = useState(false);
  const editorTargetRef = useRef(null);
  const tinyRef = useRef(null);

  const titleRef = useRef(title); titleRef.current = title;
  const tagsRef = useRef(tags); tagsRef.current = tags;
  const selectedLfRef = useRef(selectedLf); selectedLfRef.current = selectedLf;
  const isPrivateRef = useRef(isPrivate); isPrivateRef.current = isPrivate;
  const savedIdRef = useRef(existing?.id || null);
  const createdAtRef = useRef(existing?.createdAt || new Date().toISOString().slice(0, 10));

  const initialHtml = (() => {
    if (!existing) return '';
    if ((existing.format || 'markdown') === 'html') return existing.content || '';
    return window.marked ? window.marked.parse(existing.content || '') : (existing.content || '');
  })();

  const handleSave = (opts = {}) => {
    const content = tinyRef.current ? tinyRef.current.getContent() : '';
    const tagList = tagsRef.current.split(',').map(t => t.trim()).filter(Boolean);
    if (!savedIdRef.current) savedIdRef.current = `n${Date.now()}`;
    onSave({
      id: savedIdRef.current,
      title: titleRef.current || 'Unbenannte Notiz',
      content,
      format: 'html',
      tags: tagList,
      sectionId: selectedLfRef.current,
      isPrivate: isPrivateRef.current,
      connections: existing?.connections || [],
      createdAt: createdAtRef.current,
      updatedAt: new Date().toISOString().slice(0, 10),
    }, { silent: !!opts.silent, navigate: !opts.silent });
    setSaved(true);
    setTimeout(() => setSaved(false), 2000);
  };
  const handleSaveRef = useRef(handleSave);
  handleSaveRef.current = handleSave;

  useEffect(() => {
    if (!editorTargetRef.current || !window.tinymce) return;
    let editor = null;
    let autoSaveTimer = null;
    let lastSaved = '';
    const isDark = document.documentElement.getAttribute('data-theme') !== 'light';
    const bg = isDark ? '#0b0b0e' : '#f7f6fc';
    const fg = isDark ? '#e8e5f0' : '#1a1728';
    const borderC = isDark ? '#26263a' : '#e3dff2';
    const accent = isDark ? '#9b5cf6' : '#7c3aed';

    const TEMPLATES = [
      {
        title: 'Lernfeld-Zusammenfassung',
        description: 'Strukturierte Zusammenfassung mit Theorie, Praxis, Merksatz, Quellen',
        content: '<h1>Thema</h1><h2>Theorie</h2><p>Die wichtigsten Konzepte:</p><ul><li>Punkt 1</li><li>Punkt 2</li></ul><h2>Praxis / Anwendung</h2><p>Wie wende ich das an?</p><h2>Wichtig zu merken</h2><blockquote><p>Merksatz hier.</p></blockquote><h2>Quellen</h2><ul><li>Buch / Seite</li></ul>',
      },
      {
        title: 'Klausurvorbereitung',
        description: 'Checkliste mit Lernzielen',
        content: '<h1>Klausur: Thema</h1><p><strong>Datum:</strong> TT.MM.JJJJ</p><h2>Lernziele</h2><ul><li>☐ Ziel 1</li><li>☐ Ziel 2</li><li>☐ Ziel 3</li></ul><h2>Stoff</h2><ul><li>Kapitel / Lernfeld</li></ul><h2>Übungsaufgaben</h2><ol><li>Aufgabe 1</li></ol>',
      },
      {
        title: 'Vokabeln / Fachbegriffe',
        description: 'Tabelle mit Begriffen und Definitionen',
        content: '<h1>Fachbegriffe — Thema</h1><table><thead><tr><th>Begriff</th><th>Definition</th><th>Beispiel</th></tr></thead><tbody><tr><td>Begriff 1</td><td>Definition</td><td>Beispiel</td></tr><tr><td>&nbsp;</td><td>&nbsp;</td><td>&nbsp;</td></tr></tbody></table>',
      },
      {
        title: 'Tutorial / How-To',
        description: 'Schritt-für-Schritt-Anleitung',
        content: '<h1>How-To: …</h1><p><em>Voraussetzungen:</em> …</p><h2>Schritte</h2><ol><li>Erster Schritt</li><li>Zweiter Schritt</li><li>Dritter Schritt</li></ol><h2>Häufige Probleme</h2><ul><li>Problem → Lösung</li></ul>',
      },
    ];

    window.tinymce.init({
      target: editorTargetRef.current,
      height: '100%',
      menubar: 'edit insert format table',
      plugins: 'advlist autolink lists link image charmap searchreplace code table help fullscreen quickbars wordcount codesample emoticons media',
      toolbar: 'undo redo | blocks vorlagen | bold italic underline strikethrough | alignleft aligncenter alignright | bullist numlist outdent indent | link image media table codesample emoticons | removeformat | code fullscreen',
      quickbars_selection_toolbar: 'bold italic underline | h1 h2 h3 | blockquote codesample | link',
      quickbars_insert_toolbar: false,
      quickbars_image_toolbar: 'alignleft aligncenter alignright | rotateleft rotateright',
      codesample_languages: [
        { text: 'Bash', value: 'bash' },
        { text: 'C', value: 'c' },
        { text: 'C++', value: 'cpp' },
        { text: 'CSS', value: 'css' },
        { text: 'HTML/XML', value: 'markup' },
        { text: 'JavaScript', value: 'javascript' },
        { text: 'JSON', value: 'json' },
        { text: 'Java', value: 'java' },
        { text: 'PHP', value: 'php' },
        { text: 'Python', value: 'python' },
        { text: 'SQL', value: 'sql' },
        { text: 'TypeScript', value: 'typescript' },
        { text: 'YAML', value: 'yaml' },
      ],
      branding: false,
      promotion: false,
      license_key: 'gpl',
      skin: isDark ? 'oxide-dark' : 'oxide',
      content_css: isDark ? 'dark' : 'default',
      content_style: `
        body {
          background: ${bg};
          color: ${fg};
          font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
          font-size: 14px;
          line-height: 1.6;
          padding: 16px 24px;
        }
        a { color: ${accent}; }
        img { max-width: 100%; height: auto; border-radius: 6px; }
        table { border-collapse: collapse; }
        table td, table th { border: 1px solid ${borderC}; padding: 6px 10px; }
        table th { background: ${isDark ? '#1c1c25' : '#f0eefa'}; }
        blockquote { border-left: 3px solid ${accent}; padding-left: 12px; color: ${isDark ? '#6b6880' : '#7b7896'}; margin: 8px 0; }
        code { background: ${isDark ? '#1c1c25' : '#f0eefa'}; padding: 2px 6px; border-radius: 4px; font-family: 'JetBrains Mono', monospace; font-size: 0.9em; }
        pre { background: ${isDark ? '#16161d' : '#f0eefa'}; padding: 12px; border-radius: 6px; overflow-x: auto; }
        pre code { background: none; padding: 0; }
        hr { border: none; border-top: 1px solid ${borderC}; margin: 16px 0; }
      `,
      paste_data_images: true,
      images_upload_handler: async (blobInfo) => {
        const fd = new FormData();
        fd.append('file', blobInfo.blob(), blobInfo.filename());
        const res = await window.apiFetch('/api/uploads', { method: 'POST', body: fd });
        if (!res.ok) throw new Error(res.data?.error || `Upload fehlgeschlagen (${res.status})`);
        return res.data.url;
      },
      setup: (ed) => {
        editor = ed;
        tinyRef.current = ed;
        ed.on('init', () => {
          ed.setContent(initialHtml);
          lastSaved = ed.getContent();
        });
        ed.addShortcut('meta+s', 'Speichern', () => handleSaveRef.current());
        ed.ui.registry.addMenuButton('vorlagen', {
          text: 'Vorlagen',
          tooltip: 'Notiz-Vorlage einfügen',
          fetch: (callback) => {
            callback(TEMPLATES.map(t => ({
              type: 'menuitem',
              text: t.title,
              onAction: () => {
                if (ed.getContent({ format: 'text' }).trim().length > 0
                    && !confirm('Vorlage einfügen? Die aktuelle Notiz wird ersetzt.')) return;
                ed.setContent(t.content);
              },
            })));
          },
        });
      },
    });

    autoSaveTimer = setInterval(() => {
      if (!tinyRef.current) return;
      const now = tinyRef.current.getContent();
      if (now && now !== lastSaved && titleRef.current.trim()) {
        lastSaved = now;
        handleSaveRef.current({ silent: true });
      }
    }, 5000);

    return () => {
      if (autoSaveTimer) clearInterval(autoSaveTimer);
      if (editor) { try { editor.destroy(); } catch {} }
      tinyRef.current = null;
    };
  }, []);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
      {/* Editor Header */}
      <div style={{ borderBottom: '1px solid var(--border)', padding: '12px 24px', display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0, background: 'var(--sidebar-bg)' }}>
        <button onClick={() => setRoute(existing ? { page: 'note', noteId: existing.id } : { page: 'section', sectionId: selectedLf })}
          style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', display: 'flex', gap: 4, alignItems: 'center', fontSize: 13 }}>
          <Icon name="arrowLeft" size={14} /> Zurück
        </button>
        <div style={{ flex: 1 }}>
          <input value={title} onChange={e => setTitle(e.target.value)} placeholder="Titel der Notiz..."
            style={{
              width: '100%', background: 'none', border: 'none', outline: 'none',
              fontSize: 18, fontWeight: 700, color: 'var(--text)', fontFamily: 'var(--font-sans)',
            }}
          />
        </div>
        <Btn variant={saved ? 'secondary' : 'primary'} icon={saved ? 'check' : 'save'} onClick={handleSave}>
          {saved ? 'Gespeichert!' : 'Speichern'}
        </Btn>
      </div>

      {/* Meta Bar */}
      <div style={{ borderBottom: '1px solid var(--border)', padding: '8px 24px', display: 'flex', alignItems: 'center', gap: 16, flexShrink: 0, flexWrap: 'wrap' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <span style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>LF:</span>
          <select value={selectedLf} onChange={e => setSelectedLf(e.target.value)}
            style={{ background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text)', fontSize: 12, padding: '4px 8px', fontFamily: 'var(--font-mono)', outline: 'none' }}>
            {sections.map(lf => <option key={lf.id} value={lf.id}>{lf.short} — {lf.label.slice(0, 40)}...</option>)}
          </select>
        </div>
        <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          <span style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>Tags:</span>
          <input value={tags} onChange={e => setTags(e.target.value)} placeholder="tag1, tag2, tag3"
            style={{ background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text)', fontSize: 12, padding: '4px 8px', fontFamily: 'var(--font-mono)', outline: 'none', width: 200 }}
          />
        </div>
        <label style={{ display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer', fontSize: 12, color: 'var(--text-muted)' }}>
          <input type="checkbox" checked={isPrivate} onChange={e => setIsPrivate(e.target.checked)} style={{ accentColor: 'var(--accent)' }} />
          <Icon name="lock" size={12} /> Privat
        </label>
      </div>

      {/* TinyMCE Editor */}
      <div style={{ flex: 1, minHeight: 0, overflow: 'hidden' }}>
        <textarea ref={editorTargetRef} style={{ width: '100%', height: '100%' }} />
      </div>
    </div>
  );
};

// ── FLASHCARDS ────────────────────────────────────────────────────────────────
const FlashcardsView = ({ sectionId, sections, flashcards, setRoute, userCan = () => true }) => {
  const lf = sections.find(l => l.id === sectionId);
  const cards = flashcards.filter(c => c.sectionId === sectionId);
  const [idx, setIdx] = useState(0);
  const [flipped, setFlipped] = useState(false);
  const [known, setKnown] = useState([]);
  const [unknown, setUnknown] = useState([]);
  const [done, setDone] = useState(false);

  if (cards.length === 0) return (
    <div style={{ padding: 32 }}>
      <EmptyState
        icon="flash"
        title="Keine Lernkarten"
        subtitle="Für dieses Lernfeld gibt es noch keine Lernkarten."
        action={userCan('flashcards:write') ? (
          <Btn variant="primary" icon="plus" onClick={() => setRoute({ page: 'flashcards-manage', sectionId })}>Karten verwalten</Btn>
        ) : null}
      />
    </div>
  );

  if (done || idx >= cards.length) {
    return (
      <div style={{ padding: 32, maxWidth: 560, width: '100%', margin: '0 auto', boxSizing: 'border-box', textAlign: 'center' }}>
        <div style={{ marginBottom: 16 }}><Icon name="check" size={48} color="#34d399" /></div>
        <h2 style={{ color: 'var(--text)', marginBottom: 8 }}>Runde beendet!</h2>
        <div style={{ color: 'var(--text-muted)', marginBottom: 24, fontSize: 14 }}>
          <span style={{ color: '#34d399' }}>{known.length} gewusst</span> · <span style={{ color: '#f87171' }}>{unknown.length} nicht gewusst</span>
        </div>
        <div style={{ display: 'flex', gap: 10, justifyContent: 'center' }}>
          <Btn variant="primary" onClick={() => { setIdx(0); setFlipped(false); setKnown([]); setUnknown([]); setDone(false); }}>Nochmal</Btn>
          <Btn variant="secondary" onClick={() => setRoute({ page: 'section', sectionId })}>Zurück zu {lf?.short}</Btn>
        </div>
      </div>
    );
  }

  const card = cards[idx];
  const handleKnown = (k) => {
    if (k) setKnown(p => [...p, card.id]);
    else setUnknown(p => [...p, card.id]);
    setFlipped(false);
    setTimeout(() => setIdx(i => i + 1), 200);
  };

  return (
    <div style={{ padding: 32, maxWidth: 640, width: '100%', margin: '0 auto', boxSizing: 'border-box' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 24 }}>
        <button onClick={() => setRoute({ page: 'section', sectionId })} style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', display: 'flex', alignItems: 'center', gap: 4, fontSize: 13 }}>
          <Icon name="arrowLeft" size={14} /> {lf?.short}
        </button>
        <span style={{ color: 'var(--text-dim)' }}>·</span>
        <span style={{ fontSize: 13, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{idx + 1} / {cards.length}</span>
        <div style={{ flex: 1 }}>
          <ProgressBar value={((idx) / cards.length) * 100} color={lf?.color} height={3} animate={false} />
        </div>
      </div>

      {/* Card */}
      <div
        onClick={() => setFlipped(!flipped)}
        style={{
          perspective: 1000,
          cursor: 'pointer', marginBottom: 24,
        }}
      >
        <div style={{
          position: 'relative', minHeight: 260,
          transformStyle: 'preserve-3d',
          transform: flipped ? 'rotateY(180deg)' : 'rotateY(0)',
          transition: 'transform 0.5s cubic-bezier(0.4,0,0.2,1)',
        }}>
          {/* Front */}
          <div style={{
            position: flipped ? 'absolute' : 'relative', inset: 0,
            backfaceVisibility: 'hidden', WebkitBackfaceVisibility: 'hidden',
            background: 'var(--surface)', border: `1px solid ${lf?.color || 'var(--accent)'}40`,
            borderRadius: 14, padding: '32px 32px',
            display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 16,
            boxShadow: `0 8px 32px rgba(0,0,0,0.2)`,
          }}>
            <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.1em' }}>Frage</div>
            <div style={{ fontSize: 18, fontWeight: 600, color: 'var(--text)', textAlign: 'center', lineHeight: 1.5 }}>{card.question}</div>
            <div style={{ fontSize: 12, color: 'var(--text-dim)' }}>Klicken zum Umdrehen →</div>
          </div>
          {/* Back */}
          <div style={{
            position: 'absolute', inset: 0,
            backfaceVisibility: 'hidden', WebkitBackfaceVisibility: 'hidden',
            transform: 'rotateY(180deg)',
            background: `linear-gradient(135deg, ${lf?.color || 'var(--accent)'}15, var(--surface))`,
            border: `1px solid ${lf?.color || 'var(--accent)'}60`,
            borderRadius: 14, padding: '32px 32px',
            display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: 16,
          }}>
            <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: lf?.color || 'var(--accent)', textTransform: 'uppercase', letterSpacing: '0.1em' }}>Antwort</div>
            <div style={{ fontSize: 15, color: 'var(--text)', textAlign: 'center', lineHeight: 1.7, whiteSpace: 'pre-line', fontFamily: 'var(--font-mono)' }}>{card.answer}</div>
          </div>
        </div>
      </div>

      {/* Actions */}
      {flipped && (
        <div style={{ display: 'flex', gap: 12, justifyContent: 'center' }}>
          <button onClick={() => handleKnown(false)}
            style={{ flex: 1, maxWidth: 180, padding: '12px 20px', borderRadius: 10, border: '1px solid rgba(248,113,113,0.4)', background: 'rgba(248,113,113,0.1)', color: '#f87171', cursor: 'pointer', fontSize: 14, fontWeight: 600, fontFamily: 'var(--font-sans)' }}>
            ✕ Nicht gewusst
          </button>
          <button onClick={() => handleKnown(true)}
            style={{ flex: 1, maxWidth: 180, padding: '12px 20px', borderRadius: 10, border: '1px solid rgba(52,211,153,0.4)', background: 'rgba(52,211,153,0.1)', color: '#34d399', cursor: 'pointer', fontSize: 14, fontWeight: 600, fontFamily: 'var(--font-sans)' }}>
            ✓ Gewusst
          </button>
        </div>
      )}
      {!flipped && (
        <div style={{ textAlign: 'center' }}>
          <button onClick={() => setFlipped(true)}
            style={{ padding: '10px 24px', borderRadius: 10, border: '1px solid var(--border)', background: 'var(--surface)', color: 'var(--text-muted)', cursor: 'pointer', fontSize: 13 }}>
            Antwort zeigen
          </button>
        </div>
      )}
    </div>
  );
};

// ── FLASHCARDS MANAGE ─────────────────────────────────────────────────────────
const FlashcardsManageView = ({ sectionId, sections, flashcards, setRoute, userCan = () => true, currentUser, onAdd, onUpdate, onDelete }) => {
  const lf = sections.find(l => l.id === sectionId);
  const lfCards = flashcards.filter(c => c.sectionId === sectionId);
  const [editing, setEditing] = useState(null); // null | 'new' | { id, ... }
  const [confirmDeleteId, setConfirmDeleteId] = useState(null);

  if (!lf) return <div style={{ padding: 32, color: 'var(--text-muted)' }}>Lernfeld nicht gefunden.</div>;

  const canEditCard = (card) => currentUser?.id === card.ownerId || can(currentUser, 'flashcards:write');

  const blank = { question: '', answer: '', isPrivate: false };
  const startNew = () => setEditing({ ...blank, isNew: true });
  const startEdit = (card) => setEditing({ ...card });
  const cancel = () => setEditing(null);

  const save = async () => {
    if (!editing) return;
    if (!editing.question.trim() || !editing.answer.trim()) return;
    const payload = {
      sectionId: lf.id,
      question: editing.question.trim(),
      answer: editing.answer.trim(),
      isPrivate: !!editing.isPrivate,
    };
    if (editing.isNew) {
      const ok = await onAdd(payload);
      if (ok) setEditing(null);
    } else {
      const ok = await onUpdate({ ...payload, id: editing.id });
      if (ok) setEditing(null);
    }
  };

  const confirmDelete = async () => {
    if (!confirmDeleteId) return;
    await onDelete(confirmDeleteId);
    setConfirmDeleteId(null);
  };

  return (
    <div style={{ padding: 32, maxWidth: 880, width: '100%', margin: '0 auto', boxSizing: 'border-box' }}>
      <Breadcrumb parts={['flashcards', lf.short.toLowerCase().replace(' ', '-'), 'verwalten']} />

      {/* Header */}
      <div style={{
        marginTop: 16, marginBottom: 24, padding: '20px 24px',
        background: 'var(--surface)', border: `1px solid ${lf.color}40`, borderRadius: 12,
        borderLeft: `4px solid ${lf.color}`,
      }}>
        <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 16, flexWrap: 'wrap' }}>
          <div style={{ flex: 1 }}>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: lf.color, marginBottom: 4, fontWeight: 600 }}>{lf.short}</div>
            <h2 style={{ margin: '0 0 6px', fontSize: 20, fontWeight: 700, color: 'var(--text)', lineHeight: 1.3 }}>Lernkarten verwalten</h2>
            <div style={{ fontSize: 13, color: 'var(--text-muted)' }}>{lfCards.length} {lfCards.length === 1 ? 'Karte' : 'Karten'}</div>
          </div>
          <div style={{ display: 'flex', gap: 8, flexShrink: 0 }}>
            {lfCards.length > 0 && (
              <Btn variant="secondary" icon="flash" onClick={() => setRoute({ page: 'flashcards', sectionId: lf.id })}>Lernen</Btn>
            )}
            <Btn variant="ghost" onClick={() => setRoute({ page: 'section', sectionId: lf.id })}>Zurück</Btn>
          </div>
        </div>
      </div>

      {/* Add button */}
      {userCan('flashcards:write') && (
        <div style={{ marginBottom: 16 }}>
          <Btn variant="primary" icon="plus" onClick={startNew}>Neue Karte</Btn>
        </div>
      )}

      {/* Cards list */}
      {lfCards.length === 0 ? (
        <EmptyState
          icon="flash"
          title="Noch keine Lernkarten"
          subtitle="Erstelle deine erste Karte für dieses Lernfeld."
          action={userCan('flashcards:write') ? <Btn variant="primary" icon="plus" onClick={startNew}>Karte erstellen</Btn> : null}
        />
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {lfCards.map(card => {
            const editable = canEditCard(card);
            return (
              <div key={card.id} style={{
                padding: '14px 18px',
                background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 10,
                display: 'flex', gap: 14, alignItems: 'flex-start',
              }}>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 6 }}>
                    <span style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>Frage</span>
                    {card.isPrivate && <TagBadge label="🔒 privat" />}
                  </div>
                  <div style={{ fontSize: 14, color: 'var(--text)', fontWeight: 600, marginBottom: 10, lineHeight: 1.4 }}>{card.question}</div>
                  <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 4 }}>Antwort</div>
                  <div style={{ fontSize: 13, color: 'var(--text-muted)', whiteSpace: 'pre-line', lineHeight: 1.5 }}>{card.answer}</div>
                </div>
                {editable && (
                  <div style={{ display: 'flex', gap: 6, flexShrink: 0 }}>
                    <Btn variant="secondary" size="sm" icon="edit" onClick={() => startEdit(card)}>Bearbeiten</Btn>
                    <Btn variant="danger" size="sm" icon="trash" onClick={() => setConfirmDeleteId(card.id)} />
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}

      {/* Editor modal */}
      <Modal
        open={!!editing}
        onClose={cancel}
        title={editing?.isNew ? 'Neue Lernkarte' : 'Lernkarte bearbeiten'}
        width={560}
      >
        {editing && (
          <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
            <div>
              <label style={{ display: 'block', fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 6 }}>Frage</label>
              <textarea
                value={editing.question}
                onChange={e => setEditing(p => ({ ...p, question: e.target.value }))}
                rows={3}
                style={{
                  width: '100%', boxSizing: 'border-box', padding: '8px 12px',
                  background: 'var(--surface-2)', border: '1px solid var(--border)',
                  borderRadius: 'var(--radius)', color: 'var(--text)',
                  fontFamily: 'var(--font-sans)', fontSize: 13, resize: 'vertical', outline: 'none',
                }}
              />
            </div>
            <div>
              <label style={{ display: 'block', fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 6 }}>Antwort</label>
              <textarea
                value={editing.answer}
                onChange={e => setEditing(p => ({ ...p, answer: e.target.value }))}
                rows={6}
                style={{
                  width: '100%', boxSizing: 'border-box', padding: '8px 12px',
                  background: 'var(--surface-2)', border: '1px solid var(--border)',
                  borderRadius: 'var(--radius)', color: 'var(--text)',
                  fontFamily: 'var(--font-mono)', fontSize: 13, resize: 'vertical', outline: 'none',
                  whiteSpace: 'pre-wrap',
                }}
              />
            </div>
            <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 13, color: 'var(--text)', cursor: 'pointer' }}>
              <input
                type="checkbox"
                checked={!!editing.isPrivate}
                onChange={e => setEditing(p => ({ ...p, isPrivate: e.target.checked }))}
              />
              Privat (nur für mich sichtbar)
            </label>
            <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 8 }}>
              <Btn variant="ghost" onClick={cancel}>Abbrechen</Btn>
              <Btn variant="primary" icon="check"
                onClick={save}
                disabled={!editing.question.trim() || !editing.answer.trim()}>
                Speichern
              </Btn>
            </div>
          </div>
        )}
      </Modal>

      {/* Delete confirm */}
      <Modal
        open={!!confirmDeleteId}
        onClose={() => setConfirmDeleteId(null)}
        title="Karte löschen?"
        width={400}
      >
        <div style={{ fontSize: 13, color: 'var(--text-muted)', marginBottom: 16 }}>
          Diese Lernkarte wird unwiderruflich gelöscht.
        </div>
        <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end' }}>
          <Btn variant="ghost" onClick={() => setConfirmDeleteId(null)}>Abbrechen</Btn>
          <Btn variant="danger" icon="trash" onClick={confirmDelete}>Löschen</Btn>
        </div>
      </Modal>
    </div>
  );
};

// ── SEARCH VIEW ───────────────────────────────────────────────────────────────
const SearchView = ({ query: initialQuery, notes, sections, setRoute }) => {
  const [q, setQ] = useState(initialQuery || '');
  const [filterLf, setFilterLf] = useState(null);
  const inputRef = useRef(null);

  // Sync with sidebar search on every route change
  useEffect(() => { setQ(initialQuery || ''); }, [initialQuery]);

  // Auto-focus input on mount
  useEffect(() => { inputRef.current?.focus(); }, []);

  const lq = q.trim().toLowerCase();

  // Fuse-Index: enthält pro Notiz die Felder + injizierte LF-Spalten,
  // damit ein Match auf 'BS-Lernfeld 03' / 'Datenbanksysteme' gegen die LF-Metadaten geht.
  const fuse = useMemo(() => {
    if (!window.Fuse) return null;
    const lfMap = new Map(sections.map(l => [l.id, l]));
    const docs = notes.map(n => {
      const lf = lfMap.get(n.sectionId);
      return {
        ...n,
        __lfShort: lf?.short || '',
        __lfLabel: lf?.label || lf?.title || '',
      };
    });
    return new window.Fuse(docs, {
      keys: [
        { name: 'title',     weight: 0.45 },
        { name: 'tags',      weight: 0.20 },
        { name: '__lfShort', weight: 0.15 },
        { name: '__lfLabel', weight: 0.10 },
        { name: 'content',   weight: 0.10 },
      ],
      threshold: 0.35,
      ignoreLocation: true,
      includeMatches: true,
      minMatchCharLength: 2,
    });
  }, [notes, sections]);

  // Build (note, matchKeys) pairs. Wenn Fuse fehlt (CDN-Ausfall), fallback auf
  // exakten Substring-Match wie vorher, damit Suche nie kaputt ist.
  const fallbackMatchInfo = (note) => {
    const lf = sections.find(l => l.id === note.sectionId);
    const inTitle   = note.title.toLowerCase().includes(lq);
    const inTags    = note.tags.some(t => t.toLowerCase().includes(lq));
    const inContent = note.content.toLowerCase().includes(lq);
    const inLf      = (lf?.short.toLowerCase().includes(lq) || lf?.label?.toLowerCase().includes(lq) || lf?.title?.toLowerCase().includes(lq));
    return { inTitle, inTags, inContent, inLf, matches: inTitle || inTags || inContent || inLf };
  };

  const matchInfoFromFuse = (matchKeys) => ({
    inTitle:   matchKeys.has('title'),
    inTags:    matchKeys.has('tags'),
    inContent: matchKeys.has('content'),
    inLf:      matchKeys.has('__lfShort') || matchKeys.has('__lfLabel'),
    matches:   matchKeys.size > 0,
  });

  const resultsWithInfo = (() => {
    if (lq.length === 0) return [];
    if (fuse) {
      return fuse.search(q.trim())
        .filter(r => !filterLf || r.item.sectionId === filterLf)
        .map(r => ({
          note: r.item,
          info: matchInfoFromFuse(new Set((r.matches || []).map(m => m.key))),
        }));
    }
    // Fallback ohne Fuse.js
    return notes
      .filter(n => !filterLf || n.sectionId === filterLf)
      .map(n => ({ note: n, info: fallbackMatchInfo(n) }))
      .filter(r => r.info.matches);
  })();

  const results = resultsWithInfo.map(r => r.note);
  const matchInfoByNoteId = new Map(resultsWithInfo.map(r => [r.note.id, r.info]));
  const getMatchInfo = (note) => matchInfoByNoteId.get(note.id) || { inTitle: false, inTags: false, inContent: false, inLf: false, matches: false };

  // Highlight matching text
  const highlight = (text, query) => {
    if (!query) return text;
    const idx = text.toLowerCase().indexOf(query.toLowerCase());
    if (idx < 0) return text;
    return (
      <>
        {text.slice(0, idx)}
        <mark style={{ background: 'var(--accent-dim)', color: 'var(--accent)', borderRadius: 2, padding: '0 1px' }}>
          {text.slice(idx, idx + query.length)}
        </mark>
        {text.slice(idx + query.length)}
      </>
    );
  };

  const lfsWithNotes = [...new Set(notes.map(n => n.sectionId))];

  return (
    <div style={{ padding: 32, maxWidth: 820, width: '100%', margin: '0 auto', boxSizing: 'border-box' }}>
      <Breadcrumb parts={['suche']} />
      <h2 style={{ margin: '16px 0 20px', fontSize: 22, fontWeight: 700, color: 'var(--text)' }}>Suche</h2>

      {/* Search input */}
      <div style={{ position: 'relative', marginBottom: 16 }}>
        <div style={{ position: 'absolute', left: 14, top: '50%', transform: 'translateY(-50%)', color: 'var(--text-muted)', pointerEvents: 'none' }}>
          <Icon name="search" size={16} />
        </div>
        <input
          ref={inputRef}
          value={q}
          onChange={e => setQ(e.target.value)}
          placeholder="Titel, Inhalt, Tags, Lernfeld suchen…"
          style={{
            width: '100%', boxSizing: 'border-box',
            padding: '12px 14px 12px 42px',
            background: 'var(--surface)', border: `1px solid ${q ? 'var(--accent)' : 'var(--border)'}`,
            borderRadius: 10, color: 'var(--text)', fontFamily: 'var(--font-sans)',
            fontSize: 15, outline: 'none', transition: 'border-color 0.15s',
            boxShadow: q ? '0 0 0 3px var(--accent-dim)' : 'none',
          }}
        />
        {q && (
          <button onClick={() => setQ('')}
            style={{ position: 'absolute', right: 12, top: '50%', transform: 'translateY(-50%)', background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', display: 'flex' }}>
            <Icon name="x" size={16} />
          </button>
        )}
      </div>

      {/* LF Filter chips */}
      <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginBottom: 20 }}>
        <button onClick={() => setFilterLf(null)}
          style={{ padding: '4px 12px', borderRadius: 20, border: '1px solid var(--border)', background: !filterLf ? 'var(--accent-dim)' : 'transparent', color: !filterLf ? 'var(--accent)' : 'var(--text-muted)', cursor: 'pointer', fontSize: 11, fontFamily: 'var(--font-mono)', transition: 'all 0.15s' }}>
          Alle LFs
        </button>
        {lfsWithNotes.map(sectionId => {
          const lf = sections.find(l => l.id === sectionId);
          if (!lf) return null;
          return (
            <button key={sectionId} onClick={() => setFilterLf(filterLf === sectionId ? null : sectionId)}
              style={{ padding: '4px 12px', borderRadius: 20, border: `1px solid ${lf.color}40`, background: filterLf === sectionId ? `${lf.color}20` : 'transparent', color: filterLf === sectionId ? lf.color : 'var(--text-muted)', cursor: 'pointer', fontSize: 11, fontFamily: 'var(--font-mono)', transition: 'all 0.15s' }}>
              {lf.short}
            </button>
          );
        })}
      </div>

      {/* Results */}
      {lq.length > 0 ? (
        <>
          <div style={{ fontSize: 12, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', marginBottom: 14, display: 'flex', alignItems: 'center', gap: 8 }}>
            <span style={{ color: results.length > 0 ? 'var(--accent)' : 'var(--text-dim)' }}>{results.length}</span>
            &nbsp;Ergebnis{results.length !== 1 ? 'se' : ''} für „{q}"
            {filterLf && <span>· gefiltert nach <TagBadge label={sections.find(l => l.id === filterLf)?.short || filterLf} color={sections.find(l => l.id === filterLf)?.color} /></span>}
          </div>

          {results.length === 0 ? (
            <EmptyState icon="search" title="Keine Ergebnisse"
              subtitle={`Nichts gefunden für „${q}"${filterLf ? ` in ${sections.find(l => l.id === filterLf)?.short}` : ''}`}
              action={filterLf ? <Btn variant="secondary" size="sm" onClick={() => setFilterLf(null)}>Filter entfernen</Btn> : null}
            />
          ) : (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
              {results.map(note => {
                const lf = sections.find(l => l.id === note.sectionId);
                const m = getMatchInfo(note);
                // Content snippet around the match
                const contentIdx = note.content.toLowerCase().indexOf(lq);
                const rawSnippet = contentIdx >= 0
                  ? note.content.slice(Math.max(0, contentIdx - 50), contentIdx + 120).replace(/[#*`\[\]>]/g, '')
                  : null;

                return (
                  <button key={note.id} onClick={() => setRoute({ page: 'note', noteId: note.id })}
                    style={{ display: 'flex', flexDirection: 'column', gap: 8, padding: '14px 16px', background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 10, cursor: 'pointer', textAlign: 'left', transition: 'all 0.15s' }}
                    onMouseEnter={e => e.currentTarget.style.borderColor = lf?.color || 'var(--accent)'}
                    onMouseLeave={e => e.currentTarget.style.borderColor = 'var(--border)'}
                  >
                    {/* Top row */}
                    <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
                      {lf && <span style={{ width: 7, height: 7, borderRadius: '50%', background: lf.color, flexShrink: 0 }} />}
                      <span style={{ fontWeight: 600, fontSize: 14, color: 'var(--text)' }}>
                        {m.inTitle ? highlight(note.title, q) : note.title}
                      </span>
                      {lf && <TagBadge label={lf.short} color={lf.color} />}
                      {/* Match type badges */}
                      {m.inTitle   && <span style={{ fontSize: 10, color: 'var(--accent)', fontFamily: 'var(--font-mono)', padding: '1px 5px', background: 'var(--accent-dim)', borderRadius: 4 }}>titel</span>}
                      {m.inTags    && <span style={{ fontSize: 10, color: '#34d399', fontFamily: 'var(--font-mono)', padding: '1px 5px', background: 'rgba(52,211,153,0.12)', borderRadius: 4 }}>tag</span>}
                      {m.inContent && !m.inTitle && <span style={{ fontSize: 10, color: '#60a5fa', fontFamily: 'var(--font-mono)', padding: '1px 5px', background: 'rgba(96,165,250,0.12)', borderRadius: 4 }}>inhalt</span>}
                      {m.inLf      && <span style={{ fontSize: 10, color: '#a78bfa', fontFamily: 'var(--font-mono)', padding: '1px 5px', background: 'rgba(167,139,250,0.12)', borderRadius: 4 }}>lernfeld</span>}
                    </div>

                    {/* Content snippet */}
                    {rawSnippet && !m.inTitle && (
                      <div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.6, fontFamily: 'var(--font-mono)', borderLeft: '2px solid var(--border)', paddingLeft: 10 }}>
                        …{highlight(rawSnippet.trim(), q)}…
                      </div>
                    )}

                    {/* Tags */}
                    <div style={{ display: 'flex', gap: 4, flexWrap: 'wrap' }}>
                      {note.tags.map(t => (
                        <span key={t} style={{
                          display: 'inline-flex', alignItems: 'center', padding: '2px 8px', borderRadius: 20,
                          fontSize: 11, fontFamily: 'var(--font-mono)',
                          background: t.toLowerCase().includes(lq) ? 'rgba(52,211,153,0.15)' : 'var(--surface-2)',
                          color: t.toLowerCase().includes(lq) ? '#34d399' : 'var(--text-muted)',
                          border: `1px solid ${t.toLowerCase().includes(lq) ? 'rgba(52,211,153,0.3)' : 'var(--border)'}`,
                        }}>
                          {t.toLowerCase().includes(lq) ? highlight(t, q) : t}
                        </span>
                      ))}
                    </div>
                  </button>
                );
              })}
            </div>
          )}
        </>
      ) : (
        <div style={{ color: 'var(--text-muted)', fontSize: 13, fontFamily: 'var(--font-mono)' }}>
          // Suchbegriff eingeben — Titel, Inhalt, Tags, Lernfelder werden durchsucht
        </div>
      )}
    </div>
  );
};

// ── CALENDAR VIEW ─────────────────────────────────────────────────────────────
const TYPE_COLORS = { exam: '#f87171', task: '#60a5fa', project: '#a78bfa', presentation: '#fbbf24' };
const WEEKDAYS = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'];
const ymd = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;

const CalendarView = ({
  deadlines, sections, onAdd, onDelete, userCan, view = 'list', onViewChange,
  reminderSubs = [], currentUserId, onAddReminder, onUpdateReminder, onDeleteReminder,
  defaultReminderLeadDays = 1, onExportIcs,
}) => {
  const [showAdd, setShowAdd] = useState(false);
  const [form, setForm] = useState({ title: '', sectionId: 'lf03', date: '', type: 'task', description: '', reminderEnabled: true, reminderLeadDays: defaultReminderLeadDays });
  const [cursor, setCursor] = useState(() => { const t = new Date(); return new Date(t.getFullYear(), t.getMonth(), 1); });
  const [selectedDay, setSelectedDay] = useState(null); // 'YYYY-MM-DD' oder null
  const [reminderPopover, setReminderPopover] = useState(null); // { deadlineId, leadDays }

  const sorted = [...deadlines].sort((a, b) => new Date(a.date) - new Date(b.date));
  const today = ymd(new Date());
  const canWrite = userCan ? userCan('deadlines:write') : true;
  const canDelete = userCan ? userCan('deadlines:delete') : true;

  // Map deadlineId → eigenes Sub-Objekt
  const subByDeadline = {};
  for (const s of reminderSubs) if (s.userId === currentUserId) subByDeadline[s.deadlineId] = s;

  const addDeadline = () => {
    if (!form.title || !form.date) return;
    const lead = form.reminderEnabled ? Math.max(1, Number(form.reminderLeadDays) || 1) : 0;
    onAdd({ ...form, reminderLeadDays: lead });
    setForm({ title: '', sectionId: 'lf03', date: '', type: 'task', description: '', reminderEnabled: true, reminderLeadDays: defaultReminderLeadDays });
    setShowAdd(false);
  };

  const openAddOnDay = (dateStr) => {
    if (!canWrite) return;
    setForm(f => ({ ...f, date: dateStr }));
    setShowAdd(true);
  };

  // Monatsgrid: 6 Wochen × 7 Tage, Montag-First
  const monthCells = (() => {
    const first = new Date(cursor.getFullYear(), cursor.getMonth(), 1);
    const offset = (first.getDay() + 6) % 7; // 0=Mo, ..., 6=So
    const start = new Date(first); start.setDate(first.getDate() - offset);
    const cells = [];
    for (let i = 0; i < 42; i++) {
      const d = new Date(start); d.setDate(start.getDate() + i);
      cells.push(d);
    }
    return cells;
  })();

  const byDate = (() => {
    const m = {};
    for (const d of deadlines) (m[d.date] = m[d.date] || []).push(d);
    return m;
  })();

  const monthLabel = cursor.toLocaleDateString('de', { month: 'long', year: 'numeric' });
  const stepMonth = (delta) => setCursor(c => new Date(c.getFullYear(), c.getMonth() + delta, 1));
  const goToday = () => { const t = new Date(); setCursor(new Date(t.getFullYear(), t.getMonth(), 1)); setSelectedDay(today); };

  const RemBtn = ({ deadlineId }) => {
    const sub = subByDeadline[deadlineId];
    const handleClick = (e) => {
      e.stopPropagation();
      setReminderPopover({ deadlineId, leadDays: sub?.leadDays ?? defaultReminderLeadDays });
    };
    if (!sub) {
      return (
        <button onClick={handleClick} title="Reminder einrichten"
          style={{ background: 'transparent', border: '1px solid var(--border)', borderRadius: 6, padding: '4px 7px', display: 'flex', alignItems: 'center', gap: 4, color: 'var(--text-muted)', cursor: 'pointer', fontSize: 11, fontFamily: 'var(--font-mono)' }}>
          <Icon name="bell" size={12} />
          <span>Erinnern</span>
        </button>
      );
    }
    return (
      <button onClick={handleClick} title="Reminder bearbeiten"
        style={{ background: 'var(--accent-dim)', border: '1px solid var(--accent)', borderRadius: 6, padding: '4px 7px', display: 'flex', alignItems: 'center', gap: 4, color: 'var(--accent)', cursor: 'pointer', fontSize: 11, fontFamily: 'var(--font-mono)' }}>
        <Icon name="bell" size={12} color="var(--accent)" />
        <span>{sub.leadDays}d</span>
      </button>
    );
  };

  const ViewToggle = (
    <div style={{ display: 'flex', gap: 4, background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8, padding: 3 }}>
      {[{ id: 'list', icon: 'list', label: 'Liste' }, { id: 'month', icon: 'calendar', label: 'Monat' }].map(t => (
        <button key={t.id} onClick={() => onViewChange && onViewChange(t.id)}
          style={{
            display: 'flex', alignItems: 'center', gap: 6, padding: '6px 12px',
            borderRadius: 6, border: 'none', cursor: 'pointer', fontSize: 12,
            fontFamily: 'var(--font-sans)', transition: 'all 0.15s',
            background: view === t.id ? 'var(--accent-dim)' : 'transparent',
            color: view === t.id ? 'var(--accent)' : 'var(--text-muted)',
          }}>
          <Icon name={t.icon} size={12} color={view === t.id ? 'var(--accent)' : 'var(--text-muted)'} />
          {t.label}
        </button>
      ))}
    </div>
  );

  return (
    <div style={{ padding: 32, maxWidth: 1100, width: '100%', margin: '0 auto', boxSizing: 'border-box' }}>
      <Breadcrumb parts={['kalender']} />
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', margin: '16px 0 24px', gap: 12, flexWrap: 'wrap' }}>
        <h2 style={{ margin: 0, fontSize: 22, fontWeight: 700, color: 'var(--text)' }}>Termine & Deadlines</h2>
        <div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
          {ViewToggle}
          {onExportIcs && deadlines.length > 0 && (
            <Btn variant="ghost" icon="upload" onClick={onExportIcs} title="Als .ics exportieren">.ics</Btn>
          )}
          {canWrite && <Btn variant="primary" icon="plus" onClick={() => setShowAdd(true)}>Hinzufügen</Btn>}
        </div>
      </div>

      {view === 'list' && (sorted.length === 0 ? (
        <EmptyState icon="calendar" title="Keine Termine" subtitle="Füge deinen ersten Termin hinzu." />
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {sorted.map(d => {
            const lf = sections.find(l => l.id === d.sectionId);
            const isPast = d.date < today;
            const isToday = d.date === today;
            return (
              <div key={d.id} style={{
                display: 'flex', alignItems: 'flex-start', gap: 14, padding: '14px 16px',
                background: 'var(--surface)', border: `1px solid ${isToday ? 'var(--accent)' : 'var(--border)'}`,
                borderRadius: 10, opacity: isPast ? 0.6 : 1,
              }}>
                <div style={{ textAlign: 'center', minWidth: 50, flexShrink: 0, padding: '4px 0' }}>
                  <div style={{ fontFamily: 'var(--font-mono)', fontSize: 20, fontWeight: 700, color: isToday ? 'var(--accent)' : 'var(--text)', lineHeight: 1 }}>
                    {d.date.slice(8)}
                  </div>
                  <div style={{ fontFamily: 'var(--font-mono)', fontSize: 10, color: 'var(--text-muted)', textTransform: 'uppercase' }}>
                    {new Date(d.date).toLocaleDateString('de', { month: 'short' })}
                  </div>
                </div>
                <div style={{ flex: 1 }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4, flexWrap: 'wrap' }}>
                    <span style={{ fontWeight: 600, fontSize: 14, color: 'var(--text)' }}>{d.title}</span>
                    <TypeBadge type={d.type} />
                    {lf && <TagBadge label={lf.short} color={lf.color} />}
                    {isToday && <TagBadge label="Heute!" color="var(--accent)" />}
                  </div>
                  {d.description && <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>{d.description}</div>}
                </div>
                <RemBtn deadlineId={d.id} />
                {canDelete && (
                  <button onClick={() => onDelete(d.id)}
                    style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-dim)', padding: 4, display: 'flex' }}
                    onMouseEnter={e => e.currentTarget.style.color = '#f87171'}
                    onMouseLeave={e => e.currentTarget.style.color = 'var(--text-dim)'}
                  >
                    <Icon name="x" size={14} />
                  </button>
                )}
              </div>
            );
          })}
        </div>
      ))}

      {view === 'month' && (
        <>
          {/* Monatskopf mit Navigation */}
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 12 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
              <button onClick={() => stepMonth(-1)} title="Vorheriger Monat"
                style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text)', cursor: 'pointer', padding: '6px 10px', display: 'flex' }}>
                <Icon name="arrowLeft" size={14} />
              </button>
              <button onClick={() => stepMonth(1)} title="Nächster Monat"
                style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text)', cursor: 'pointer', padding: '6px 10px', display: 'flex', transform: 'scaleX(-1)' }}>
                <Icon name="arrowLeft" size={14} />
              </button>
              <button onClick={goToday}
                style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text-muted)', cursor: 'pointer', padding: '6px 12px', fontSize: 12, fontFamily: 'var(--font-mono)' }}>
                Heute
              </button>
              <div style={{ marginLeft: 12, fontSize: 16, fontWeight: 600, color: 'var(--text)', textTransform: 'capitalize' }}>{monthLabel}</div>
            </div>
          </div>

          {/* Wochentag-Header */}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 6, marginBottom: 6 }}>
            {WEEKDAYS.map(w => (
              <div key={w} style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--text-muted)', textTransform: 'uppercase', textAlign: 'center', letterSpacing: '0.08em' }}>{w}</div>
            ))}
          </div>

          {/* Grid 6×7 */}
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 6 }}>
            {monthCells.map((d, i) => {
              const dateStr = ymd(d);
              const inMonth = d.getMonth() === cursor.getMonth();
              const isToday = dateStr === today;
              const isSelected = selectedDay === dateStr;
              const items = byDate[dateStr] || [];
              const visible = items.slice(0, 3);
              const overflow = items.length - visible.length;
              return (
                <div key={i}
                  onClick={() => setSelectedDay(s => s === dateStr ? null : dateStr)}
                  onDoubleClick={() => openAddOnDay(dateStr)}
                  style={{
                    minHeight: 96, padding: '6px 8px', borderRadius: 8,
                    background: 'var(--surface)',
                    border: `1px solid ${isSelected ? 'var(--accent)' : isToday ? 'var(--accent)' : 'var(--border)'}`,
                    boxShadow: isToday ? '0 0 0 1px var(--accent) inset' : 'none',
                    cursor: 'pointer', opacity: inMonth ? 1 : 0.4, transition: 'border-color 0.1s',
                    display: 'flex', flexDirection: 'column', gap: 4, overflow: 'hidden',
                  }}>
                  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                    <span style={{ fontSize: 12, fontFamily: 'var(--font-mono)', fontWeight: isToday ? 700 : 500, color: isToday ? 'var(--accent)' : 'var(--text)' }}>{d.getDate()}</span>
                    {items.length > 0 && <span style={{ fontSize: 9, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{items.length}</span>}
                  </div>
                  {visible.map(it => (
                    <div key={it.id} style={{
                      fontSize: 10, padding: '2px 5px', borderRadius: 3, lineHeight: 1.3,
                      background: `${TYPE_COLORS[it.type] || 'var(--accent)'}22`,
                      borderLeft: `2px solid ${TYPE_COLORS[it.type] || 'var(--accent)'}`,
                      color: 'var(--text)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
                    }} title={it.title}>{it.title}</div>
                  ))}
                  {overflow > 0 && (
                    <div style={{ fontSize: 10, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>+{overflow} weitere</div>
                  )}
                </div>
              );
            })}
          </div>

          {/* Detailliste für ausgewählten Tag */}
          {selectedDay && (
            <div style={{ marginTop: 16, padding: '14px 16px', background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 10 }}>
              <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
                <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>
                  {new Date(selectedDay).toLocaleDateString('de', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' })}
                </div>
                {canWrite && (
                  <Btn variant="secondary" icon="plus" onClick={() => openAddOnDay(selectedDay)}>Termin</Btn>
                )}
              </div>
              {(byDate[selectedDay] || []).length === 0 ? (
                <div style={{ fontSize: 12, color: 'var(--text-muted)' }}>Keine Termine an diesem Tag.</div>
              ) : (
                <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
                  {(byDate[selectedDay] || []).map(d => {
                    const lf = sections.find(l => l.id === d.sectionId);
                    return (
                      <div key={d.id} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 10px', background: 'var(--surface-2)', borderRadius: 6 }}>
                        <span style={{ width: 4, height: 24, borderRadius: 2, background: TYPE_COLORS[d.type] || 'var(--accent)', flexShrink: 0 }} />
                        <div style={{ flex: 1, minWidth: 0 }}>
                          <div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
                            <span style={{ fontSize: 13, fontWeight: 600, color: 'var(--text)' }}>{d.title}</span>
                            <TypeBadge type={d.type} />
                            {lf && <TagBadge label={lf.short} color={lf.color} />}
                          </div>
                          {d.description && <div style={{ fontSize: 11, color: 'var(--text-muted)', marginTop: 2 }}>{d.description}</div>}
                        </div>
                        <RemBtn deadlineId={d.id} />
                        {canDelete && (
                          <button onClick={() => onDelete(d.id)}
                            style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-dim)', padding: 4, display: 'flex' }}
                            onMouseEnter={e => e.currentTarget.style.color = '#f87171'}
                            onMouseLeave={e => e.currentTarget.style.color = 'var(--text-dim)'}>
                            <Icon name="x" size={13} />
                          </button>
                        )}
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          )}
        </>
      )}

      <Modal open={showAdd} onClose={() => setShowAdd(false)} title="Termin hinzufügen">
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          <Input value={form.title} onChange={e => setForm(f => ({ ...f, title: e.target.value }))} placeholder="Titel des Termins" />
          <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 10 }}>
            <Input type="date" value={form.date} onChange={e => setForm(f => ({ ...f, date: e.target.value }))} />
            <select value={form.type} onChange={e => setForm(f => ({ ...f, type: e.target.value }))}
              style={{ padding: '8px 12px', background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text)', fontSize: 13, outline: 'none' }}>
              <option value="exam">Klassenarbeit</option>
              <option value="task">Aufgabe</option>
              <option value="project">Projekt</option>
              <option value="presentation">Präsentation</option>
            </select>
          </div>
          <select value={form.sectionId} onChange={e => setForm(f => ({ ...f, sectionId: e.target.value }))}
            style={{ padding: '8px 12px', background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text)', fontSize: 13, outline: 'none' }}>
            {sections.map(lf => <option key={lf.id} value={lf.id}>{lf.short} — {lf.label.slice(0, 40)}</option>)}
          </select>
          <Input value={form.description} onChange={e => setForm(f => ({ ...f, description: e.target.value }))} placeholder="Beschreibung (optional)" />
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '8px 0', borderTop: '1px dashed var(--border)' }}>
            <label style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, color: 'var(--text-muted)', cursor: 'pointer', fontFamily: 'var(--font-mono)' }}>
              <input type="checkbox" checked={form.reminderEnabled}
                onChange={e => setForm(f => ({ ...f, reminderEnabled: e.target.checked }))} />
              <Icon name={form.reminderEnabled ? 'bell' : 'bellOff'} size={12} />
              Erinnerung per Mail
            </label>
            {form.reminderEnabled && (
              <>
                <input type="number" min="1" max="365" value={form.reminderLeadDays}
                  onChange={e => setForm(f => ({ ...f, reminderLeadDays: e.target.value }))}
                  style={{ width: 56, padding: '6px 8px', background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text)', fontSize: 12, fontFamily: 'var(--font-mono)', outline: 'none' }} />
                <span style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>Tag(e) vorher</span>
              </>
            )}
          </div>
          <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 4 }}>
            <Btn variant="secondary" onClick={() => setShowAdd(false)}>Abbrechen</Btn>
            <Btn variant="primary" onClick={addDeadline}>Speichern</Btn>
          </div>
        </div>
      </Modal>

      <Modal open={!!reminderPopover} onClose={() => setReminderPopover(null)} title="Erinnerung">
        {reminderPopover && (() => {
          const sub = subByDeadline[reminderPopover.deadlineId];
          const dl = deadlines.find(d => d.id === reminderPopover.deadlineId);
          return (
            <div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
              {dl && (
                <div style={{ fontSize: 13, color: 'var(--text-muted)' }}>
                  <strong style={{ color: 'var(--text)' }}>{dl.title}</strong>
                  <div style={{ fontFamily: 'var(--font-mono)', fontSize: 11, marginTop: 2 }}>{formatDateDe(dl.date)}</div>
                </div>
              )}
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <span style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>Erinnern</span>
                <input type="number" min="1" max="365" value={reminderPopover.leadDays}
                  onChange={e => setReminderPopover(p => ({ ...p, leadDays: e.target.value }))}
                  style={{ width: 64, padding: '6px 8px', background: 'var(--surface-2)', border: '1px solid var(--border)', borderRadius: 6, color: 'var(--text)', fontSize: 13, fontFamily: 'var(--font-mono)', outline: 'none' }} />
                <span style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>Tag(e) vorher</span>
              </div>
              <div style={{ display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 4 }}>
                {sub && (
                  <Btn variant="danger" onClick={async () => {
                    await onDeleteReminder(sub.id);
                    setReminderPopover(null);
                  }}>Abo entfernen</Btn>
                )}
                <Btn variant="secondary" onClick={() => setReminderPopover(null)}>Abbrechen</Btn>
                <Btn variant="primary" onClick={async () => {
                  const lead = Math.max(1, Math.min(365, Math.floor(Number(reminderPopover.leadDays) || 1)));
                  if (sub) await onUpdateReminder(sub.id, { leadDays: lead });
                  else await onAddReminder(reminderPopover.deadlineId, lead);
                  setReminderPopover(null);
                }}>{sub ? 'Speichern' : 'Erinnern'}</Btn>
              </div>
            </div>
          );
        })()}
      </Modal>
    </div>
  );
};

// ── LOGIN VIEW ────────────────────────────────────────────────────────────────
const LoginView = ({ onLogin, onRegister, onTotpVerify, registrationEnabled = true }) => {
  const [mode, setMode] = useState('login'); // login | register | forgot | reset | verifySuccess | verifyError | totp
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [password2, setPassword2] = useState('');
  const [showPw, setShowPw] = useState(false);
  const [error, setError] = useState('');
  const [info, setInfo] = useState('');
  const [loading, setLoading] = useState(false);
  const [name, setName] = useState('');
  const [resetToken, setResetToken] = useState('');
  // 2FA-Step state
  const [totpToken, setTotpToken] = useState('');
  const [totpCode, setTotpCode] = useState('');
  const [totpUseBackup, setTotpUseBackup] = useState(false);
  const [mfaMethods, setMfaMethods] = useState(['totp']); // Subset von ['totp', 'telegram', 'email']
  const [mfaMethod, setMfaMethod] = useState('totp');     // aktive Auswahl
  const [tgSending, setTgSending] = useState(false);
  const [tgSent, setTgSent] = useState(false);
  const [emailOtpSending, setEmailOtpSending] = useState(false);
  const [emailOtpSent, setEmailOtpSent] = useState(false);

  useEffect(() => {
    if (!registrationEnabled && mode === 'register') { setMode('login'); setError(''); }
  }, [registrationEnabled, mode]);

  // URL-Params bei Mount prüfen: ?verify=… oder ?reset=…
  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const verifyTok = params.get('verify');
    const resetTok = params.get('reset');
    if (verifyTok) {
      window.history.replaceState({}, '', window.location.pathname);
      (async () => {
        const res = await window.apiFetch('/api/auth/verify-email', { method: 'POST', body: { token: verifyTok } });
        setMode(res.ok ? 'verifySuccess' : 'verifyError');
        if (!res.ok) setError(res.data?.error || 'Verifikation fehlgeschlagen.');
      })();
    } else if (resetTok) {
      setResetToken(resetTok);
      setMode('reset');
      window.history.replaceState({}, '', window.location.pathname);
    }
  }, []);

  const isRegister = mode === 'register';

  const handleSubmit = async (e) => {
    e.preventDefault();
    setError(''); setInfo('');
    if (mode === 'login') {
      if (!email || !password) { setError('E-Mail und Passwort erforderlich.'); return; }
      setLoading(true);
      try {
        const result = await onLogin(email, password);
        if (!result.ok) { setError(result.error); return; }
        if (result.requireTotp) {
          const methods = Array.isArray(result.methods) && result.methods.length ? result.methods : ['totp'];
          setMfaMethods(methods);
          setMfaMethod(methods[0]);
          setTotpToken(result.totpToken);
          setTotpCode(''); setTotpUseBackup(false);
          setTgSent(false); setEmailOtpSent(false);
          setMode('totp');
        }
      } finally { setLoading(false); }
      return;
    }
    if (mode === 'totp') {
      if (!totpCode.trim()) { setError(totpUseBackup ? 'Backup-Code erforderlich.' : 'Code erforderlich.'); return; }
      setLoading(true);
      try {
        const body = totpUseBackup ? { totpToken, backupCode: totpCode } : { totpToken, code: totpCode };
        const result = await onTotpVerify(body);
        if (!result.ok) setError(result.error);
      } finally { setLoading(false); }
      return;
    }
    if (mode === 'register') {
      if (!name || !email || !password) { setError('Bitte alle Felder ausfüllen.'); return; }
      setLoading(true);
      try {
        const result = await onRegister(name, email, password);
        if (!result.ok) setError(result.error);
      } finally { setLoading(false); }
      return;
    }
    if (mode === 'forgot') {
      if (!email) { setError('E-Mail erforderlich.'); return; }
      setLoading(true);
      try {
        await window.apiFetch('/api/auth/forgot-password', { method: 'POST', body: { email } });
        setInfo('Falls die Email existiert, ist eine Mail mit Reset-Link unterwegs. Schau in dein Postfach (auch Spam).');
      } finally { setLoading(false); }
      return;
    }
    if (mode === 'reset') {
      if (!password || !password2) { setError('Beide Felder erforderlich.'); return; }
      if (password !== password2) { setError('Passwörter stimmen nicht überein.'); return; }
      if (password.length < 6) { setError('Passwort muss mindestens 6 Zeichen haben.'); return; }
      setLoading(true);
      try {
        const res = await window.apiFetch('/api/auth/reset-password', { method: 'POST', body: { token: resetToken, password } });
        if (!res.ok) { setError(res.data?.error || 'Reset fehlgeschlagen.'); return; }
        setInfo('Passwort gesetzt. Du kannst dich jetzt einloggen.');
        setPassword(''); setPassword2(''); setResetToken('');
        setTimeout(() => { setMode('login'); setInfo(''); }, 1500);
      } finally { setLoading(false); }
      return;
    }
  };

  return (
    <div style={{
      minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center',
      background: 'var(--bg)', position: 'relative', overflow: 'hidden',
    }}>
      {/* Animated background blobs */}
      <div style={{ position: 'absolute', inset: 0, overflow: 'hidden', pointerEvents: 'none' }}>
        <div style={{ position: 'absolute', width: 400, height: 400, borderRadius: '50%', background: 'radial-gradient(circle, rgba(155,92,246,0.12), transparent 70%)', top: '10%', left: '20%', animation: 'float1 8s ease-in-out infinite' }} />
        <div style={{ position: 'absolute', width: 300, height: 300, borderRadius: '50%', background: 'radial-gradient(circle, rgba(99,102,241,0.10), transparent 70%)', bottom: '20%', right: '15%', animation: 'float2 10s ease-in-out infinite' }} />
      </div>

      <div style={{ position: 'relative', zIndex: 1, width: '100%', maxWidth: 400, padding: '0 24px' }}>
        {/* Logo */}
        <div style={{ textAlign: 'center', marginBottom: 32 }}>
          <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
            <div style={{ width: 40, height: 40, borderRadius: 10, background: 'linear-gradient(135deg, var(--accent), #6d28d9)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
              <Icon name="zap" size={20} color="#fff" />
            </div>
            <span style={{ fontFamily: 'var(--font-mono)', fontSize: 22, fontWeight: 700, color: 'var(--text)', letterSpacing: '-0.03em' }}>Heronry</span>
          </div>
          <div style={{ fontSize: 13, color: 'var(--text-muted)' }}>Wissen sammeln, verknüpfen, wiederfinden.</div>
        </div>

        {/* Card */}
        <div style={{ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 14, padding: '28px 28px', boxShadow: '0 24px 60px rgba(0,0,0,0.3)' }}>
          {(mode === 'verifySuccess' || mode === 'verifyError') && (
            <div>
              <div style={{ fontSize: 18, fontWeight: 700, color: 'var(--text)', marginBottom: 12 }}>
                {mode === 'verifySuccess' ? 'Email bestätigt ✓' : 'Verifikation fehlgeschlagen'}
              </div>
              <div style={{ fontSize: 13, color: 'var(--text-muted)', lineHeight: 1.6, marginBottom: 16 }}>
                {mode === 'verifySuccess'
                  ? 'Deine Email-Adresse ist jetzt bestätigt. Du kannst dich anmelden.'
                  : (error || 'Der Link ist ungültig oder bereits abgelaufen.')}
              </div>
              <button onClick={() => { setMode('login'); setError(''); }} style={{ width: '100%', padding: '10px', borderRadius: 8, border: 'none', background: 'var(--accent)', color: '#fff', fontSize: 14, fontWeight: 600, cursor: 'pointer', fontFamily: 'var(--font-sans)' }}>
                Zum Login
              </button>
            </div>
          )}

          {(mode === 'login' || mode === 'register' || mode === 'forgot' || mode === 'reset' || mode === 'totp') && (
            <>
              <div style={{ fontSize: 18, fontWeight: 700, color: 'var(--text)', marginBottom: 20 }}>
                {mode === 'register' && 'Account erstellen'}
                {mode === 'login' && 'Anmelden'}
                {mode === 'forgot' && 'Passwort vergessen?'}
                {mode === 'reset' && 'Neues Passwort setzen'}
                {mode === 'totp' && 'Zwei-Faktor-Code'}
              </div>

              <form onSubmit={handleSubmit}>
                <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
                  {mode === 'register' && (
                    <div>
                      <label style={{ fontSize: 12, color: 'var(--text-muted)', display: 'block', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>Name</label>
                      <Input value={name} onChange={e => setName(e.target.value)} placeholder="Max Mustermann" icon="user" />
                    </div>
                  )}
                  {(mode === 'login' || mode === 'register' || mode === 'forgot') && (
                    <div>
                      <label style={{ fontSize: 12, color: 'var(--text-muted)', display: 'block', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>E-Mail</label>
                      <Input type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="dein@email.de" icon="user" />
                    </div>
                  )}
                  {(mode === 'login' || mode === 'register') && (
                    <div>
                      <label style={{ fontSize: 12, color: 'var(--text-muted)', display: 'block', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>Passwort</label>
                      <div style={{ position: 'relative' }}>
                        <Input type={showPw ? 'text' : 'password'} value={password} onChange={e => setPassword(e.target.value)} placeholder="••••••••" icon="lock" />
                        <button type="button" onClick={() => setShowPw(!showPw)}
                          style={{ position: 'absolute', right: 10, top: '50%', transform: 'translateY(-50%)', background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', display: 'flex' }}>
                          <Icon name={showPw ? 'eyeOff' : 'eye'} size={14} />
                        </button>
                      </div>
                    </div>
                  )}
                  {mode === 'reset' && (
                    <>
                      <div>
                        <label style={{ fontSize: 12, color: 'var(--text-muted)', display: 'block', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>Neues Passwort</label>
                        <Input type={showPw ? 'text' : 'password'} value={password} onChange={e => setPassword(e.target.value)} placeholder="••••••••" icon="lock" />
                      </div>
                      <div>
                        <label style={{ fontSize: 12, color: 'var(--text-muted)', display: 'block', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>Wiederholen</label>
                        <Input type={showPw ? 'text' : 'password'} value={password2} onChange={e => setPassword2(e.target.value)} placeholder="••••••••" icon="lock" />
                      </div>
                    </>
                  )}
                  {mode === 'totp' && (
                    <>
                      {mfaMethods.length > 1 && (
                        <div style={{ display: 'flex', gap: 4, padding: 4, background: 'var(--surface-2)', borderRadius: 8 }}>
                          {mfaMethods.map(m => {
                            const isActive = m === mfaMethod;
                            const label = m === 'totp' ? 'Authenticator' : (m === 'telegram' ? 'Telegram' : 'E-Mail');
                            return (
                              <button key={m} type="button"
                                onClick={() => { setMfaMethod(m); setTotpCode(''); setTotpUseBackup(false); setError(''); setTgSent(false); setEmailOtpSent(false); }}
                                style={{
                                  flex: 1, padding: '6px 10px', border: 'none', borderRadius: 6, cursor: 'pointer',
                                  background: isActive ? 'var(--accent)' : 'transparent',
                                  color: isActive ? '#fff' : 'var(--text-muted)',
                                  fontSize: 12, fontWeight: 600, fontFamily: 'var(--font-sans)',
                                }}>
                                {label}
                              </button>
                            );
                          })}
                        </div>
                      )}
                      <div style={{ fontSize: 12, color: 'var(--text-muted)', lineHeight: 1.6 }}>
                        {mfaMethod === 'telegram'
                          ? 'Klick "Code senden", öffne Telegram, kopiere den 6-stelligen Code hierher.'
                          : mfaMethod === 'email'
                            ? 'Klick "Code senden", öffne dein Postfach, kopiere den 6-stelligen Code hierher.'
                            : (totpUseBackup
                                ? 'Einen deiner gespeicherten Backup-Codes eingeben (Format xxxx-xxxx). Jeder Code ist nur einmal verwendbar.'
                                : 'Den 6-stelligen Code aus deiner Authenticator-App eingeben.')}
                      </div>
                      {mfaMethod === 'telegram' && (
                        <button type="button"
                          onClick={async () => {
                            setTgSending(true); setError('');
                            const res = await window.apiFetch('/api/auth/2fa/telegram/send-otp', { method: 'POST', body: { totpToken } });
                            setTgSending(false);
                            if (!res.ok) { setError(res.data?.error || 'OTP-Versand fehlgeschlagen.'); return; }
                            setTgSent(true);
                          }}
                          disabled={tgSending}
                          style={{
                            padding: '8px 12px', border: '1px solid var(--border)', borderRadius: 8,
                            background: 'var(--surface-2)', color: 'var(--text)', fontSize: 12, fontWeight: 600,
                            fontFamily: 'var(--font-sans)', cursor: tgSending ? 'wait' : 'pointer',
                            display: 'inline-flex', alignItems: 'center', gap: 6,
                          }}>
                          <Icon name="bell" size={14} />
                          {tgSending ? 'Sende…' : (tgSent ? 'Erneut senden' : 'Code per Telegram senden')}
                        </button>
                      )}
                      {mfaMethod === 'email' && (
                        <button type="button"
                          onClick={async () => {
                            setEmailOtpSending(true); setError('');
                            const res = await window.apiFetch('/api/auth/2fa/email/send-otp', { method: 'POST', body: { totpToken } });
                            setEmailOtpSending(false);
                            if (!res.ok) { setError(res.data?.error || 'OTP-Versand fehlgeschlagen.'); return; }
                            setEmailOtpSent(true);
                          }}
                          disabled={emailOtpSending}
                          style={{
                            padding: '8px 12px', border: '1px solid var(--border)', borderRadius: 8,
                            background: 'var(--surface-2)', color: 'var(--text)', fontSize: 12, fontWeight: 600,
                            fontFamily: 'var(--font-sans)', cursor: emailOtpSending ? 'wait' : 'pointer',
                            display: 'inline-flex', alignItems: 'center', gap: 6,
                          }}>
                          <Icon name="mail" size={14} />
                          {emailOtpSending ? 'Sende…' : (emailOtpSent ? 'Erneut senden' : 'Code per Mail senden')}
                        </button>
                      )}
                      <div>
                        <label style={{ fontSize: 12, color: 'var(--text-muted)', display: 'block', marginBottom: 6, fontFamily: 'var(--font-mono)' }}>
                          {totpUseBackup ? 'Backup-Code' : 'Code'}
                        </label>
                        <Input
                          value={totpCode}
                          onChange={e => setTotpCode(totpUseBackup ? e.target.value : e.target.value.replace(/[^\d]/g, '').slice(0, 6))}
                          placeholder={totpUseBackup ? 'xxxx-xxxx' : '123456'}
                          icon="lock"
                        />
                      </div>
                    </>
                  )}

                  {error && <div style={{ fontSize: 12, color: '#f87171', background: 'rgba(248,113,113,0.1)', padding: '8px 12px', borderRadius: 6, border: '1px solid rgba(248,113,113,0.2)' }}>{error}</div>}
                  {info && <div style={{ fontSize: 12, color: '#4ade80', background: 'rgba(74,222,128,0.1)', padding: '8px 12px', borderRadius: 6, border: '1px solid rgba(74,222,128,0.2)' }}>{info}</div>}

                  <button type="submit" style={{
                    width: '100%', padding: '10px', borderRadius: 8, border: 'none',
                    background: 'var(--accent)', color: '#fff', fontSize: 14, fontWeight: 600,
                    cursor: loading ? 'not-allowed' : 'pointer', opacity: loading ? 0.7 : 1,
                    fontFamily: 'var(--font-sans)', marginTop: 4, transition: 'filter 0.15s',
                  }}>
                    {loading ? '...' : (
                      mode === 'register' ? 'Account erstellen' :
                      mode === 'login' ? 'Anmelden' :
                      mode === 'forgot' ? 'Reset-Link senden' :
                      mode === 'totp' ? 'Bestätigen' :
                      'Passwort speichern'
                    )}
                  </button>
                </div>
              </form>

              <div style={{ marginTop: 16, fontSize: 12, color: 'var(--text-muted)', display: 'flex', flexDirection: 'column', gap: 6, alignItems: 'center' }}>
                {mode === 'login' && (
                  <>
                    <button onClick={() => { setMode('forgot'); setError(''); setInfo(''); setPassword(''); }} style={{ background: 'none', border: 'none', color: 'var(--accent)', cursor: 'pointer', fontSize: 12, textDecoration: 'underline' }}>
                      Passwort vergessen?
                    </button>
                    {registrationEnabled && (
                      <div>
                        Noch kein Account?{' '}
                        <button onClick={() => { setMode('register'); setError(''); setInfo(''); }} style={{ background: 'none', border: 'none', color: 'var(--accent)', cursor: 'pointer', fontSize: 12, textDecoration: 'underline' }}>
                          Registrieren
                        </button>
                      </div>
                    )}
                  </>
                )}
                {mode === 'register' && (
                  <div>
                    Bereits einen Account?{' '}
                    <button onClick={() => { setMode('login'); setError(''); setInfo(''); }} style={{ background: 'none', border: 'none', color: 'var(--accent)', cursor: 'pointer', fontSize: 12, textDecoration: 'underline' }}>
                      Anmelden
                    </button>
                  </div>
                )}
                {(mode === 'forgot' || mode === 'reset') && (
                  <button onClick={() => { setMode('login'); setError(''); setInfo(''); setPassword(''); setPassword2(''); }} style={{ background: 'none', border: 'none', color: 'var(--accent)', cursor: 'pointer', fontSize: 12, textDecoration: 'underline' }}>
                    Zurück zum Login
                  </button>
                )}
                {mode === 'totp' && (
                  <>
                    {mfaMethod === 'totp' && (
                      <button type="button" onClick={() => { setTotpUseBackup(b => !b); setTotpCode(''); setError(''); }}
                        style={{ background: 'none', border: 'none', color: 'var(--accent)', cursor: 'pointer', fontSize: 12, textDecoration: 'underline' }}>
                        {totpUseBackup ? 'Doch App-Code verwenden' : 'Backup-Code stattdessen verwenden'}
                      </button>
                    )}
                    <button type="button" onClick={() => { setMode('login'); setTotpToken(''); setTotpCode(''); setTotpUseBackup(false); setError(''); setPassword(''); setTgSent(false); setEmailOtpSent(false); }}
                      style={{ background: 'none', border: 'none', color: 'var(--text-muted)', cursor: 'pointer', fontSize: 12, textDecoration: 'underline' }}>
                      Abbrechen
                    </button>
                  </>
                )}
              </div>
            </>
          )}
        </div>
      </div>
    </div>
  );
};

// ── PATCH NOTES ───────────────────────────────────────────────────────────────
const PatchNotesView = ({ releases = [], focusVersion, currentUser, releaseNotifications, onToggleSubscription, onMarkSeen }) => {
  const [openVersion, setOpenVersion] = useState(focusVersion || (releases[0]?.version || null));

  // Beim Ansehen: lastSeenReleaseVersion auf das Top-Release aktualisieren
  useEffect(() => {
    if (releases.length && onMarkSeen) onMarkSeen(releases[0].version);
  }, [releases, onMarkSeen]);

  const BUCKET_ORDER = ['Added', 'Changed', 'Fixed', 'Removed', 'Security'];
  const BUCKET_COLORS = { Added: '#34d399', Changed: '#60a5fa', Fixed: '#fbbf24', Removed: '#f87171', Security: '#a78bfa' };

  // Inline-Markdown: **bold**, _italic_, `code`. Genug für Patch-Notes-Bullets.
  const renderInline = (text) => {
    const parts = [];
    const re = /\*\*([^*]+)\*\*|_([^_\n]+)_|`([^`]+)`/g;
    let lastIdx = 0, m, key = 0;
    while ((m = re.exec(text)) !== null) {
      if (m.index > lastIdx) parts.push(text.slice(lastIdx, m.index));
      if (m[1] !== undefined) parts.push(<strong key={key++}>{m[1]}</strong>);
      else if (m[2] !== undefined) parts.push(<em key={key++}>{m[2]}</em>);
      else if (m[3] !== undefined) parts.push(<code key={key++} style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4, fontFamily: 'var(--font-mono)', fontSize: '0.92em' }}>{m[3]}</code>);
      lastIdx = m.index + m[0].length;
    }
    if (lastIdx < text.length) parts.push(text.slice(lastIdx));
    return parts;
  };

  return (
    <div style={{ padding: 32, maxWidth: 880, width: '100%', margin: '0 auto', boxSizing: 'border-box' }}>
      <Breadcrumb parts={['patch-notes']} />
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', margin: '16px 0 24px', gap: 12, flexWrap: 'wrap' }}>
        <div>
          <h2 style={{ margin: 0, fontSize: 22, fontWeight: 700, color: 'var(--text)' }}>Patch Notes</h2>
          <p style={{ margin: '4px 0 0', fontSize: 13, color: 'var(--text-muted)' }}>Was sich in Heronry geändert hat — neueste Version oben.</p>
        </div>
        {currentUser && currentUser.emailVerified && (
          <label style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '8px 12px', background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 8, cursor: 'pointer', fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>
            <input type="checkbox" checked={!!releaseNotifications}
              onChange={e => onToggleSubscription && onToggleSubscription(e.target.checked)} />
            <Icon name={releaseNotifications ? 'bell' : 'bellOff'} size={12} />
            Per Mail benachrichtigen
          </label>
        )}
      </div>

      {releases.length === 0 ? (
        <EmptyState icon="tag" title="Noch keine Releases" subtitle="Sobald die erste Version veröffentlicht ist, erscheint sie hier." />
      ) : (
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          {releases.map((rel, idx) => {
            const isOpen = openVersion === rel.version;
            const typeBadgeColor = rel.type === 'feature' ? '#a78bfa' : '#60a5fa';
            const typeLabel = rel.type === 'feature' ? 'Feature' : 'Patch';
            return (
              <div key={rel.version} style={{
                background: 'var(--surface)', border: '1px solid var(--border)',
                borderRadius: 10, overflow: 'hidden',
                borderLeft: `4px solid ${typeBadgeColor}`,
              }}>
                <button onClick={() => setOpenVersion(isOpen ? null : rel.version)}
                  style={{
                    width: '100%', display: 'flex', alignItems: 'center', gap: 12,
                    padding: '14px 18px', background: 'transparent', border: 'none',
                    cursor: 'pointer', textAlign: 'left',
                  }}>
                  <Icon name={isOpen ? 'chevronDown' : 'chevronRight'} size={14} color="var(--text-muted)" />
                  <span style={{ fontFamily: 'var(--font-mono)', fontSize: 18, fontWeight: 700, color: 'var(--text)' }}>v{rel.version}</span>
                  <TagBadge label={typeLabel} color={typeBadgeColor} />
                  {idx === 0 && <TagBadge label="Aktuell" color="var(--accent)" />}
                  <span style={{ flex: 1 }} />
                  {rel.date && (
                    <span style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{formatDateDe(rel.date)}</span>
                  )}
                </button>
                {isOpen && (
                  <div style={{ padding: '4px 18px 20px 44px', display: 'flex', flexDirection: 'column', gap: 14 }}>
                    {(() => {
                      const buckets = rel.buckets || {};
                      const keys = BUCKET_ORDER.filter(k => buckets[k]?.length).concat(
                        Object.keys(buckets).filter(k => !BUCKET_ORDER.includes(k) && buckets[k]?.length)
                      );
                      if (!keys.length) {
                        return <div style={{ fontSize: 13, color: 'var(--text-muted)', fontStyle: 'italic' }}>Keine Details hinterlegt.</div>;
                      }
                      return keys.map(k => (
                        <div key={k}>
                          <div style={{
                            fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
                            color: BUCKET_COLORS[k] || 'var(--accent)',
                            textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 6,
                          }}>{k}</div>
                          <ul style={{ margin: 0, paddingLeft: 20, color: 'var(--text)', fontSize: 13, lineHeight: 1.6, display: 'flex', flexDirection: 'column', gap: 4 }}>
                            {buckets[k].map((item, i) => <li key={i}>{renderInline(item)}</li>)}
                          </ul>
                        </div>
                      ));
                    })()}
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
};

// ── WHAT'S NEW MODAL ──────────────────────────────────────────────────────────
const WhatsNewModal = ({ open, release, onAcknowledge, onSuppress, onClose }) => {
  if (!release) return null;
  const BUCKET_ORDER = ['Added', 'Changed', 'Fixed', 'Removed', 'Security'];
  const BUCKET_COLORS = { Added: '#34d399', Changed: '#60a5fa', Fixed: '#fbbf24', Removed: '#f87171', Security: '#a78bfa' };

  const renderInline = (text) => {
    const parts = [];
    const re = /\*\*([^*]+)\*\*|_([^_\n]+)_|`([^`]+)`/g;
    let lastIdx = 0, m, key = 0;
    while ((m = re.exec(text)) !== null) {
      if (m.index > lastIdx) parts.push(text.slice(lastIdx, m.index));
      if (m[1] !== undefined) parts.push(<strong key={key++}>{m[1]}</strong>);
      else if (m[2] !== undefined) parts.push(<em key={key++}>{m[2]}</em>);
      else if (m[3] !== undefined) parts.push(<code key={key++} style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4, fontFamily: 'var(--font-mono)', fontSize: '0.92em' }}>{m[3]}</code>);
      lastIdx = m.index + m[0].length;
    }
    if (lastIdx < text.length) parts.push(text.slice(lastIdx));
    return parts;
  };

  const buckets = release.buckets || {};
  const keys = BUCKET_ORDER.filter(k => buckets[k]?.length).concat(
    Object.keys(buckets).filter(k => !BUCKET_ORDER.includes(k) && buckets[k]?.length)
  );
  const typeBadgeColor = release.type === 'feature' ? '#a78bfa' : '#60a5fa';
  const typeLabel = release.type === 'feature' ? 'Feature-Release' : 'Patch';

  return (
    <Modal open={open} onClose={onClose} title="" width={560}>
      <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
        <div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
            <span style={{ fontSize: 11, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.08em' }}>Was ist neu</span>
          </div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
            <span style={{ fontFamily: 'var(--font-mono)', fontSize: 22, fontWeight: 700, color: 'var(--text)' }}>v{release.version}</span>
            <TagBadge label={typeLabel} color={typeBadgeColor} />
            {release.date && (
              <span style={{ fontSize: 12, color: 'var(--text-muted)', fontFamily: 'var(--font-mono)' }}>{formatDateDe(release.date)}</span>
            )}
          </div>
        </div>

        <div style={{ display: 'flex', flexDirection: 'column', gap: 14, maxHeight: '50vh', overflowY: 'auto', paddingRight: 4 }}>
          {keys.length === 0 ? (
            <div style={{ fontSize: 13, color: 'var(--text-muted)', fontStyle: 'italic' }}>Keine Details hinterlegt.</div>
          ) : keys.map(k => (
            <div key={k}>
              <div style={{
                fontFamily: 'var(--font-mono)', fontSize: 11, fontWeight: 600,
                color: BUCKET_COLORS[k] || 'var(--accent)',
                textTransform: 'uppercase', letterSpacing: '0.08em', marginBottom: 6,
              }}>{k}</div>
              <ul style={{ margin: 0, paddingLeft: 20, color: 'var(--text)', fontSize: 13, lineHeight: 1.55, display: 'flex', flexDirection: 'column', gap: 4 }}>
                {buckets[k].map((item, i) => <li key={i}>{renderInline(item)}</li>)}
              </ul>
            </div>
          ))}
        </div>

        <div style={{ display: 'flex', gap: 8, justifyContent: 'space-between', alignItems: 'center', paddingTop: 8, borderTop: '1px solid var(--border)', marginTop: 4 }}>
          <button onClick={onSuppress}
            style={{ background: 'none', border: 'none', cursor: 'pointer', color: 'var(--text-muted)', fontSize: 11, fontFamily: 'var(--font-mono)', textDecoration: 'underline', padding: 4 }}
            title="Diese Pop-ups deaktivieren — du findest das Toggle in den Einstellungen"
          >
            In Zukunft nicht mehr anzeigen
          </button>
          <Btn variant="primary" onClick={onAcknowledge}>Gelesen</Btn>
        </div>
      </div>
    </Modal>
  );
};

// ── ONBOARDING MODAL (Welcome-Tour nach Self-Registrierung) ───────────────────
// Spotlight-Tour: pro Slide wird ein UI-Element via data-tour-Attribut hervor-
// gehoben (per box-shadow: 0 0 0 9999px Trick), der Tooltip positioniert sich
// dynamisch neben dem Element. Wenn kein Target gesetzt ist → zentrierter Modus
// mit Default-Backdrop. Beide End-Wege (Skip + Fertig) rufen onComplete().
const OnboardingModal = ({ open, user, setRoute, onComplete }) => {
  const [step, setStep] = useState(0);
  const [targetRect, setTargetRect] = useState(null);

  const firstName = (user?.name || '').split(/\s+/)[0] || '';

  const slides = [
    {
      icon: 'zap',
      title: firstName ? `Willkommen, ${firstName}!` : 'Willkommen!',
      target: null,
      body: (
        <>
          <p style={{ margin: 0 }}>
            Schön, dass du dabei bist. Du bist gerade in <strong>Heronry</strong> gelandet —
            der Wissens-App auf der <strong>Haikara</strong>-Plattform: Notizen, Karteikarten,
            Termine, Chat und Mindmap, alles an einem Ort und auf allen Geräten synchron.
          </p>
          <p style={{ margin: '12px 0 0', color: 'var(--text-muted)', fontSize: 13 }}>
            Diese kurze Tour zeigt dir in 60 Sekunden, was hier wo liegt — und welche anderen
            Apps die Haikara-Plattform sonst noch hat. Du kannst jederzeit überspringen.
          </p>
        </>
      ),
    },
    {
      icon: 'book',
      title: 'Themenbereiche & Sektionen',
      target: 'sidebar-sections',
      placement: 'right',
      body: (
        <>
          <p style={{ margin: 0 }}>
            Deine Inhalte sind in <strong>Themenbereichen</strong> organisiert, jeder mit eigenen{' '}
            <strong>Sektionen</strong> — z.&nbsp;B. Lernfelder einer Ausbildung, Räume eines Homelabs
            oder Projekte eines Hobbies.
          </p>
          <p style={{ margin: '12px 0 0' }}>
            Klick auf eine Sektion, und du bekommst dort <strong>Notizen</strong>, <strong>Karteikarten</strong>{' '}
            und einen <strong>Live-Chat</strong> zum jeweiligen Thema.
          </p>
          <p style={{ margin: '12px 0 0', color: 'var(--text-muted)', fontSize: 13 }}>
            Notizen unterstützen Markdown, Tags und{' '}
            <code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4, fontFamily: 'var(--font-mono)', fontSize: '0.92em' }}>[[Verknüpfungen]]</code>{' '}
            — auch über Bereiche hinweg.
          </p>
        </>
      ),
    },
    {
      icon: 'calendar',
      title: 'Kalender — alle Termine an einem Ort',
      target: 'nav-calendar',
      placement: 'right',
      body: (
        <>
          <p style={{ margin: 0 }}>
            Der <strong>Kalender</strong> sammelt alle Abgaben, Prüfungen und Deadlines aus allen Sektionen
            in einer Übersicht — wahlweise als Liste oder Monatsansicht.
          </p>
          <p style={{ margin: '12px 0 0', color: 'var(--text-muted)', fontSize: 13 }}>
            Pro Termin kannst du E-Mail-Erinnerungen setzen, damit dich nichts überrascht.
          </p>
        </>
      ),
    },
    {
      icon: 'link',
      title: 'Graph — Wissen visualisiert',
      target: 'nav-graph',
      placement: 'right',
      body: (
        <>
          <p style={{ margin: 0 }}>
            Im <strong>Graph</strong> siehst du alle deine Notizen als Netzwerk: jede Notiz ist ein Knoten,
            jede <code style={{ background: 'var(--surface-2)', padding: '1px 5px', borderRadius: 4, fontFamily: 'var(--font-mono)', fontSize: '0.92em' }}>[[Verknüpfung]]</code> eine Linie.
          </p>
          <p style={{ margin: '12px 0 0', color: 'var(--text-muted)', fontSize: 13 }}>
            Gut, um Cluster zu erkennen, Lücken zu finden oder einfach nur zu staunen, wie sehr alles zusammenhängt.
          </p>
        </>
      ),
    },
    {
      icon: 'grid',
      title: 'Andere Apps auf Haikara',
      target: 'hub-back',
      placement: 'bottom',
      body: (
        <>
          <p style={{ margin: 0 }}>
            Heronry ist eine von mehreren Apps auf der Plattform. Über das <strong>Grid-Icon</strong>{' '}
            oben rechts (oder dein User-Menü) kommst du jederzeit zurück zum <strong>Haikara-Hub</strong>{' '}
            und von dort zu den anderen Tools:
          </p>
          <ul style={{ margin: '10px 0 0', paddingLeft: 18, fontSize: 13, lineHeight: 1.7 }}>
            <li><strong>Egret</strong> — IP-/Subnet-Calculator mit Bit-Visualizer</li>
            <li><strong>Plumage</strong> — Cheatsheets für Linux, Netzwerke, Git, Windows, …</li>
            <li><em>Lab Logbook</em> (in Entwicklung) — Doku für Übungsaufgaben & Configs</li>
          </ul>
          <p style={{ margin: '12px 0 0', color: 'var(--text-muted)', fontSize: 13 }}>
            Ein Login auf Haikara — du bist automatisch in allen Apps eingeloggt.
          </p>
        </>
      ),
    },
    {
      icon: 'lock',
      title: 'Sicherheit (empfohlen)',
      target: 'user-security',
      placement: 'top',
      body: (
        <>
          <p style={{ margin: 0 }}>
            Schütze deinen Account mit <strong>Zwei-Faktor-Authentifizierung</strong>. Drei Methoden
            stehen zur Wahl, du kannst sie auch parallel aktivieren:
          </p>
          <ul style={{ margin: '10px 0 0', paddingLeft: 18, fontSize: 13, lineHeight: 1.7 }}>
            <li><strong>Authenticator-App</strong> (Google Authenticator, Aegis, 1Password, …)</li>
            <li><strong>Telegram</strong> — Code direkt vom Bot in den Chat</li>
            <li><strong>E-Mail</strong> — 6-stelliger Code an deine registrierte Adresse</li>
          </ul>
          <p style={{ margin: '12px 0 0', color: 'var(--text-muted)', fontSize: 13 }}>
            Manche administrativen Funktionen sind nur mit aktivierter 2FA verfügbar.
          </p>
        </>
      ),
      cta: { label: '2FA jetzt einrichten', icon: 'lock', route: { page: 'settings', tab: 'security' } },
    },
  ];

  const slide = slides[step];
  const isLast = step === slides.length - 1;

  // Target-Rect bei Slide-Wechsel + Resize/Scroll neu berechnen.
  useEffect(() => {
    if (!open) return;
    const updateRect = () => {
      if (!slide.target) { setTargetRect(null); return; }
      const el = document.querySelector(`[data-tour="${slide.target}"]`);
      if (el) {
        const r = el.getBoundingClientRect();
        // Neue Object-Identity nur, wenn sich was ändert (verhindert Render-Loops bei resize-spam)
        setTargetRect(prev => (prev && prev.top === r.top && prev.left === r.left && prev.width === r.width && prev.height === r.height)
          ? prev
          : { top: r.top, left: r.left, width: r.width, height: r.height, right: r.right, bottom: r.bottom });
      } else {
        setTargetRect(null);
      }
    };
    // Kleines Delay beim Step-Wechsel, falls das Target-Element gerade umbaut/animiert.
    const t = setTimeout(updateRect, 50);
    window.addEventListener('resize', updateRect);
    window.addEventListener('scroll', updateRect, true);
    return () => {
      clearTimeout(t);
      window.removeEventListener('resize', updateRect);
      window.removeEventListener('scroll', updateRect, true);
    };
  }, [open, step, slide.target]);

  const finish = () => { onComplete?.(); };
  const next = () => { isLast ? finish() : setStep(step + 1); };
  const back = () => setStep(Math.max(0, step - 1));
  const skip = () => finish();
  const openCta = () => {
    onComplete?.();
    if (slide.cta?.route) setRoute?.(slide.cta.route);
  };

  if (!open) return null;

  // ── Modal-Position ────────────────────────────────────────────────────────
  // Wenn kein target → zentriert.
  // Mit target → an der präferierten Seite, mit Fallback ins Viewport.
  const PAD = 8;
  const TOOLTIP_W = 460;
  const TOOLTIP_GAP = 16;
  const computeStyle = () => {
    if (!targetRect) {
      return { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' };
    }
    const vw = window.innerWidth;
    const vh = window.innerHeight;
    let s = {};
    if (slide.placement === 'right') {
      const left = targetRect.right + TOOLTIP_GAP;
      // Falls rechts kein Platz: stattdessen unter dem Element
      if (left + TOOLTIP_W > vw - 16) {
        s = { top: targetRect.bottom + TOOLTIP_GAP, left: Math.max(16, targetRect.left) };
      } else {
        s = { top: Math.max(16, targetRect.top - 8), left };
      }
    } else if (slide.placement === 'left') {
      s = { top: Math.max(16, targetRect.top - 8), right: vw - targetRect.left + TOOLTIP_GAP };
    } else if (slide.placement === 'top') {
      s = { bottom: vh - targetRect.top + TOOLTIP_GAP, left: Math.max(16, Math.min(targetRect.left, vw - TOOLTIP_W - 16)) };
    } else { // bottom / fallback
      s = { top: targetRect.bottom + TOOLTIP_GAP, left: Math.max(16, Math.min(targetRect.left, vw - TOOLTIP_W - 16)) };
    }
    // Vertikal clampen
    if (s.top !== undefined && typeof s.top === 'number' && s.top + 360 > vh - 16) {
      s.top = Math.max(16, vh - 360 - 16);
    }
    return s;
  };
  const modalStyle = computeStyle();

  return (
    <>
      {/* Pointer-events catcher: blockt Klicks auf die Hintergrund-UI während der Tour. */}
      <div style={{
        position: 'fixed', inset: 0, zIndex: 1000,
        background: targetRect ? 'transparent' : 'rgba(0,0,0,0.65)',
        // Kein onClick → User schließt nur über Skip/Fertig, nicht durch Klick außerhalb.
      }} />

      {/* Spotlight um das Target. box-shadow simuliert das Loch im Backdrop. */}
      {targetRect && (
        <div style={{
          position: 'fixed',
          top: targetRect.top - PAD,
          left: targetRect.left - PAD,
          width: targetRect.width + 2 * PAD,
          height: targetRect.height + 2 * PAD,
          border: '2px solid var(--accent)',
          borderRadius: 10,
          boxShadow: '0 0 0 9999px rgba(0,0,0,0.65), 0 0 22px var(--accent)',
          pointerEvents: 'none',
          zIndex: 1001,
          transition: 'top 0.3s ease, left 0.3s ease, width 0.3s ease, height 0.3s ease',
        }} />
      )}

      {/* Tooltip-/Modal-Card */}
      <div style={{
        position: 'fixed', zIndex: 1002,
        ...modalStyle,
        background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 12,
        width: TOOLTIP_W, maxWidth: 'calc(100vw - 32px)',
        boxShadow: '0 24px 60px rgba(0,0,0,0.5)',
        padding: 22,
        display: 'flex', flexDirection: 'column', gap: 16,
      }}>
        {/* Step-Anzeige */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 6, justifyContent: 'center' }}>
          {slides.map((_, i) => (
            <span key={i} style={{
              width: i === step ? 24 : 8, height: 8, borderRadius: 4,
              background: i === step ? 'var(--accent)' : 'var(--border)',
              transition: 'all 200ms ease',
            }} />
          ))}
        </div>

        {/* Slide-Body */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <div style={{
              width: 44, height: 44, borderRadius: 11, background: 'var(--surface-2)',
              display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
            }}>
              <Icon name={slide.icon} size={22} color="var(--accent)" />
            </div>
            <h3 style={{ margin: 0, fontSize: 18, fontWeight: 600, color: 'var(--text)' }}>
              {slide.title}
            </h3>
          </div>
          <div style={{ fontSize: 13.5, lineHeight: 1.6, color: 'var(--text)' }}>
            {slide.body}
          </div>
          {slide.cta && (
            <div>
              <Btn variant="secondary" icon={slide.cta.icon} onClick={openCta}>
                {slide.cta.label}
              </Btn>
            </div>
          )}
        </div>

        {/* Footer */}
        <div style={{
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          paddingTop: 12, borderTop: '1px solid var(--border)', gap: 8,
        }}>
          <button
            onClick={skip}
            style={{
              background: 'none', border: 'none', cursor: 'pointer',
              color: 'var(--text-muted)', fontSize: 12,
              fontFamily: 'var(--font-mono)', textDecoration: 'underline', padding: 4,
            }}
          >
            Überspringen
          </button>
          <div style={{ display: 'flex', gap: 8 }}>
            {step > 0 && (
              <Btn variant="ghost" icon="arrowLeft" onClick={back}>Zurück</Btn>
            )}
            <Btn variant="primary" onClick={next}>
              {isLast ? 'Fertig' : 'Weiter'}
            </Btn>
          </div>
        </div>
      </div>
    </>
  );
};


// ─── Maintenance ────────────────────────────────────────────────────────────
// Drei Komponenten, gespeist aus `publicSettings`:
//   • MaintenancePreBanner — blau, zeigt geplantes Wartungsfenster (vor Beginn)
//   • MaintenanceBypassBanner — orange, zeigt aktiven Wartungsmodus für Bypass-User
//   • MaintenanceView — Vollbild-Page für nicht-Bypass-User während aktiver Wartung
// publicSettings-Keys: maintenanceApps (string[]), maintenanceScheduledAt (ISO),
// maintenanceScheduledDuration (Minuten), maintenanceMessage.
// Heronry ist gated wenn 'hub' (globaler Lockdown) oder 'heronry' im Array.

const isHeronryGated = (publicSettings) => {
  const apps = publicSettings?.maintenanceApps;
  if (!Array.isArray(apps)) return false;
  return apps.includes('hub') || apps.includes('heronry');
};

const fmtScheduleLine = (iso, durationMin) => {
  if (!iso) return '';
  const d = new Date(iso);
  if (isNaN(d.getTime())) return '';
  const date = d.toLocaleString('de-DE', { weekday: 'short', day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' });
  if (durationMin && durationMin > 0) return `${date} Uhr (≈ ${durationMin} Min)`;
  return `${date} Uhr`;
};

const MaintenancePreBanner = ({ publicSettings }) => {
  const at = publicSettings?.maintenanceScheduledAt;
  const dur = Number(publicSettings?.maintenanceScheduledDuration) || 0;
  const mode = isHeronryGated(publicSettings);
  const [dismissed, setDismissed] = React.useState(() => {
    if (!at) return false;
    return localStorage.getItem('hk_maint_dismiss') === at;
  });
  React.useEffect(() => {
    setDismissed(at ? localStorage.getItem('hk_maint_dismiss') === at : false);
  }, [at]);
  if (!at || mode) return null;                  // kein Termin oder schon aktiv
  const ms = new Date(at).getTime() - Date.now();
  if (!isFinite(ms) || ms <= 0) return null;     // Termin liegt in der Vergangenheit
  if (dismissed) return null;
  return (
    <div style={{
      flexShrink: 0, background: 'rgba(59,130,246,0.12)',
      borderBottom: '1px solid rgba(59,130,246,0.3)',
      color: '#60a5fa', padding: '10px 24px',
      display: 'flex', alignItems: 'center', gap: 12, fontSize: 13,
    }}>
      <span style={{ fontSize: 16 }}>🔧</span>
      <div style={{ flex: 1 }}>
        <strong>Geplante Wartung:</strong> {fmtScheduleLine(at, dur)}
        {publicSettings.maintenanceMessage && <span style={{ marginLeft: 8, color: 'var(--text-muted)' }}>· {publicSettings.maintenanceMessage}</span>}
      </div>
      <button onClick={() => { localStorage.setItem('hk_maint_dismiss', at); setDismissed(true); }}
        style={{
          background: 'rgba(59,130,246,0.18)', border: '1px solid rgba(59,130,246,0.4)',
          color: '#60a5fa', borderRadius: 6, padding: '4px 10px',
          fontSize: 12, fontFamily: 'var(--font-mono)', cursor: 'pointer',
        }}>verstanden</button>
    </div>
  );
};

const MaintenanceBypassBanner = ({ publicSettings }) => {
  if (!isHeronryGated(publicSettings)) return null;
  return (
    <div style={{
      flexShrink: 0, background: 'rgba(251,146,60,0.14)',
      borderBottom: '1px solid rgba(251,146,60,0.35)',
      color: '#fb923c', padding: '10px 24px',
      display: 'flex', alignItems: 'center', gap: 12, fontSize: 13,
    }}>
      <span style={{ fontSize: 16 }}>🔧</span>
      <div style={{ flex: 1 }}>
        <strong>Wartungsmodus aktiv.</strong> Du hast Bypass-Zugang — andere User sehen die Wartungs-Seite.
        {publicSettings.maintenanceMessage && <span style={{ marginLeft: 8, color: 'var(--text-muted)' }}>· {publicSettings.maintenanceMessage}</span>}
      </div>
    </div>
  );
};

const MaintenanceView = ({ publicSettings, onLoginClick }) => {
  const at = publicSettings?.maintenanceScheduledAt;
  const dur = Number(publicSettings?.maintenanceScheduledDuration) || 0;
  let until = '';
  if (at && dur) {
    const end = new Date(new Date(at).getTime() + dur * 60_000);
    if (!isNaN(end.getTime())) {
      until = end.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
    }
  }
  const message = publicSettings?.maintenanceMessage || 'Wir frischen kurz auf — gleich wieder da.';
  return (
    <div style={{
      minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center',
      background: 'var(--bg)', color: 'var(--text)', fontFamily: 'var(--font-sans)',
      padding: 24,
    }}>
      <div style={{
        maxWidth: 480, width: '100%', textAlign: 'center',
        background: 'var(--surface)', border: '1px solid var(--border)',
        borderRadius: 16, padding: '40px 32px',
      }}>
        <div style={{
          width: 64, height: 64, borderRadius: 16, margin: '0 auto 24px',
          background: 'linear-gradient(135deg, var(--accent), var(--accent-2, var(--accent)))',
          display: 'flex', alignItems: 'center', justifyContent: 'center',
          fontSize: 32, color: '#fff',
        }}>🔧</div>
        <h1 style={{ fontFamily: 'var(--font-mono)', fontSize: 24, margin: '0 0 8px', letterSpacing: '-0.02em' }}>
          Wartungsmodus
        </h1>
        <p style={{ color: 'var(--text-muted)', fontSize: 14, lineHeight: 1.6, margin: '0 0 20px' }}>
          {message}
        </p>
        {until && (
          <div style={{
            display: 'inline-block', padding: '8px 16px', borderRadius: 8,
            background: 'var(--surface-2)', border: '1px solid var(--border)',
            fontFamily: 'var(--font-mono)', fontSize: 13, color: 'var(--text)', marginBottom: 24,
          }}>
            Voraussichtlich fertig um <strong>{until} Uhr</strong>
          </div>
        )}
        <div style={{ marginTop: 8 }}>
          <button onClick={onLoginClick} style={{
            background: 'transparent', border: '1px solid var(--border)',
            color: 'var(--text-muted)', borderRadius: 8, padding: '8px 16px',
            fontFamily: 'var(--font-mono)', fontSize: 12, cursor: 'pointer',
          }}>
            Login als Entwickler →
          </button>
        </div>
        <div style={{ marginTop: 32, fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--text-dim)' }}>
          haikara · heronry
        </div>
      </div>
    </div>
  );
};

// SecurityView, SettingsView, SettingsCard, AppearanceTab, NotificationsTab leben jetzt in settings.jsx.
Object.assign(window, { Dashboard, SectionView, NoteView, MarkdownEditor, FlashcardsView, FlashcardsManageView, SearchView, CalendarView, LoginView, PatchNotesView, WhatsNewModal, OnboardingModal, MaintenanceView, MaintenancePreBanner, MaintenanceBypassBanner, isHeronryGated });
