// Dictionary, Conjugations, Grammar, Examples views.
const { useState: useStateV, useMemo: useMemoV, useEffect: useEffectV, useRef: useRefV } = React;

function stripDiacritics(s) {
  return s.normalize('NFD').replace(/[̀-ͯ]/g, '');
}

// ---------- Dictionary ----------

function DictionaryView({ data, pulseId, onPulseClear, pushToast, onRefresh }) {
  const [search, setSearch] = useStateV('');
  const [cats, setCats] = useStateV([]);
  const [confs, setConfs] = useStateV([]);
  const [openId, setOpenId] = useStateV(null);
  const [sort, setSort] = useStateV('alpha');

  const allCats = useMemoV(() => {
    return [...new Set((data.entries || []).map(e => e.category))].sort();
  }, [data.entries]);

  const filtered = useMemoV(() => {
    const q = search.trim().toLowerCase();
    const qNorm = stripDiacritics(q);
    let list = (data.entries || []).filter(e => {
      if (cats.length && !cats.includes(e.category)) return false;
      if (confs.length && !confs.includes(e.confidence)) return false;
      if (q) {
        const blob = `${e.word} ${e.meaning} ${e.notes ?? ''} ${e.plural ?? ''} ${e.pronunciation ?? ''}`.toLowerCase();
        if (!blob.includes(q) && !stripDiacritics(blob).includes(qNorm)) return false;
      }
      return true;
    });
    if (sort === 'alpha') list.sort((a, b) => a.word.localeCompare(b.word));
    else if (sort === 'conf') list.sort((a, b) => CONF_ORDER.indexOf(a.confidence) - CONF_ORDER.indexOf(b.confidence));
    else if (sort === 'recent') list.sort((a, b) => new Date(b.last_updated || 0).getTime() - new Date(a.last_updated || 0).getTime());
    return list;
  }, [data.entries, search, cats, confs, sort]);

  const grouped = useMemoV(() => {
    if (sort !== 'alpha') return null;
    const map = {};
    filtered.forEach(e => {
      const k = e.word[0].toUpperCase();
      (map[k] = map[k] || []).push(e);
    });
    return Object.entries(map);
  }, [filtered, sort]);

  const toggle = (arr, set, v) => set(arr.includes(v) ? arr.filter(x => x !== v) : [...arr, v]);

  return (
    <div>
      <section className="dict-toolbar">
        <div className="dict-search-wrap">
          <span className="dict-search-icon">⌕</span>
          <input
            className="field"
            type="search"
            placeholder="Search a word, a meaning, a note…"
            value={search}
            onChange={e => setSearch(e.target.value)}
            style={{ paddingLeft: 36 }}
          />
          {search && (
            <button className="dict-search-clear" onClick={() => setSearch('')}>✕</button>
          )}
        </div>
        <div className="dict-sort">
          {[['alpha','A–Z'],['conf','Confidence'],['recent','Newest']].map(([k, l]) => (
            <button key={k} className={`chip ${sort === k ? 'on' : ''}`} onClick={() => setSort(k)}>
              {l}
            </button>
          ))}
        </div>
      </section>

      <section className="dict-filters">
        <div className="filter-group">
          <span className="filter-label">Category</span>
          <div className="filter-chips">
            {allCats.map(c => (
              <button key={c} className={`chip ${cats.includes(c) ? 'on' : ''}`} onClick={() => toggle(cats, setCats, c)}>
                <span className="dot"></span>{c}
              </button>
            ))}
            {cats.length > 0 && <button className="chip" onClick={() => setCats([])}>clear</button>}
          </div>
        </div>
        <div className="filter-group">
          <span className="filter-label">Confidence</span>
          <div className="filter-chips">
            {CONF_ORDER.map(c => (
              <button key={c} className={`chip ${confs.includes(c) ? 'on' : ''}`} onClick={() => toggle(confs, setConfs, c)}>
                <span className={`dot conf-${c}`} style={{ background: `var(--conf-${c})` }}></span>{c}
              </button>
            ))}
            {confs.length > 0 && <button className="chip" onClick={() => setConfs([])}>clear</button>}
          </div>
        </div>
      </section>

      <div className="dict-meta">
        <span className="mono" style={{ color: 'var(--fg-mute)', fontSize: 12 }}>
          {filtered.length} of {(data.entries || []).length} entries
        </span>
      </div>

      {filtered.length === 0 ? (
        <div className="empty-state">
          <span className="serif" style={{ fontStyle: 'italic', fontSize: 22, color: 'var(--paper-dim)' }}>nothing matches.</span>
          <span className="hand" style={{ display: 'block', marginTop: 8, fontSize: 20, color: 'var(--accent-warm)' }}>maybe coin one?</span>
        </div>
      ) : grouped ? (
        grouped.map(([letter, items]) => (
          <DictGroup
            key={letter}
            letter={letter}
            items={items}
            meta={data.meta}
            data={data}
            openId={openId}
            setOpenId={setOpenId}
            pulseId={pulseId}
            onPulseClear={onPulseClear}
            pushToast={pushToast}
            onRefresh={onRefresh}
          />
        ))
      ) : (
        <div className="dict-list">
          {groupByWord(filtered).map(({ primary, siblings }) => (
            <DictRow
              key={primary.id}
              entry={primary}
              siblings={siblings}
              meta={data.meta}
              data={data}
              open={openId === primary.id}
              onToggle={() => setOpenId(openId === primary.id ? null : primary.id)}
              pulse={pulseId === primary.id || siblings.some(s => pulseId === s.id)}
              onPulseClear={onPulseClear}
              pushToast={pushToast}
              onRefresh={onRefresh}
            />
          ))}
        </div>
      )}
    </div>
  );
}

