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';

// OPENBUREAU-Palette (Hex aus assets/css/custom.css) — für Dropdown + Punkte.
const COLORS = [
  ['', 'keine', 'transparent'],
  ['ajisai', 'Ajisai · Hortensie', '#A39EC4'],
  ['sakura', 'Sakura · Kirschblüte', '#C49EC4'],
  ['suna', 'Suna · Sand', '#C4C19E'],
  ['ichigo', 'Ichigo · Erdbeere', '#C49EA0'],
  ['yuyake', 'Yuyake · Sonnenuntergang', '#CEB188'],
  ['sora', 'Sora · Himmel', '#9EC3C4'],
  ['kusa', 'Kusa · Gras', '#9EC49F'],
  ['kori', 'Kori · Eis', '#A5B4CB'],
  ['amagumo', 'Amagumo · Regenwolke', '#4C4C4C'],
  ['yuki', 'Yuki · Schnee', '#F0F0F0'],
];
const hexOf = (name) => (COLORS.find((c) => c[0] === name) || [])[2] || 'transparent';

const LAYOUTS = ['', 'text', 'image', 'icon'];
const SECTIONS = ['buerofuehrung', 'software', 'theorie'];
const KIND_LABEL = { beitrag: 'Beiträge', biblio: 'Library', seite: 'Seiten', rubrik: 'Rubriken' };

