import { useEffect, useRef, useState } from 'react';
import ToastEditor from '@toast-ui/editor';
import '@toast-ui/editor/dist/toastui-editor.css';
import { supabase } from './supabase.js';
import { api } from './api.js';
import {
  Field, blankForm, formFromFrontmatter, frontmatterFromForm, targetPath,
  isShort, isBody, hexOf, DEFAULT_PALETTE,
} from './fields.jsx';

export default function App() {
  const [session, setSession] = useState(null);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    supabase.auth.getSession().then(({ data }) => { setSession(data.session); setLoading(false); });
    const { data: sub } = supabase.auth.onAuthStateChange((_e, s) => setSession(s));
    return () => sub.subscription.unsubscribe();
  }, []);
  if (loading) return <div className="center muted">…</div>;
  if (!session) return <Login />;
  return <Dashboard email={session.user.email} />;
}

function Login() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [err, setErr] = useState(null);
  async function submit(e) {
    e.preventDefault(); setErr(null);
    const { error } = await supabase.auth.signInWithPassword({ email, password });
    if (error) setErr(error.message);
  }
  return (
    <div className="center">
      <form className="login" onSubmit={submit}>
        <div className="login-brand">OPENBUREAU</div>
        <div className="login-sub">Redaktion</div>
        <input type="email" placeholder="E-Mail" value={email} onChange={(e) => setEmail(e.target.value)} autoFocus />
        <input type="password" placeholder="Passwort" value={password} onChange={(e) => setPassword(e.target.value)} />
        <button type="submit">Anmelden</button>
        {err && <p className="err">{err}</p>}
      </form>
    </div>
  );
}