function groupByWord(items) {
  const map = {};
  for (const e of items) {
    const key = e.word.toLowerCase();
    (map[key] = map[key] || []).push(e);
  }
  const result = [];
  const seen = new Set();
  for (const e of items) {
    const key = e.word.toLowerCase();
    if (seen.has(key)) continue;
    seen.add(key);
    const group = map[key];
    group.sort((a, b) => CONF_ORDER.indexOf(b.confidence) - CONF_ORDER.indexOf(a.confidence));
    result.push({ primary: group[0], siblings: group.slice(1) });
  }
  return result;
}

function DictGroup({ letter, items, meta, data, openId, setOpenId, pulseId, onPulseClear, pushToast, onRefresh }) {
  const wordGroups = groupByWord(items);
  return (
    <section className="dict-group">
      <header className="dict-group-head">
        <span className="dict-group-letter serif">{letter}</span>
        <span className="dict-group-rule"></span>
        <span className="mono" style={{ color: 'var(--fg-dim)', fontSize: 11 }}>{wordGroups.length}</span>
      </header>
      <div className="dict-list">
        {wordGroups.map(({ primary, siblings }) => (
          <DictRow
            key={primary.id}
            entry={primary}
            siblings={siblings}
            meta={meta}
            data={data}
            open={openId === primary.id}
            onToggle={() => setOpenId(openId === primary.id ? null : primary.id)}
            pulse={pulseId === primary.id || siblings.some(s => pulseId === s.id)}
            onPulseClear={onPulseClear}
            pushToast={pushToast}
            onRefresh={onRefresh}
          />
        ))}
      </div>
    </section>
  );
}