const EMPTY = {
  isNew: true, path: '', type: 'beitrag', section: 'software', slug: '',
  title: '', date: new Date().toISOString().slice(0, 10), weight: '',
  color: '', layout: 'text', tags: '', summary: '', description: '',
  cover_image: '', external: '', authors: '', group: '', toc: false, draft: true, body: '',
};

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 [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(() => {}); }, []);
  useEffect(() => { if (!msg) return; const t = setTimeout(() => setMsg(null), 4000); return () => clearTimeout(t); }, [msg]);

  async function open(entry) {
    try { setCurrent(fromRead(await api.read(entry.path))); }
    catch (err) { setMsg({ type: 'err', text: err.message }); }
  }

  const q = query.trim().toLowerCase();
  const filtered = q ? entries.filter((e) => e.title.toLowerCase().includes(q) || (e.section || '').includes(q)) : entries;
  const groups = { beitrag: [], biblio: [], seite: [], rubrik: [] };
  for (const e of filtered) (groups[e.kind] || groups.seite).push(e);

  return (
    <div className="app">
      <header className="topbar">
        <span className="logo">OPENBUREAU</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 && <button className={view === 'moderation' ? 'active' : ''} onClick={() => setView('moderation')}>Moderation</button>}
          {me?.isAdmin && <button className={view === 'forums' ? 'active' : ''} onClick={() => setView('forums')}>Foren</button>}
          {me?.isAdmin && <button className={view === 'users' ? 'active' : ''} onClick={() => setView('users')}>Autor:innen</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} />
        ) : (
          <>
            <aside>
              <button className="new" onClick={() => setCurrent({ ...EMPTY })}>＋ Neuer Beitrag</button>
              <div className="search"><span>⌕</span><input placeholder="Suchen…" value={query} onChange={(e) => setQuery(e.target.value)} /></div>
              {['beitrag', 'biblio', 'seite', 'rubrik'].map((kind) => groups[kind].length > 0 && (
                <div className="group" key={kind}>
                  <div className="group-title">{KIND_LABEL[kind]} <span>{groups[kind].length}</span></div>
                  <ul className="list">
                    {groups[kind].map((e) => (
                      <li key={e.path} className={current?.path === e.path ? 'active' : ''} onClick={() => open(e)}>
                        <span className="dot" style={{ background: e.color ? hexOf(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'} initial={current}
                    onSaved={(loaded) => { setCurrent(loaded); refresh(); }} onMsg={setMsg} />
                : <div className="empty"><p>Wähle links einen Eintrag — oder leg einen neuen Beitrag an.</p></div>}
            </main>
          </>
        )}
      </div>

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

function Editor({ initial, 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 coverIn = useRef(null);
  const set = (k) => (e) => setF({ ...f, [k]: e.target.type === 'checkbox' ? e.target.checked : e.target.value });
  const isWiki = f.type === 'biblio' || (f.path || '').startsWith('library/');

  async function pickCover(ev) {
    const file = ev.target.files?.[0]; ev.target.value = '';
    if (!file) return;
    setBusy(true);
    try { const { url } = await api.upload(file); setF((p) => ({ ...p, cover_image: url })); }
    catch (e) { onMsg({ type: 'err', text: e.message }); }
    finally { setBusy(false); }
  }

  // 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(); }

  function currentPath(data = f) {
    if (!data.isNew) return data.path;
    const slug = (data.slug || '').trim();
    if (!slug) return '';
    if (data.type === 'beitrag') return `archiv/${data.section}/${slug}.md`;
    if (data.type === 'biblio') return `library/${slug}.md`;
    return `${slug}.md`;
  }

  // overrides erlauben z.B. { draft: false } beim Publizieren.
  async function save(overrides = {}) {
    const data = { ...f, ...overrides };
    const path = currentPath(data);
    if (!path) { onMsg({ type: 'err', text: 'Bitte einen Slug angeben.' }); return null; }
    if (!data.title.trim()) { onMsg({ type: 'err', text: 'Titel fehlt.' }); return null; }
    setBusy(true);
    try {
      await api.save(path, buildFrontmatter(data), data.body);
      const loaded = fromRead(await api.read(path));
      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? Der Beitrag wird aus „Entwurf“ genommen.')) return;
    const path = await save({ draft: false }); // Publizieren = nicht mehr Entwurf
    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" />
          {f.draft ? <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.type} onChange={set('type')}>
                  <option value="beitrag">Beitrag</option>
                  <option value="biblio">Library-Seite</option>
                  <option value="seite">Seite</option>
                </select>
              </label>
              {f.type === 'beitrag' && (
                <label className="sm">Rubrik
                  <select value={f.section} onChange={set('section')}>{SECTIONS.map((s) => <option key={s}>{s}</option>)}</select>
                </label>
              )}
              <label>Slug<input value={f.slug} onChange={set('slug')} placeholder="z.B. neuer-beitrag" /></label>
            </div>
          )}

          <label className="big">Titel<input value={f.title} onChange={set('title')} placeholder="Titel des Beitrags" /></label>

          <div className="meta">
            {isWiki && <label className="sm">Gruppe<input value={f.group} onChange={set('group')} placeholder="z. B. Begriffe" /></label>}
            <label className="sm">Datum<input type="date" value={f.date} onChange={set('date')} /></label>
            <label className="xs">Reihenfolge<input type="number" value={f.weight} onChange={set('weight')} placeholder="weight" /></label>
            <label className="sm">Farbe
              <div className="colorpick">
                <span className="swatch" style={{ background: hexOf(f.color) }} />
                <select value={f.color} onChange={set('color')}>{COLORS.map(([v, label]) => <option key={v} value={v}>{label}</option>)}</select>
              </div>
            </label>
            <label className="sm">Layout
              <select value={f.layout} onChange={set('layout')}>{LAYOUTS.map((l) => <option key={l} value={l}>{l || '(automatisch)'}</option>)}</select>
            </label>
            <label>Tags<input value={f.tags} onChange={set('tags')} placeholder="komma, getrennt" /></label>
            <label className="check"><input type="checkbox" checked={f.toc} onChange={set('toc')} /> Inhaltsverz.</label>
            <label className="check"><input type="checkbox" checked={f.draft} onChange={set('draft')} /> Entwurf</label>
          </div>

          <label>Kurztext (summary)<input value={f.summary} onChange={set('summary')} /></label>
          <div className="row">
            <label>Cover-Bild
              <div className="cover-row">
                <input value={f.cover_image} onChange={set('cover_image')} placeholder="/images/…jpg" />
                <button type="button" onClick={() => coverIn.current?.click()} disabled={busy}>Hochladen</button>
                <input ref={coverIn} type="file" accept="image/*" hidden onChange={pickCover} />
                {f.cover_image && <span className="cover-thumb" style={{ backgroundImage: `url(${f.cover_image})` }} />}
              </div>
            </label>
            <label>Externer Link<input value={f.external} onChange={set('external')} placeholder="https://…" /></label>
          </div>
          <label>Autor:innen (E-Mails, Komma — für gemeinsamen Zugriff)
            <input value={f.authors} onChange={set('authors')} placeholder="du@…, kollege@…" />
          </label>

          <div className="rich">
            <RichEditor value={f.body} onChange={(body) => setF((p) => ({ ...p, body }))}
              onUpload={async (file) => (await api.upload(file)).url} />
          </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>
    </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}</span>
      <span className="stat-label">{label}</span>
      <span className="stat-hint">{hint || ' '}</span>
    </button>
  );
  return (
    <div className="overview">
      <h2>Übersicht</h2>
      <div className="stat-grid">
        <Card label="Beiträge" value={s.content.beitraege} hint={`${s.content.entwuerfe} Entwürfe`} to="content" />
        <Card label="Library-Seiten" value={s.content.library} to="content" />
        <Card label="Seiten" value={s.content.seiten} />
        <Card label="Autor:innen" value={s.users.total} hint={`${s.users.admin} Admin · ${s.users.editor} Red.`} to="users" />
        <Card label="Foren" value={s.dialog.forums} to="forums" />
        <Card label="Threads" value={s.dialog.threads} to="moderation" />
        <Card label="Wortmeldungen" value={s.dialog.comments} to="moderation" />
      </div>
      <div className="overview-actions">
        <h3>Schnellzugriff</h3>
        <div className="quick">
          <button onClick={() => go('content')}>Inhalte bearbeiten</button>
          <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>
          <a className="quick-link" href="/dialog/" target="_blank" rel="noreferrer">Dialog ↗</a>
          <a className="quick-link" href="/library/" target="_blank" rel="noreferrer">Library ↗</a>
        </div>
      </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, '');
}

// ── Mapping Datei-Lesart → Formular ────────────────────────────────────────
function fromRead(r) {
  const fm = r.frontmatter || {};
  const p = r.path || '';
  const type = p.startsWith('archiv/') ? 'beitrag' : p.startsWith('library/') ? 'biblio' : 'seite';
  return {
    isNew: false, path: r.path, type, section: '', slug: '',
    title: fm.title || '', date: fm.date ? String(fm.date).slice(0, 10) : '',
    weight: fm.weight ?? '', color: fm.color || '', layout: fm.layout || '',
    tags: Array.isArray(fm.tags) ? fm.tags.join(', ') : '',
    summary: fm.summary || '', description: fm.description || '',
    cover_image: fm.cover_image || '', external: fm.external || '',
    authors: Array.isArray(fm.authors) ? fm.authors.join(', ') : (fm.authors || ''),
    group: fm.group || '', toc: !!fm.toc, draft: !!fm.draft, body: r.body || '',
  };
}
function buildFrontmatter(f) {
  const fm = { title: f.title };
  if (f.date) fm.date = f.date;
  if (f.weight !== '' && f.weight != null) fm.weight = Number(f.weight);
  const tags = f.tags ? f.tags.split(',').map((t) => t.trim()).filter(Boolean) : [];
  if (tags.length) fm.tags = tags;
  if (f.summary) fm.summary = f.summary;
  if (f.description) fm.description = f.description;
  if (f.cover_image) fm.cover_image = f.cover_image;
  if (f.layout) fm.layout = f.layout;
  if (f.external) fm.external = f.external;
  if (f.color) fm.color = f.color;
  const authors = f.authors ? f.authors.split(',').map((t) => t.trim()).filter(Boolean) : [];
  if (authors.length) fm.authors = authors;
  if (f.group) fm.group = f.group;
  if (f.toc) fm.toc = true;
  if (f.draft) fm.draft = true;
  return fm;
}