function Dashboard({ email }) {
  const [entries, setEntries] = useState([]);
  const [collections, setCollections] = useState(null);
  const [plugins, setPlugins] = useState([]);
  const [site, setSite] = useState('');
  const [current, setCurrent] = useState(null);
  const [query, setQuery] = useState('');
  const [view, setView] = useState('content');
  const [me, setMe] = useState(null);
  const [msg, setMsg] = useState(null);

  async function refresh() {
    try { setEntries(await api.list()); }
    catch (e) { setMsg({ type: 'err', text: e.message }); }
  }
  useEffect(() => {
    refresh();
    api.getMe().then(setMe).catch(() => {});
    api.schema().then((sc) => {
      setCollections((sc.collections || []).slice().sort((a, b) => (a.order ?? 99) - (b.order ?? 99)));
      setPlugins(sc.plugins || []); setSite(sc.site || '');
    }).catch(() => setCollections([]));
  }, []);
  useEffect(() => { if (!msg) return; const t = setTimeout(() => setMsg(null), 4000); return () => clearTimeout(t); }, [msg]);

  const cols = collections || [];
  const hasDialog = plugins.includes('dialog');
  const collectionOf = (kind) => cols.find((c) => c.kind === kind) || cols.find((c) => c.fallback) || cols[0] || null;

  async function open(entry) {
    const col = collectionOf(entry.kind);
    try {
      const r = await api.read(entry.path);
      const bodyField = (col?.fields || []).find(isBody);
      setCurrent({
        isNew: false, kind: entry.kind, path: entry.path,
        ...formFromFrontmatter(r.frontmatter || {}, col || { fields: [] }),
        ...(bodyField ? { [bodyField.name]: r.body || '' } : {}),
      });
    } catch (err) { setMsg({ type: 'err', text: err.message }); }
  }
  function openNew() {
    const col = cols[0];
    if (col) setCurrent({ isNew: true, kind: col.kind, path: '', ...blankForm(col) });
  }

  const q = query.trim().toLowerCase();
  const filtered = q ? entries.filter((e) => (e.title || '').toLowerCase().includes(q) || (e.section || '').includes(q)) : entries;
  const fallbackKind = (cols.find((c) => c.fallback) || cols[0] || {}).kind;
  const groups = {};
  for (const c of cols) groups[c.kind] = [];
  for (const e of filtered) { const k = groups[e.kind] ? e.kind : fallbackKind; if (groups[k]) groups[k].push(e); }

  return (
    <div className="app">
      <header className="topbar">
        <span className="logo">{(site || 'cms').toUpperCase()}</span>
        <span className="logo-sub">Redaktion</span>
        <nav className="nav">
          {me?.isAdmin && <button className={view === 'overview' ? 'active' : ''} onClick={() => setView('overview')}>Übersicht</button>}
          <button className={view === 'content' ? 'active' : ''} onClick={() => setView('content')}>Inhalte</button>
          <button className={view === 'profile' ? 'active' : ''} onClick={() => setView('profile')}>Profil</button>
          {me?.canModerate && hasDialog && <button className={view === 'moderation' ? 'active' : ''} onClick={() => setView('moderation')}>Moderation</button>}
          {me?.isAdmin && hasDialog && <button className={view === 'forums' ? 'active' : ''} onClick={() => setView('forums')}>Foren</button>}
          {me?.isAdmin && <button className={view === 'users' ? 'active' : ''} onClick={() => setView('users')}>Autor:innen</button>}
          {me?.isAdmin && <button className={view === 'system' ? 'active' : ''} onClick={() => setView('system')}>System</button>}
        </nav>
        <span className="spacer" />
        <span className="who">{email}</span>
        <button className="ghost" onClick={() => supabase.auth.signOut()}>Abmelden</button>
      </header>

      <div className="body">
        {view === 'overview' ? (
          <Overview onMsg={setMsg} go={setView} />
        ) : view === 'profile' ? (
          <Profile onMsg={setMsg} />
        ) : view === 'users' ? (
          <Users onMsg={setMsg} currentEmail={me?.email} />
        ) : view === 'forums' ? (
          <Forums onMsg={setMsg} />
        ) : view === 'moderation' ? (
          <Moderation onMsg={setMsg} />
        ) : view === 'system' ? (
          <System onMsg={setMsg} />
        ) : (
          <>
            <aside>
              <button className="new" onClick={openNew} disabled={!cols.length}>＋ Neu</button>
              <div className="search"><span>⌕</span><input placeholder="Suchen…" value={query} onChange={(e) => setQuery(e.target.value)} /></div>
              {cols.map((c) => groups[c.kind]?.length > 0 && (
                <div className="group" key={c.kind}>
                  <div className="group-title">{c.label} <span>{groups[c.kind].length}</span></div>
                  <ul className="list">
                    {groups[c.kind].map((e) => (
                      <li key={e.path} className={current?.path === e.path ? 'active' : ''} onClick={() => open(e)}>
                        <span className="dot" style={{ background: e.color ? hexOf(DEFAULT_PALETTE, e.color) : 'var(--line)' }} />
                        <span className="t">
                          <span className="t-title">{e.title}</span>
                          <span className="t-meta">{[e.section, e.date].filter(Boolean).join(' · ')}</span>
                        </span>
                        {e.draft && <span className="draft-tag">Entwurf</span>}
                      </li>
                    ))}
                  </ul>
                </div>
              ))}
            </aside>
            <main>
              {current
                ? <Editor key={(current.path || 'new') + ':' + current.kind} initial={current} collections={cols}
                    onSaved={(loaded) => { setCurrent(loaded); refresh(); }} onMsg={setMsg} />
                : <div className="empty"><p>Wähle links einen Eintrag — oder leg einen neuen an.</p></div>}
            </main>
          </>
        )}
      </div>

      {msg && <div className={`toast ${msg.type}`} onClick={() => setMsg(null)}>{msg.text}</div>}
    </div>
  );
}