function DictRow({ entry, siblings = [], meta, data, open, onToggle, pulse, onPulseClear, pushToast, onRefresh }) {
  const ref = useRefV();
  const [editing, setEditing] = useStateV(false);
  const [editFields, setEditFields] = useStateV({});
  const [saving, setSaving] = useStateV(false);
  const [aiCorrection, setAiCorrection] = useStateV('');
  const [aiLoading, setAiLoading] = useStateV(false);
  const [aiResult, setAiResult] = useStateV(null);

  useEffectV(() => {
    if (pulse && ref.current) {
      ref.current.classList.add('pulse-new');
      const t = setTimeout(() => {
        ref.current && ref.current.classList.remove('pulse-new');
        onPulseClear && onPulseClear();
      }, 2400);
      return () => clearTimeout(t);
    }
  }, [pulse]);

  function startEdit() {
    setEditFields({
      word: entry.word || '',
      category: entry.category || '',
      meaning: entry.meaning || '',
      confidence: entry.confidence || 'guess',
      notes: entry.notes || '',
      plural: entry.plural || '',
      pronunciation: entry.pronunciation || '',
    });
    setEditing(true);
    setAiResult(null);
  }

  async function saveEdit() {
    setSaving(true);
    try {
      const res = await fetch(`/api/entry/${entry.id}`, {
        method: 'PATCH',
        headers: adminHeaders({ 'Content-Type': 'application/json' }),
        body: JSON.stringify(editFields),
      });
      if (res.status === 401) { promptAdminToken(); setSaving(false); return; }
      if (!res.ok) throw new Error('Save failed');
      setEditing(false);
      pushToast && pushToast({ kind: 'confirmed', title: 'Saved', body: `"${entry.word}" updated.` });
      onRefresh && onRefresh();
    } catch (err) {
      pushToast && pushToast({ kind: 'error', title: 'Error', body: err.message });
    }
    setSaving(false);
  }

  async function rewriteNotes() {
    setAiLoading(true);
    setAiResult(null);
    try {
      const res = await fetch('/api/correct', {
        method: 'POST',
        headers: adminHeaders({ 'Content-Type': 'application/json' }),
        body: JSON.stringify({ entry_id: entry.id, correction: 'Rewrite the notes as clean dictionary-style text — etymology, usage context, linguistic observations. Remove any correction logs, changelog references, or [CORRECTED] prefixes. The notes should read as if the entry was always correct.' }),
      });
      if (res.status === 401) { promptAdminToken(); setAiLoading(false); return; }
      const data = await res.json();
      if (data.ok) {
        if (data.applied?.length) {
          setEditFields(prev => {
            const updated = { ...prev };
            for (const c of data.applied) { if (c.field in updated) updated[c.field] = c.new_value; }
            return updated;
          });
        }
        pushToast && pushToast({ kind: 'confirmed', title: 'Notes rewritten', body: data.reasoning || 'Done.' });
        onRefresh && onRefresh();
      }
    } catch (err) {
      pushToast && pushToast({ kind: 'error', title: 'Error', body: err.message });
    }
    setAiLoading(false);
  }

  async function submitAiCorrection() {
    if (!aiCorrection.trim()) return;
    setAiLoading(true);
    setAiResult(null);
    try {
      const res = await fetch('/api/correct', {
        method: 'POST',
        headers: adminHeaders({ 'Content-Type': 'application/json' }),
        body: JSON.stringify({ entry_id: entry.id, correction: aiCorrection }),
      });
      if (res.status === 401) { promptAdminToken(); setAiLoading(false); return; }
      const data = await res.json();
      if (data.ok) {
        setAiResult(data);
        setAiCorrection('');
        if (data.applied?.length) {
          setEditFields(prev => {
            const updated = { ...prev };
            for (const c of data.applied) { if (c.field in updated) updated[c.field] = c.new_value; }
            return updated;
          });
        }
        pushToast && pushToast({ kind: 'confirmed', title: 'AI correction', body: data.reasoning || `${data.applied.length} change(s) applied.` });
        onRefresh && onRefresh();
      } else {
        setAiResult({ error: data.error });
      }
    } catch (err) {
      setAiResult({ error: err.message });
    }
    setAiLoading(false);
  }

  const isAdmin = isAdminMode();

  return (
    <article ref={ref} className={`dict-row ${open ? 'open' : ''}`}>
      <button className="dict-row-main" onClick={onToggle}>
        <span className="dict-row-word mono">{entry.word}</span>
        <span className="dict-row-pron mono">{entry.pronunciation || ''}</span>
        <span className="dict-row-cat">{entry.category}</span>
        <span className="dict-row-meaning">{entry.meaning}</span>
        <span className={confChipClass(entry.confidence)}>{entry.confidence}</span>
        <span className="dict-row-caret">{open ? '−' : '+'}</span>
      </button>
      {open && (
        <div className="dict-row-detail">
          <div className="dict-detail-grid">
            {entry.plural && (
              <div>
                <div className="eyebrow"><span className="rule"></span>Plural</div>
                <div className="mono dict-detail-val">{entry.plural}</div>
              </div>
            )}
            {entry.pronunciation && (
              <div>
                <div className="eyebrow"><span className="rule"></span>Pronunciation</div>
                <div className="mono dict-detail-val">{entry.pronunciation}</div>
              </div>
            )}
            <div>
              <div className="eyebrow"><span className="rule"></span>Category</div>
              <div className="dict-detail-val serif" style={{ fontStyle: 'italic' }}>{entry.category}</div>
            </div>
          </div>
          {siblings.length > 0 && (
            <div className="dict-detail-siblings">
              <div className="eyebrow"><span className="rule"></span>Also used as</div>
              {siblings.map(sib => (
                <div key={sib.id} className="dict-sibling">
                  <span className="dict-sibling-cat serif" style={{ fontStyle: 'italic' }}>{sib.category}</span>
                  <span className="dict-sibling-meaning">{sib.meaning}</span>
                  <span className={confChipClass(sib.confidence)} style={{ fontSize: 10, padding: '1px 6px' }}>{sib.confidence}</span>
                </div>
              ))}
            </div>
          )}
          {entry.notes && (
            <div className="dict-detail-notes">
              <div className="eyebrow"><span className="rule"></span>Editor's note</div>
              <p>{entry.notes}</p>
            </div>
          )}
          {(() => {
            const baseSources = entry.source_messages || [];
            const allSources = isAdmin && siblings.length > 0
              ? [...baseSources, ...siblings.flatMap(s => s.source_messages || [])].filter((sm, i, arr) => arr.findIndex(x => x.message_id === sm.message_id) === i)
              : baseSources;
            return allSources.length > 0 && (
            <div className="dict-detail-evidence">
              <div className="eyebrow"><span className="rule"></span>Evidence ({allSources.length})</div>
              <ul>
                {allSources.map(sm => (
                  <li key={sm.message_id}>
                    <a href={discordLink(meta, sm.message_id)} target="_blank" rel="noreferrer" className="evidence-tag">
                      {sm.author_is_jorn ? 'JORN' : 'OTHER'}
                    </a>
                    <span className="evidence-snippet">"{sm.snippet}"</span>
                  </li>
                ))}
              </ul>
            </div>
            );
          })()}

          {/* Related conjugations, grammar, examples */}
          {data && (() => {
            const w = entry.word.toLowerCase();
            const wNorm = stripDiacritics(w);
            const conj = data.conjugations ? Object.values(data.conjugations).find(c => c.infinitive.toLowerCase() === w) : null;
            const grammar = (data.grammar_rules || []).filter(r =>
              r.rule.toLowerCase().includes(w) || r.detail.toLowerCase().includes(w)
              || stripDiacritics(r.rule.toLowerCase()).includes(wNorm) || stripDiacritics(r.detail.toLowerCase()).includes(wNorm)
            );
            const examples = (data.example_sentences || []).filter(s =>
              s.conlang.toLowerCase().includes(w) || stripDiacritics(s.conlang.toLowerCase()).includes(wNorm)
            );
            if (!conj && !grammar.length && !examples.length) return null;
            return (
              <div className="dict-detail-related">
                {conj && (
                  <div>
                    <div className="eyebrow"><span className="rule"></span>Conjugation</div>
                    <table className="related-conj-table">
                      <tbody>
                        {sortForms(Object.entries(conj.forms)).map(([pron, form]) => (
                          <tr key={pron}>
                            <td className="mono" style={{ color: 'var(--fg-mute)', paddingRight: 12 }}>{pron}</td>
                            <td className="mono">{form}</td>
                          </tr>
                        ))}
                      </tbody>
                    </table>
                    {conj.notes && <p style={{ fontSize: 12, color: 'var(--fg-mute)', margin: '4px 0 0' }}>{conj.notes}</p>}
                  </div>
                )}
                {grammar.length > 0 && (
                  <div>
                    <div className="eyebrow"><span className="rule"></span>Grammar ({grammar.length})</div>
                    {grammar.map(r => (
                      <div key={r.id} style={{ marginBottom: 6 }}>
                        <span className="mono" style={{ fontSize: 13 }}>{r.rule}</span>
                        <span style={{ display: 'block', fontSize: 12, color: 'var(--fg-mute)' }}>{r.detail}</span>
                      </div>
                    ))}
                  </div>
                )}
                {examples.length > 0 && (
                  <div>
                    <div className="eyebrow"><span className="rule"></span>Examples ({examples.length})</div>
                    {examples.map(s => (
                      <div key={s.id} style={{ marginBottom: 6 }}>
                        <span className="mono" style={{ fontSize: 13 }}>{s.conlang}</span>
                        <span style={{ display: 'block', fontSize: 12, color: 'var(--fg-mute)', fontStyle: 'italic' }}>{s.english}</span>
                      </div>
                    ))}
                  </div>
                )}
              </div>
            );
          })()}

          {/* Admin: direct edit + AI correction */}
          {isAdmin && !editing && (
            <div style={{ display: 'flex', gap: 10, paddingTop: 8 }}>
              <button className="btn" onClick={startEdit}>✏ Edit entry</button>
              <button className="btn" style={{ color: 'var(--accent-warm)' }} onClick={() => {
                if (window.confirm(`Delete "${entry.word}" (${entry.category})? This cannot be undone.`)) {
                  fetch(`/api/entry/${entry.id}`, { method: 'DELETE', headers: adminHeaders() })
                    .then(r => { if (r.ok) { pushToast && pushToast({ kind: 'confirmed', title: 'Deleted', body: `"${entry.word}" removed.` }); onRefresh && onRefresh(); } });
                }
              }}>🗑 Delete</button>
            </div>
          )}
          {editing && (
            <div className="edit-panel">
              <div className="eyebrow"><span className="rule"></span>Edit entry</div>
              <div className="edit-row-inline">
                <div className="edit-row" style={{ flex: 1 }}>
                  <label className="edit-label">Word</label>
                  <input className="field field-mono" value={editFields.word} onChange={e => setEditFields({...editFields, word: e.target.value})} />
                </div>
                <div className="edit-row" style={{ flex: 1 }}>
                  <label className="edit-label">Category</label>
                  <select className="field field-mono" value={editFields.category} onChange={e => setEditFields({...editFields, category: e.target.value})}>
                    {['noun','verb','adjective','adverb','pronoun','particle','emotion','slang','interjection','other'].map(c => <option key={c} value={c}>{c}</option>)}
                  </select>
                </div>
              </div>
              <div className="edit-row">
                <label className="edit-label">Meaning</label>
                <textarea className="field" value={editFields.meaning} onChange={e => setEditFields({...editFields, meaning: e.target.value})} rows={2} />
              </div>
              <div className="edit-row-inline">
                <div className="edit-row" style={{ flex: 1 }}>
                  <label className="edit-label">Confidence</label>
                  <select className="field field-mono" value={editFields.confidence} onChange={e => setEditFields({...editFields, confidence: e.target.value})}>
                    {CONF_ORDER.map(c => <option key={c} value={c}>{c}</option>)}
                  </select>
                </div>
                <div className="edit-row" style={{ flex: 1 }}>
                  <label className="edit-label">Plural</label>
                  <input className="field field-mono" value={editFields.plural} onChange={e => setEditFields({...editFields, plural: e.target.value})} />
                </div>
                <div className="edit-row" style={{ flex: 1 }}>
                  <label className="edit-label">Pronunciation</label>
                  <input className="field field-mono" value={editFields.pronunciation} onChange={e => setEditFields({...editFields, pronunciation: e.target.value})} />
                </div>
              </div>
              <div className="edit-row">
                <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
                  <label className="edit-label">Notes</label>
                  <button className="btn" style={{ fontSize: 11, padding: '3px 8px' }} onClick={rewriteNotes} disabled={aiLoading}>
                    {aiLoading ? 'Rewriting…' : 'Rewrite with AI'}
                  </button>
                </div>
                <textarea className="field" value={editFields.notes} onChange={e => setEditFields({...editFields, notes: e.target.value})} rows={2} />
              </div>
              <div className="edit-actions">
                <button className="btn btn-primary" onClick={saveEdit} disabled={saving}>{saving ? 'Saving…' : 'Save'}</button>
                <button className="btn" onClick={() => setEditing(false)}>Cancel</button>
              </div>

              <div style={{ borderTop: '1px dashed var(--line)', paddingTop: 14, marginTop: 6 }}>
                <div className="eyebrow"><span className="rule"></span>AI correction</div>
                <p style={{ color: 'var(--fg-mute)', fontSize: 12, margin: '6px 0 10px' }}>
                  Describe what's wrong in plain English. Claude will figure out what to change.
                </p>
                <textarea
                  className="field"
                  placeholder="e.g. 'This word actually means X, Jorn confirmed it yesterday'"
                  value={aiCorrection}
                  onChange={e => setAiCorrection(e.target.value)}
                  rows={2}
                />
                <div className="edit-actions">
                  <button className="btn btn-accent" onClick={submitAiCorrection} disabled={aiLoading || !aiCorrection.trim()}>
                    {aiLoading ? 'Thinking…' : 'Ask AI →'}
                  </button>
                </div>
                {aiResult && !aiResult.error && (
                  <div className="ai-result" style={{ marginTop: 10 }}>
                    <strong style={{ color: 'var(--conf-confirmed)' }}>Applied {aiResult.applied?.length || 0} change(s).</strong>
                    <p style={{ margin: '6px 0 0' }}>{aiResult.reasoning}</p>
                  </div>
                )}
                {aiResult && aiResult.error && (
                  <div className="ai-result" style={{ marginTop: 10, borderColor: 'var(--accent-warm)', background: 'rgba(244, 63, 94, 0.08)' }}>
                    <span style={{ color: 'var(--accent-warm)' }}>Error: {aiResult.error}</span>
                  </div>
                )}
              </div>
            </div>
          )}
        </div>
      )}
    </article>
  );
}