function Editor({ initial, collections, onSaved, onMsg }) {
  const [f, setF] = useState(initial);
  const [previewUrl, setPreviewUrl] = useState(null);
  const [showPreview, setShowPreview] = useState(false);
  const [pw, setPw] = useState(44);
  const [busy, setBusy] = useState(false);
  const editorRef = useRef(null);
  const dragging = useRef(false);

  const cols = collections || [];
  const collection = cols.find((c) => c.kind === f.kind) || cols[0] || { fields: [] };
  const fields = collection.fields || [];
  const titleField = fields.find((x) => x.name === 'title') || fields.find((x) => x.type === 'string' && x.required) || null;
  const bodyField = fields.find(isBody);
  const draftField = fields.find((x) => x.type === 'bool' && x.name === 'draft');
  const shortFields = fields.filter((x) => x !== titleField && !isBody(x) && isShort(x));
  const longFields = fields.filter((x) => x !== titleField && !isBody(x) && !isShort(x));
  const isDraft = draftField ? !!f[draftField.name] : false;
  const hasSlugField = fields.some((x) => x.name === 'slug');

  const setField = (name, val) => setF((p) => ({ ...p, [name]: val }));
  const upload = async (file) => (await api.upload(file)).url;
  function changeKind(kind) {
    const col = cols.find((c) => c.kind === kind);
    if (col) setF({ isNew: true, kind, path: '', ...blankForm(col) });
  }

  // Ziehbarer Trenner Editor ↔ Vorschau.
  useEffect(() => {
    function move(e) {
      if (!dragging.current || !editorRef.current) return;
      const r = editorRef.current.getBoundingClientRect();
      setPw(Math.min(70, Math.max(25, ((r.right - e.clientX) / r.width) * 100)));
    }
    function up() { dragging.current = false; document.body.style.cursor = ''; document.body.style.userSelect = ''; }
    window.addEventListener('mousemove', move);
    window.addEventListener('mouseup', up);
    return () => { window.removeEventListener('mousemove', move); window.removeEventListener('mouseup', up); };
  }, []);
  function startDrag(e) { dragging.current = true; document.body.style.cursor = 'col-resize'; document.body.style.userSelect = 'none'; e.preventDefault(); }

  async function save(overrides = {}) {
    const data = { ...f, ...overrides };
    const path = data.isNew ? targetPath(data, collection) : data.path;
    if (!path) { onMsg({ type: 'err', text: 'Bitte einen Slug (und ggf. Pflichtsegmente wie Rubrik) angeben.' }); return null; }
    if (titleField && !((data[titleField.name] || '').trim())) { onMsg({ type: 'err', text: 'Titel fehlt.' }); return null; }
    setBusy(true);
    try {
      const fm = frontmatterFromForm(data, collection);
      const body = bodyField ? (data[bodyField.name] || '') : '';
      await api.save(path, fm, body);
      const r = await api.read(path);
      const loaded = {
        isNew: false, kind: data.kind, path,
        ...formFromFrontmatter(r.frontmatter || {}, collection),
        ...(bodyField ? { [bodyField.name]: r.body || '' } : {}),
      };
      onSaved(loaded); setF(loaded);
      return path;
    } catch (e) { onMsg({ type: 'err', text: e.message }); return null; }
    finally { setBusy(false); }
  }
  async function preview() {
    const path = await save(); if (!path) return;
    setShowPreview(true); setBusy(true);
    try { const res = await api.preview(path); setPreviewUrl(`${res.url}?t=${Date.now()}`); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
    finally { setBusy(false); }
  }
  async function publish() {
    if (!confirm('Live publizieren?')) return;
    const path = await save(draftField ? { [draftField.name]: false } : {});
    if (!path) return;
    setBusy(true);
    try { const res = await api.publish(path); onMsg({ type: 'ok', text: `Live: ${res.url}` }); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
    finally { setBusy(false); }
  }

  return (
    <div className="editor" ref={editorRef}>
      <div className="editor-main">
        <div className="editor-head">
          <div className="crumb">{f.isNew ? 'Neuer Eintrag' : f.path}</div>
          <span className="spacer" />
          {draftField && (isDraft ? <span className="status draft">Entwurf</span> : <span className="status live">Veröffentlicht</span>)}
          <button className="toggle" onClick={() => setShowPreview((v) => !v)} title="Vorschau ein/aus">
            {showPreview ? 'Vorschau ⤫' : 'Vorschau ⤢'}
          </button>
          <button onClick={() => save().then((p) => p && onMsg({ type: 'ok', text: 'Gespeichert.' }))} disabled={busy}>Speichern</button>
          <button onClick={preview} disabled={busy}>Vorschau</button>
          <button className="primary" onClick={publish} disabled={busy}>Publizieren</button>
        </div>

        <div className="fields">
          {f.isNew && (
            <div className="row">
              <label className="sm">Typ
                <select value={f.kind} onChange={(e) => changeKind(e.target.value)}>
                  {cols.map((c) => <option key={c.kind} value={c.kind}>{c.label}</option>)}
                </select>
              </label>
              {!hasSlugField && (
                <label className="sm">Dateiname
                  <input value={f.slug || ''} onChange={(e) => setField('slug', e.target.value)} placeholder="z. B. impressum" />
                </label>
              )}
            </div>
          )}

          {titleField && (
            <label className="big">{titleField.label || 'Titel'}
              <input value={f[titleField.name] || ''} onChange={(e) => setField(titleField.name, e.target.value)} placeholder="Titel" />
            </label>
          )}

          {shortFields.length > 0 && (
            <div className="meta">
              {shortFields.map((fld) => <Field key={fld.name} field={fld} value={f[fld.name]} onChange={setField} onUpload={upload} busy={busy} />)}
            </div>
          )}

          {longFields.map((fld) => <Field key={fld.name} field={fld} value={f[fld.name]} onChange={setField} onUpload={upload} busy={busy} />)}

          {bodyField && (
            <div className="rich">
              <RichEditor value={f[bodyField.name] || ''} onChange={(body) => setField(bodyField.name, body)} onUpload={upload} />
            </div>
          )}
        </div>
      </div>

      {showPreview && <div className="splitter" onMouseDown={startDrag} />}
      {showPreview && (
        <div className="preview" style={{ width: pw + '%' }}>
          {previewUrl
            ? <iframe title="Vorschau" src={previewUrl} />
            : <div className="empty small"><p>Auf „Vorschau“ klicken — die Seite erscheint hier in deinem echten Theme.</p></div>}
        </div>
      )}
    </div>
  );
}

// ── WYSIWYG-Editor (Toast UI, vanilla) — Formatierung live, speichert Markdown ──
function RichEditor({ value, onChange, onUpload }) {
  const el = useRef(null);
  const inst = useRef(null);
  // value/onChange/onUpload in Refs, damit der Editor nur EINMAL erzeugt wird.
  const cb = useRef({ onChange, onUpload });
  cb.current = { onChange, onUpload };

  useEffect(() => {
    inst.current = new ToastEditor({
      el: el.current,
      initialValue: value || '',
      initialEditType: 'wysiwyg',
      previewStyle: 'tab',
      height: '100%',
      usageStatistics: false,
      autofocus: false,
      toolbarItems: [
        ['heading', 'bold', 'italic', 'strike'],
        ['hr', 'quote'],
        ['ul', 'ol'],
        ['link', 'image'],
        ['code', 'codeblock'],
      ],
      hooks: {
        addImageBlobHook: async (blob, done) => {
          try { done(await cb.current.onUpload(blob), blob.name || 'bild'); }
          catch { /* Upload fehlgeschlagen */ }
        },
      },
      events: { change: () => cb.current.onChange(inst.current.getMarkdown()) },
    });
    return () => { inst.current?.destroy(); inst.current = null; };
  }, []);

  return <div ref={el} className="rich-host" />;
}

// ── Profil ──────────────────────────────────────────────────────────────────
function Profile({ onMsg }) {
  const [p, setP] = useState(null);
  const [busy, setBusy] = useState(false);
  const fileIn = useRef(null);
  useEffect(() => { api.getProfile().then(setP).catch((e) => onMsg({ type: 'err', text: e.message })); }, []);
  if (!p) return <div className="empty">…</div>;
  const set = (k) => (e) => setP({ ...p, [k]: e.target.value });

  async function pickAvatar(ev) {
    const file = ev.target.files?.[0]; ev.target.value = '';
    if (!file) return;
    setBusy(true);
    try { const { url } = await api.upload(file); setP((x) => ({ ...x, avatar: url })); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
    finally { setBusy(false); }
  }
  async function save() {
    setBusy(true);
    try { await api.saveProfile({ name: p.name, bio: p.bio, avatar: p.avatar }); onMsg({ type: 'ok', text: 'Profil gespeichert.' }); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
    finally { setBusy(false); }
  }

  return (
    <div className="profile">
      <div className="profile-card">
        <h2>Profil</h2>
        <div className="avatar-row">
          <div className="avatar" style={{ backgroundImage: p.avatar ? `url(${p.avatar})` : 'none' }}>{!p.avatar && '🙂'}</div>
          <div>
            <button onClick={() => fileIn.current?.click()} disabled={busy}>Profilbild wählen</button>
            <input ref={fileIn} type="file" accept="image/*" hidden onChange={pickAvatar} />
            <p className="muted who-mail">{p.email}</p>
          </div>
        </div>
        <label>Name<input value={p.name} onChange={set('name')} placeholder="Dein Name" /></label>
        <label>Über mich<textarea value={p.bio} onChange={set('bio')} rows={5} placeholder="Kurzer Text über dich…" /></label>
        <div className="actions"><button className="primary" onClick={save} disabled={busy}>Speichern</button></div>
      </div>
      <PasswordChange onMsg={onMsg} />
    </div>
  );
}

function PasswordChange({ onMsg }) {
  const [cur, setCur] = useState('');
  const [nxt, setNxt] = useState('');
  const [busy, setBusy] = useState(false);
  async function submit(e) {
    e.preventDefault();
    if (nxt.length < 6) { onMsg({ type: 'err', text: 'Neues Passwort zu kurz (min. 6 Zeichen).' }); return; }
    setBusy(true);
    try { await api.changePassword(cur, nxt); onMsg({ type: 'ok', text: 'Passwort geändert.' }); setCur(''); setNxt(''); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
    finally { setBusy(false); }
  }
  return (
    <div className="profile-card">
      <h2>Passwort ändern</h2>
      <form onSubmit={submit}>
        <label>Aktuelles Passwort<input type="password" value={cur} onChange={(e) => setCur(e.target.value)} autoComplete="current-password" /></label>
        <label>Neues Passwort<input type="password" value={nxt} onChange={(e) => setNxt(e.target.value)} autoComplete="new-password" /></label>
        <div className="actions"><button className="primary" disabled={busy}>Passwort setzen</button></div>
      </form>
    </div>
  );
}

// ── Übersicht / Dashboard (nur Admin) ───────────────────────────────────────
function Overview({ onMsg, go }) {
  const [s, setS] = useState(null);
  useEffect(() => { api.stats().then(setS).catch((e) => onMsg({ type: 'err', text: e.message })); }, []);
  if (!s) return <div className="empty">…</div>;
  const Card = ({ label, value, hint, to }) => (
    <button className="stat-card" onClick={to ? () => go(to) : undefined} disabled={!to}>
      <span className="stat-value">{value ?? 0}</span>
      <span className="stat-label">{label}</span>
      <span className="stat-hint">{hint || ' '}</span>
    </button>
  );
  const content = s.content || {};
  const d = s.dialog || { forums: 0, threads: 0, comments: 0 };
  const hasDialog = (d.forums + d.threads + d.comments) > 0;
  return (
    <div className="overview">
      <h2>Übersicht</h2>
      <div className="stat-grid">
        {Object.entries(content).map(([k, v]) => <Card key={k} label={k} value={v} to="content" />)}
        <Card label="Autor:innen" value={s.users?.total} hint={`${s.users?.admin || 0} Admin · ${s.users?.editor || 0} Red.`} to="users" />
        {hasDialog && <Card label="Foren" value={d.forums} to="forums" />}
        {hasDialog && <Card label="Threads" value={d.threads} to="moderation" />}
        {hasDialog && <Card label="Wortmeldungen" value={d.comments} to="moderation" />}
      </div>
      <div className="overview-actions">
        <h3>Schnellzugriff</h3>
        <div className="quick">
          <button onClick={() => go('content')}>Inhalte bearbeiten</button>
          {hasDialog && <button onClick={() => go('forums')}>Foren verwalten</button>}
          <button onClick={() => go('users')}>Autor:innen &amp; Rollen</button>
          <a className="quick-link" href="/" target="_blank" rel="noreferrer">Website ↗</a>
        </div>
      </div>
    </div>
  );
}

// ── System (nur Admin): core-Version, Update-Check, Admins, Plugins, Modell ──
function System({ onMsg }) {
  const [s, setS] = useState(null);
  const [upd, setUpd] = useState(null);
  useEffect(() => {
    api.system().then(setS).catch((e) => onMsg({ type: 'err', text: e.message }));
    api.systemUpdate().then(setUpd).catch(() => {});
  }, []);
  if (!s) return <div className="empty">…</div>;
  const badge = !upd ? null
    : upd.available ? <span className="status draft">core v{upd.vendored} bereit — Rebuild nötig</span>
    : upd.vendored ? <span className="status live">aktuell</span>
    : <span className="muted">(kein Subtree)</span>;
  return (
    <div className="profile">
      <div className="profile-card wide">
        <h2>System</h2>
        <div className="sysgrid">
          <div><span className="muted">Core</span><b>{s.core.name} <span className="ver">v{s.core.version}</span></b> {badge}</div>
          <div><span className="muted">Site</span><b>{s.site || '—'}</b></div>
          <div><span className="muted">Auth</span><b>{s.auth}</b></div>
          <div><span className="muted">Hugo</span><b>{s.hugo}</b></div>
        </div>
        <h3>Admins <span className="count-pill">{(s.admins || []).length}</span></h3>
        <ul className="syslist">{(s.admins || []).map((a) => (
          <li key={a}><b>{a}</b><span className="muted">aus Config (fix)</span></li>
        ))}</ul>
        <h3>Plugins <span className="count-pill">{s.plugins.length}</span></h3>
        {s.plugins.length
          ? <ul className="syslist">{s.plugins.map((p) => (
              <li key={p.name}><b>{p.name}</b><span className="muted">{p.routes} Routen · {p.migrations} Migration(en)</span></li>
            ))}</ul>
          : <p className="muted">Keine Plugins aktiv.</p>}
        <h3>Content-Modell <span className="count-pill">{s.collections.length}</span></h3>
        <ul className="syslist">{s.collections.map((c) => (
          <li key={c.kind}><b>{c.label}</b><span className="muted">{c.kind} · {c.fields} Felder{c.statKey ? ` · ${c.statKey}` : ''}</span></li>
        ))}</ul>
        <p className="muted who-mail">openbureau-core fährt diese Site. Updates werden serverseitig eingespielt (Update-Skript).</p>
      </div>
    </div>
  );
}

// ── Autor:innen-Verwaltung (nur Admin) ──────────────────────────────────────
function Users({ onMsg, currentEmail }) {
  const [list, setList] = useState(null);
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [role, setRole] = useState('user');
  const [busy, setBusy] = useState(false);
  const [q, setQ] = useState('');
  const [pwFor, setPwFor] = useState(null);
  const [newPw, setNewPw] = useState('');

  async function refresh() {
    try { setList(await api.listUsers()); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
  }
  useEffect(() => { refresh(); }, []);

  async function create(e) {
    e.preventDefault(); setBusy(true);
    try {
      await api.createUser(email, password, role);
      onMsg({ type: 'ok', text: 'Autor:in angelegt.' });
      setEmail(''); setPassword(''); setRole('user'); refresh();
    } catch (err) { onMsg({ type: 'err', text: err.message }); }
    finally { setBusy(false); }
  }
  async function remove(u) {
    if (!confirm(`${u.email} wirklich löschen?`)) return;
    try { await api.deleteUser(u.id); refresh(); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
  }
  async function savePw(u) {
    if (!newPw || newPw.length < 6) { onMsg({ type: 'err', text: 'Passwort zu kurz (min. 6 Zeichen).' }); return; }
    try { await api.setPassword(u.id, newPw); onMsg({ type: 'ok', text: 'Passwort gesetzt.' }); setPwFor(null); setNewPw(''); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
  }
  async function changeRole(u, r) {
    try { await api.setRole(u.id, r); onMsg({ type: 'ok', text: `Rolle: ${ROLE_LABEL[r]}` }); refresh(); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
  }

  if (!list) return <div className="empty">…</div>;
  const filtered = q ? list.filter((u) => u.email.toLowerCase().includes(q.toLowerCase())) : list;
  const RoleSelect = ({ u }) => (
    <select className="role-select" value={u.role} onChange={(e) => changeRole(u, e.target.value)}>
      <option value="user">User</option><option value="editor">Redakteur</option><option value="admin">Admin</option>
    </select>
  );
  return (
    <div className="profile">
      <div className="profile-card wide">
        <h2>Autor:innen &amp; Rollen <span className="count-pill">{list.length}</span></h2>
        <form className="userform" onSubmit={create}>
          <input type="email" placeholder="E-Mail" value={email} onChange={(e) => setEmail(e.target.value)} required />
          <input type="text" placeholder="Passwort" value={password} onChange={(e) => setPassword(e.target.value)} required />
          <select className="role-select" value={role} onChange={(e) => setRole(e.target.value)}>
            <option value="user">User</option><option value="editor">Redakteur</option><option value="admin">Admin</option>
          </select>
          <button className="primary" disabled={busy}>Anlegen</button>
        </form>
        {list.length > 6 && <input className="userfilter" placeholder="filtern…" value={q} onChange={(e) => setQ(e.target.value)} />}
        <ul className="userlist">
          {filtered.map((u) => (
            <li key={u.id}>
              <span className="uavatar" style={avatarStyle(u.email)}>{(u.email || '?').slice(0, 1).toUpperCase()}</span>
              <span className="t ucol">
                <span className="uemail">{u.email}{u.email === currentEmail && <span className="you"> · du</span>}</span>
                <span className="umeta">
                  angelegt {fmtDate(u.created_at)}
                  {u.last_sign_in_at ? ` · zuletzt aktiv ${fmtDate(u.last_sign_in_at)}` : ' · nie angemeldet'}
                </span>
              </span>
              {u.fixedAdmin ? <span className="rolebadge admin">Admin · .env</span> : <RoleSelect u={u} />}
              {pwFor === u.id ? (
                <span className="pwinline">
                  <input type="text" placeholder="neues Passwort" value={newPw} autoFocus
                    onChange={(e) => setNewPw(e.target.value)}
                    onKeyDown={(e) => { if (e.key === 'Enter') savePw(u); if (e.key === 'Escape') { setPwFor(null); setNewPw(''); } }} />
                  <button onClick={() => savePw(u)}>OK</button>
                  <button onClick={() => { setPwFor(null); setNewPw(''); }}>✕</button>
                </span>
              ) : (
                <button onClick={() => { setPwFor(u.id); setNewPw(''); }}>Passwort</button>
              )}
              {u.email !== currentEmail && !u.fixedAdmin && <button onClick={() => remove(u)}>Löschen</button>}
            </li>
          ))}
        </ul>
        <p className="muted who-mail"><b>User</b> schreiben im Forum · <b>Redakteur</b> moderiert · <b>Admin</b> verwaltet alles. Admins aus <code>ADMIN_EMAILS</code> sind fix.</p>
      </div>
    </div>
  );
}

const ROLE_LABEL = { user: 'User', editor: 'Redakteur', admin: 'Admin' };
function fmtDate(ts) { if (!ts) return '—'; try { return new Date(ts).toLocaleDateString('de-CH'); } catch { return '—'; } }
function uHashHue(s) { let h = 0; for (let i = 0; i < (s || '').length; i++) h = (h * 31 + s.charCodeAt(i)) | 0; return Math.abs(h) % 360; }
function avatarStyle(s) { const h = uHashHue(s); return { background: `hsl(${h} 36% 82%)`, color: `hsl(${h} 30% 28%)` }; }

// ── Foren-Verwaltung (nur Admin) ────────────────────────────────────────────
function Forums({ onMsg }) {
  const [list, setList] = useState(null);
  const [draft, setDraft] = useState({ slug: '', name: '', sort: 50 });
  const [busy, setBusy] = useState(false);

  async function refresh() {
    try { setList(await api.listForumsAdmin()); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
  }
  useEffect(() => { refresh(); }, []);

  async function create(e) {
    e.preventDefault();
    if (!draft.slug || !draft.name) return;
    setBusy(true);
    try { await api.createForum(draft); onMsg({ type: 'ok', text: 'Kategorie angelegt.' }); setDraft({ slug: '', name: '', sort: 50 }); refresh(); }
    catch (err) { onMsg({ type: 'err', text: err.message }); }
    finally { setBusy(false); }
  }
  async function save(f, patch) {
    try { await api.updateForum(f.id, patch); refresh(); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
  }
  async function remove(f) {
    if (!confirm(`Kategorie „${f.name}“ löschen? Threads darin verschwinden.`)) return;
    try { await api.deleteForum(f.id); onMsg({ type: 'ok', text: 'Gelöscht.' }); refresh(); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
  }

  if (!list) return <div className="empty">…</div>;
  return (
    <div className="profile">
      <div className="profile-card wide">
        <h2>Foren / Kategorien</h2>
        <form className="userform" onSubmit={create}>
          <input placeholder="Name (z. B. Wettbewerbe)" value={draft.name}
            onChange={(e) => setDraft({ ...draft, name: e.target.value, slug: draft.slug || slugify(e.target.value) })} required />
          <input placeholder="slug" value={draft.slug} onChange={(e) => setDraft({ ...draft, slug: slugify(e.target.value) })} required />
          <input type="number" placeholder="Sort" style={{ width: '5em' }} value={draft.sort} onChange={(e) => setDraft({ ...draft, sort: e.target.value })} />
          <button className="primary" disabled={busy}>Anlegen</button>
        </form>
        <ul className="forumlist">
          {list.map((f) => (
            <li key={f.id} className={f.kind === 'library' ? 'is-library' : ''}>
              <span className="fsort">{f.sort}</span>
              <input className="fname" defaultValue={f.name} onBlur={(e) => e.target.value !== f.name && save(f, { name: e.target.value })} />
              <input className="fcolor" type="color" value={/^#[0-9a-fA-F]{6}$/.test(f.color || '') ? f.color : '#cccccc'} onChange={(e) => save(f, { color: e.target.value })} title="Akzentfarbe" />
              <span className="fslug">/{f.slug}</span>
              {f.kind === 'library'
                ? <span className="status">Library (auto)</span>
                : <button onClick={() => remove(f)}>Löschen</button>}
            </li>
          ))}
        </ul>
        <p className="muted who-mail">„Beiträge“ ist die automatische Library-Kategorie und kann nicht gelöscht werden.</p>
      </div>
    </div>
  );
}

// ── Moderation (Admin + Redakteur) ──────────────────────────────────────────
function Moderation({ onMsg }) {
  const [data, setData] = useState(null);

  async function refresh() {
    try { setData(await api.modOverview()); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
  }
  useEffect(() => { refresh(); }, []);

  async function delComment(c) {
    if (!confirm('Wortmeldung löschen?')) return;
    try { await api.deleteComment(c.id); onMsg({ type: 'ok', text: 'Gelöscht.' }); refresh(); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
  }
  async function toggleLock(t) {
    try { await api.lockThread(t.key, !t.locked); refresh(); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
  }
  async function delThread(t) {
    if (!confirm(`Thread „${t.title}“ ausblenden?`)) return;
    try { await api.deleteThread(t.key); onMsg({ type: 'ok', text: 'Ausgeblendet.' }); refresh(); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
  }

  if (!data) return <div className="empty">…</div>;
  return (
    <div className="moderation">
      <div className="mod-col">
        <h2>Letzte Wortmeldungen</h2>
        <ul className="modlist">
          {data.comments.map((c) => (
            <li key={c.id}>
              <div className="mod-head"><b>{c.author_name}</b>
                <span className="muted"> · {c.forum_name || '—'} · {c.thread_title}</span></div>
              <div className="mod-body">{c.body}</div>
              <div className="mod-actions">
                <a href={c.thread_url} target="_blank" rel="noreferrer">öffnen</a>
                <button onClick={() => delComment(c)}>Löschen</button>
              </div>
            </li>
          ))}
          {!data.comments.length && <li className="muted">Noch keine Wortmeldungen.</li>}
        </ul>
      </div>
      <div className="mod-col">
        <h2>Threads</h2>
        <ul className="modlist">
          {data.threads.map((t) => (
            <li key={t.key} className={t.deleted ? 'gone' : ''}>
              <div className="mod-head"><b>{t.title}</b>
                <span className="muted"> · {t.forum_name} · {t.count}</span>
                {t.locked && <span className="status">gesperrt</span>}
                {t.deleted && <span className="status">ausgeblendet</span>}</div>
              <div className="mod-actions">
                <a href={t.url} target="_blank" rel="noreferrer">öffnen</a>
                {t.kind !== 'library' && <button onClick={() => toggleLock(t)}>{t.locked ? 'Entsperren' : 'Sperren'}</button>}
                {!t.deleted && <button onClick={() => delThread(t)}>Ausblenden</button>}
              </div>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

function slugify(s) {
  return (s || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '')
    .replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '');
}