// ---------- Conjugations ----------

const PRONOUN_ORDER = ['Ëi', 'Te', 'Ton', 'Na', 'Tëi', 'Nai', 'La', 'Lai'];

function sortForms(forms) {
  const entries = Object.entries(forms || {});
  return entries.sort((a, b) => {
    const ai = PRONOUN_ORDER.indexOf(a[0]);
    const bi = PRONOUN_ORDER.indexOf(b[0]);
    if (ai !== -1 && bi !== -1) return ai - bi;
    if (ai !== -1) return -1;
    if (bi !== -1) return 1;
    return a[0].localeCompare(b[0]);
  });
}

function ConjugationsView({ data, pushToast, onRefresh }) {
  const dictWords = useMemoV(() => new Set((data.entries || []).map(e => e.word.toLowerCase())), [data.entries]);
  const list = useMemoV(() =>
    Object.values(data.conjugations || {}).filter(c => dictWords.has(c.infinitive.toLowerCase())),
    [data.conjugations, dictWords]
  );
  const orphans = useMemoV(() =>
    Object.values(data.conjugations || {}).filter(c => !dictWords.has(c.infinitive.toLowerCase())),
    [data.conjugations, dictWords]
  );
  const [editKey, setEditKey] = useStateV(null);
  const [editInf, setEditInf] = useStateV('');
  const [editForms, setEditForms] = useStateV({});
  const [editNotes, setEditNotes] = useStateV('');
  const [newPron, setNewPron] = useStateV('');
  const [saving, setSaving] = useStateV(false);
  const admin = isAdminMode();

  function startEdit(c) {
    setEditForms({ ...c.forms });
    setEditNotes(c.notes || '');
    setEditInf(c.infinitive);
    setEditKey(c.infinitive);
    setNewPron('');
  }

  function removePron(pron) {
    const f = { ...editForms };
    delete f[pron];
    setEditForms(f);
  }

  function addPron() {
    const p = newPron.trim();
    if (!p || p in editForms) return;
    setEditForms({ ...editForms, [p]: '' });
    setNewPron('');
  }

  async function saveEdit() {
    setSaving(true);
    try {
      const body = { forms: editForms, notes: editNotes };
      if (editInf !== editKey) body.infinitive = editInf;
      const res = await fetch(`/api/conjugation/${encodeURIComponent(editKey)}`, {
        method: 'PATCH',
        headers: adminHeaders({ 'Content-Type': 'application/json' }),
        body: JSON.stringify(body),
      });
      if (res.status === 401) { promptAdminToken(); setSaving(false); return; }
      if (!res.ok) throw new Error('Save failed');
      setEditKey(null);
      pushToast?.({ kind: 'confirmed', title: 'Saved', body: 'Conjugation updated.' });
      onRefresh?.();
    } catch (err) {
      pushToast?.({ kind: 'error', title: 'Error', body: err.message });
    }
    setSaving(false);
  }

  async function deleteConj(c) {
    if (!window.confirm(`Delete conjugation "${c.infinitive}"? This cannot be undone.`)) return;
    try {
      const res = await fetch(`/api/conjugation/${encodeURIComponent(c.infinitive)}`, { method: 'DELETE', headers: adminHeaders() });
      if (res.ok) { pushToast?.({ kind: 'confirmed', title: 'Deleted', body: `${c.infinitive} removed.` }); onRefresh?.(); }
    } catch {}
  }

  function renderCard(c) {
    const isIrregular = c.notes && c.notes.toLowerCase().includes('irregular');
    const isEditing = editKey === c.infinitive;
    return (
      <article key={c.infinitive} className={`conj-card ${isIrregular ? 'irregular' : ''}`}>
        <header className="conj-head">
          <div>
            <div className="eyebrow"><span className="rule"></span>infinitive</div>
            {isEditing ? (
              <input className="field field-mono" value={editInf} onChange={e => setEditInf(e.target.value)} style={{ fontSize: 18, width: '100%', marginTop: 2 }} />
            ) : (
              <div className="conj-inf mono serif">{c.infinitive}</div>
            )}
          </div>
          {isIrregular && !isEditing && <span className="conj-flag">irregular</span>}
        </header>
        {isEditing ? (
          <div className="edit-panel" style={{ margin: '8px 0' }}>
            {sortForms(editForms).map(([pron, form]) => (
              <div key={pron} className="edit-row-inline" style={{ gap: 6 }}>
                <label className="edit-label" style={{ width: 40, flexShrink: 0 }}>{pron}</label>
                <input className="field field-mono" value={form} onChange={e => setEditForms({ ...editForms, [pron]: e.target.value })} style={{ flex: 1 }} />
                <button className="btn" style={{ fontSize: 10, padding: '2px 5px', color: 'var(--accent-warm)' }} onClick={() => removePron(pron)} title="Remove">&times;</button>
              </div>
            ))}
            <div className="edit-row-inline" style={{ gap: 6, marginTop: 4 }}>
              <input className="field field-mono" placeholder="New pronoun…" value={newPron} onChange={e => setNewPron(e.target.value)} onKeyDown={e => e.key === 'Enter' && addPron()} style={{ width: 100 }} />
              <button className="btn" style={{ fontSize: 11, padding: '3px 8px' }} onClick={addPron}>+ Add</button>
            </div>
            <div className="edit-row">
              <label className="edit-label">Notes</label>
              <input className="field" value={editNotes} onChange={e => setEditNotes(e.target.value)} />
            </div>
            <div className="edit-actions">
              <button className="btn btn-primary" onClick={saveEdit} disabled={saving}>{saving ? 'Saving…' : 'Save'}</button>
              <button className="btn" onClick={() => setEditKey(null)}>Cancel</button>
            </div>
          </div>
        ) : (
          <>
            <table className="conj-table">
              <tbody>
                {sortForms(c.forms).map(([pron, form]) => (
                  <tr key={pron}>
                    <td className="conj-pron mono">{pron}</td>
                    <td className="conj-form mono">{form}</td>
                  </tr>
                ))}
              </tbody>
            </table>
            {c.notes && <p className="conj-notes serif" style={{ fontStyle: 'italic' }}>{c.notes}</p>}
            {admin && (
              <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
                <button className="btn" style={{ fontSize: 11, padding: '3px 8px' }} onClick={() => startEdit(c)}>Edit</button>
                <button className="btn" style={{ fontSize: 11, padding: '3px 8px', color: 'var(--accent-warm)' }} onClick={() => deleteConj(c)}>Delete</button>
              </div>
            )}
          </>
        )}
      </article>
    );
  }

  return (
    <div>
      <div className="page-intro">
        <div className="eyebrow"><span className="rule"></span>Verb tables</div>
        <h2 className="serif page-title">Conjugations &amp; their misbehaviour</h2>
        <p className="page-lede">
          Six pronouns, one tense (we haven't agreed on the others yet). Irregular verbs are marked.
          Most -ble verbs are irregular as a class — see grammar rule §8.
        </p>
      </div>
      <div className="conj-grid">
        {list.map(renderCard)}
      </div>
      {admin && orphans.length > 0 && (
        <>
          <div className="eyebrow" style={{ marginTop: 32 }}><span className="rule"></span>Orphaned (not in dictionary)</div>
          <div className="conj-grid" style={{ opacity: 0.6 }}>
            {orphans.map(renderCard)}
          </div>
        </>
      )}
    </div>
  );
}

// ---------- Grammar ----------

function GrammarView({ data, pushToast, onRefresh }) {
  const [editId, setEditId] = useStateV(null);
  const [editFields, setEditFields] = useStateV({});
  const [saving, setSaving] = useStateV(false);

  const sorted = [...(data.grammar_rules || [])].sort(
    (a, b) => CONF_ORDER.indexOf(a.confidence) - CONF_ORDER.indexOf(b.confidence)
  );

  function startEdit(r) {
    setEditFields({ rule: r.rule, detail: r.detail, confidence: r.confidence });
    setEditId(r.id);
  }

  async function saveEdit() {
    setSaving(true);
    try {
      const res = await fetch(`/api/grammar/${editId}`, {
        method: 'PATCH',
        headers: adminHeaders({ 'Content-Type': 'application/json' }),
        body: JSON.stringify(editFields),
      });
      if (res.status === 401) { promptAdminToken(); setSaving(false); return; }
      if (!res.ok) throw new Error('Save failed');
      setEditId(null);
      pushToast && pushToast({ kind: 'confirmed', title: 'Saved', body: 'Grammar rule updated.' });
      onRefresh && onRefresh();
    } catch (err) {
      pushToast && pushToast({ kind: 'error', title: 'Error', body: err.message });
    }
    setSaving(false);
  }

  async function deleteRule(r) {
    if (!window.confirm(`Delete grammar rule "${r.rule}"? This cannot be undone.`)) return;
    try {
      const res = await fetch(`/api/grammar/${r.id}`, { method: 'DELETE', headers: adminHeaders() });
      if (res.ok) {
        pushToast && pushToast({ kind: 'confirmed', title: 'Deleted', body: `"${r.rule}" removed.` });
        onRefresh && onRefresh();
      }
    } catch {}
  }

  const admin = isAdminMode();

  return (
    <div>
      <div className="page-intro">
        <div className="eyebrow"><span className="rule"></span>Rules of the road</div>
        <h2 className="serif page-title">Grammar, in approximate order of certainty</h2>
        <p className="page-lede">
          The confirmed rules at the top are battle-tested. The guesses at the bottom are us reverse-engineering
          our own jokes.
        </p>
      </div>
      <ol className="rules-list">
        {sorted.map((r, i) => (
          <li key={r.id} className="rule-item">
            <div className="rule-num serif">§{String(i + 1).padStart(2, '0')}</div>
            <div className="rule-body">
              <div className="rule-head">
                <h3 className="rule-name serif">{r.rule}</h3>
                <span className={confChipClass(r.confidence)}>{r.confidence}</span>
              </div>
              {editId === r.id ? (
                <div className="edit-panel" style={{ marginTop: 8 }}>
                  <div className="edit-row">
                    <label className="edit-label">Rule name</label>
                    <input className="field" value={editFields.rule} onChange={e => setEditFields({...editFields, rule: e.target.value})} />
                  </div>
                  <div className="edit-row">
                    <label className="edit-label">Detail</label>
                    <textarea className="field" value={editFields.detail} onChange={e => setEditFields({...editFields, detail: e.target.value})} rows={3} />
                  </div>
                  <div className="edit-row">
                    <label className="edit-label">Confidence</label>
                    <select className="field field-mono" value={editFields.confidence} onChange={e => setEditFields({...editFields, confidence: e.target.value})}>
                      {CONF_ORDER.map(c => <option key={c} value={c}>{c}</option>)}
                    </select>
                  </div>
                  <div className="edit-actions">
                    <button className="btn btn-primary" onClick={saveEdit} disabled={saving}>{saving ? 'Saving…' : 'Save'}</button>
                    <button className="btn" onClick={() => setEditId(null)}>Cancel</button>
                  </div>
                </div>
              ) : (
                <>
                  <p className="rule-detail">{r.detail}</p>
                  {admin && (
                    <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
                      <button className="btn" style={{ fontSize: 11, padding: '3px 8px' }} onClick={() => startEdit(r)}>✏ Edit</button>
                      <button className="btn" style={{ fontSize: 11, padding: '3px 8px', color: 'var(--accent-warm)' }} onClick={() => deleteRule(r)}>🗑 Delete</button>
                    </div>
                  )}
                </>
              )}
            </div>
          </li>
        ))}
      </ol>
    </div>
  );
}

// ---------- Examples ----------

function ExamplesView({ data, pushToast, onRefresh }) {
  const [editId, setEditId] = useStateV(null);
  const [editFields, setEditFields] = useStateV({});
  const [saving, setSaving] = useStateV(false);
  const admin = isAdminMode();

  function startEdit(s) {
    setEditFields({ conlang: s.conlang, english: s.english, confidence: s.confidence });
    setEditId(s.id);
  }

  async function saveEdit() {
    setSaving(true);
    try {
      const res = await fetch(`/api/example/${editId}`, {
        method: 'PATCH',
        headers: adminHeaders({ 'Content-Type': 'application/json' }),
        body: JSON.stringify(editFields),
      });
      if (res.status === 401) { promptAdminToken(); setSaving(false); return; }
      if (!res.ok) throw new Error('Save failed');
      setEditId(null);
      pushToast?.({ kind: 'confirmed', title: 'Saved', body: 'Example updated.' });
      onRefresh?.();
    } catch (err) {
      pushToast?.({ kind: 'error', title: 'Error', body: err.message });
    }
    setSaving(false);
  }

  async function deleteExample(s) {
    if (!window.confirm(`Delete example "${s.conlang}"? This cannot be undone.`)) return;
    try {
      const res = await fetch(`/api/example/${s.id}`, { method: 'DELETE', headers: adminHeaders() });
      if (res.ok) { pushToast?.({ kind: 'confirmed', title: 'Deleted', body: 'Example removed.' }); onRefresh?.(); }
    } catch {}
  }

  return (
    <div>
      <div className="page-intro">
        <div className="eyebrow"><span className="rule"></span>Parallel text</div>
        <h2 className="serif page-title">Example sentences</h2>
        <p className="page-lede">
          Real lines from the channel, paired with their English. Click any source to read the original.
        </p>
      </div>
      <ul className="ex-list">
        {(data.example_sentences || []).map((s, idx) => {
          const msgId = s.source_message?.message_id;
          const isEditing = editId === s.id;
          return (
            <li key={s.id || idx} className="ex-item" style={isEditing ? { display: 'block' } : undefined}>
              {isEditing ? (
                <div className="edit-panel">
                  <div className="edit-row">
                    <label className="edit-label">Seamenese</label>
                    <input className="field" value={editFields.conlang} onChange={e => setEditFields({ ...editFields, conlang: e.target.value })} />
                  </div>
                  <div className="edit-row">
                    <label className="edit-label">English</label>
                    <input className="field" value={editFields.english} onChange={e => setEditFields({ ...editFields, english: e.target.value })} />
                  </div>
                  <div className="edit-row">
                    <label className="edit-label">Confidence</label>
                    <select className="field field-mono" value={editFields.confidence} onChange={e => setEditFields({ ...editFields, confidence: e.target.value })}>
                      {CONF_ORDER.map(c => <option key={c} value={c}>{c}</option>)}
                    </select>
                  </div>
                  <div className="edit-actions">
                    <button className="btn btn-primary" onClick={saveEdit} disabled={saving}>{saving ? 'Saving…' : 'Save'}</button>
                    <button className="btn" onClick={() => setEditId(null)}>Cancel</button>
                  </div>
                </div>
              ) : (
                <>
                  <div className="ex-num mono">{String(idx + 1).padStart(2, '0')}</div>
                  <div className="ex-conlang serif">{s.conlang}</div>
                  <div className="ex-en serif" style={{ fontStyle: 'italic' }}>"{s.english}"</div>
                  <div className="ex-meta">
                    <span className={confChipClass(s.confidence)}>{s.confidence}</span>
                    {msgId && (
                      <a className="ex-link" href={discordLink(data.meta, msgId)} target="_blank" rel="noreferrer">source ↗</a>
                    )}
                    {admin && (
                      <span style={{ display: 'inline-flex', gap: 6, marginLeft: 8 }}>
                        <button className="btn" style={{ fontSize: 11, padding: '2px 6px' }} onClick={() => startEdit(s)}>Edit</button>
                        <button className="btn" style={{ fontSize: 11, padding: '2px 6px', color: 'var(--accent-warm)' }} onClick={() => deleteExample(s)}>Delete</button>
                      </span>
                    )}
                  </div>
                </>
              )}
            </li>
          );
        })}
      </ul>
    </div>
  );
}

Object.assign(window, { DictionaryView, ConjugationsView, GrammarView, ExamplesView });
