// web/screens/admin.jsx — Админка фазы 2 (И3): «Тарифы и группы» + «Пользователи».
// Работает поверх api.admin.*. Только для админа (роут гейтится в app.jsx).

// ── маленькая полоска квоты ──
function QBar({ used, limit }) {
  const unlimited = limit == null || limit < 0;
  const pct = unlimited ? 0 : Math.min(100, Math.round((used / Math.max(1, limit)) * 100));
  const full = !unlimited && used >= limit;
  return (
    <div style={{ minWidth: 90 }}>
      <div style={{ fontSize: 11.5, fontFamily: "monospace", color: "var(--ink-2)" }}>
        {used} {unlimited ? "/ ∞" : "/ " + limit}
      </div>
      {!unlimited && (
        <div style={{ height: 5, background: "var(--surface-3, #eee)", borderRadius: 9, marginTop: 2 }}>
          <div style={{ width: pct + "%", height: "100%", borderRadius: 9,
            background: full ? "var(--warn, #d28a52)" : "var(--accent, #3f6f52)" }} />
        </div>
      )}
    </div>
  );
}

// ════════════════════════════ Тарифы и группы ════════════════════════════
function AdminPlansScreen() {
  const [tab, setTab] = React.useState("groups");
  return (
    <div className="page" style={{ padding: 22 }}>
      <h1 style={{ fontSize: 20, margin: "0 0 4px" }}>Тарифы и группы</h1>
      <div className="muted" style={{ fontSize: 12.5, marginBottom: 16 }}>
        Группа прав = что пользователь <i>умеет</i> · тариф = сколько <i>можно</i>
      </div>
      <div style={{ display: "flex", gap: 6, marginBottom: 18, borderBottom: "1px solid var(--line, #e4e6e1)" }}>
        {[["groups", "Группы прав"], ["tariffs", "Тарифы"], ["trial", "Триал"]].map(([id, label]) => (
          <button key={id} onClick={() => setTab(id)}
            style={{ background: "none", border: "none", cursor: "pointer", padding: "8px 12px",
              fontSize: 13, fontWeight: tab === id ? 600 : 400,
              color: tab === id ? "var(--ink, #222)" : "var(--ink-3, #888)",
              borderBottom: tab === id ? "2px solid var(--accent, #3f6f52)" : "2px solid transparent" }}>
            {label}
          </button>
        ))}
      </div>
      {tab === "groups" && <GroupsTab />}
      {tab === "tariffs" && <TariffsTab />}
      {tab === "trial" && <TrialTab />}
    </div>
  );
}

function GroupsTab() {
  const [data, reload] = useAsync(() => Promise.all([
    api.admin.OptionsCatalog(), api.admin.Groups(), api.admin.Tariffs(),
  ]).then(([options, groups, tariffs]) => ({ options, groups, tariffs })), []);
  const [, toast] = useToasts();
  const [groups, setGroups] = React.useState(null);
  const [saving, setSaving] = React.useState(false);
  const [newGroupName, setNewGroupName] = React.useState("");

  React.useEffect(() => {
    if (data.data) setGroups(JSON.parse(JSON.stringify(data.data.groups)));
  }, [data.data]);

  if (data.loading || !groups) return <div className="muted"><Spinner /> загружаем…</div>;
  if (data.error) return <div style={{ color: "var(--danger,#c0392b)" }}>{String(data.error.message)}</div>;

  const { options, tariffs } = data.data;
  const perm = groups.filter((g) => g.groupType !== "membership");
  const membership = groups.filter((g) => g.groupType === "membership");

  // разделы каталога
  const sections = [];
  const seen = {};
  options.forEach((o) => { if (!seen[o.section]) { seen[o.section] = []; sections.push(o.section); } seen[o.section].push(o); });

  const toggle = (gid, code) => setGroups((gs) => gs.map((g) => {
    if (g.id !== gid) return g;
    const opts = new Set(g.options || []);
    opts.has(code) ? opts.delete(code) : opts.add(code);
    return { ...g, options: [...opts] };
  }));
  const setLimit = (gid, field, v) => setGroups((gs) => gs.map((g) =>
    g.id === gid ? { ...g, [field]: v === "" ? null : Math.max(0, parseInt(v, 10) || 0) } : g));
  const setBase = (gid, v) => setGroups((gs) => gs.map((g) =>
    g.id === gid ? { ...g, baseTariffId: v ? parseInt(v, 10) : null } : g));

  const save = async () => {
    setSaving(true);
    try {
      for (const g of perm) {
        await api.admin.SaveGroup({
          id: g.id, name: g.name, groupType: "permission", blockName: g.blockName,
          baseTariffId: g.baseTariffId, recognitionRetries: g.recognitionRetries,
          analysisRetries: g.analysisRetries, options: g.options || [],
        });
      }
      toast("Группы сохранены", "ok");
      reload();
    } catch (e) { toast("Ошибка: " + e.message, "error"); }
    finally { setSaving(false); }
  };

  const addGroup = async () => {
    const name = newGroupName.trim();
    if (!name) return;
    try { await api.admin.SaveGroup({ id: 0, name, groupType: "permission", options: [] }); setNewGroupName(""); reload(); }
    catch (e) { toast("Ошибка: " + e.message, "error"); }
  };

  return (
    <div>
      <div style={{ display: "flex", gap: 8, marginBottom: 12, alignItems: "center" }}>
        <input value={newGroupName} onChange={(e) => setNewGroupName(e.target.value)}
          onKeyDown={(e) => { if (e.key === "Enter") addGroup(); }}
          placeholder="Новая группа прав…" style={{ ...inp, width: 220 }} />
        <button onClick={addGroup} disabled={!newGroupName.trim()} style={btnGhost}>+ Добавить группу</button>
      </div>
      <div className="muted" style={{ fontSize: 12.5, marginBottom: 12 }}>
        Строки — права, колонки — группы. Галки складываются: возможность доступна,
        если включена хотя бы в одной группе пользователя. Два лимита-пула на группу.
      </div>
      <div className="perm-matrix-wrap">
        <table className="perm-matrix">
          <thead>
            <tr>
              <th className="pm-corner">Право</th>
              {perm.map((g) => (
                <th key={g.id} className="pm-group-col">
                  <div className="pm-group-name">{g.name}</div>
                  <div className="pm-group-meta">{g.memberCount || 0} польз.</div>
                  <div className="pm-glimits">
                    <label title="лимит распознавания">Р<input type="number" min="0"
                      value={g.recognitionRetries ?? ""} placeholder="∞" onChange={(e) => setLimit(g.id, "recognitionRetries", e.target.value)} /></label>
                    <label title="лимит анализа">А<input type="number" min="0"
                      value={g.analysisRetries ?? ""} placeholder="∞" onChange={(e) => setLimit(g.id, "analysisRetries", e.target.value)} /></label>
                  </div>
                  <select className="pm-base" value={g.baseTariffId ?? ""} onChange={(e) => setBase(g.id, e.target.value)}>
                    <option value="">— мин.тариф —</option>
                    {tariffs.map((t) => <option key={t.id} value={t.id}>{t.name}</option>)}
                  </select>
                </th>
              ))}
            </tr>
          </thead>
          <tbody>
            {sections.map((sec) => (
              <React.Fragment key={sec}>
                <tr className="pm-section-row"><td colSpan={perm.length + 1}>{sec}</td></tr>
                {seen[sec].map((o) => (
                  <tr key={o.code} className="pm-row">
                    <td className="pm-opt">
                      <div className="pm-opt-inner">
                        <span>{o.label}</span>
                        {o.limitKind && <span className="pm-limit-tag">лимит · {o.limitKind === "recognition" ? "Р" : "А"}</span>}
                      </div>
                    </td>
                    {perm.map((g) => {
                      const on = (g.options || []).includes(o.code);
                      return (
                        <td key={g.id} className={"pm-cell" + (on ? " on" : "")}>
                          <div className="pm-cell-inner">
                            <button className={"pm-check" + (on ? " on" : "")} onClick={() => toggle(g.id, o.code)} aria-label={on ? "включено" : "выключено"}>
                              {on && <Icon name="check" size={13} stroke={2.6} />}
                            </button>
                          </div>
                        </td>
                      );
                    })}
                  </tr>
                ))}
              </React.Fragment>
            ))}
          </tbody>
        </table>
      </div>

      {membership.length > 0 && (
        <div style={{ marginTop: 16 }}>
          <div className="muted" style={{ fontSize: 11.5, textTransform: "uppercase", marginBottom: 6 }}>Membership-группы (только участники)</div>
          {membership.map((g) => (
            <span key={g.id} style={{ display: "inline-block", marginRight: 8, padding: "4px 10px", border: "1px solid var(--line,#e4e6e1)", borderRadius: 99, fontSize: 12 }}>
              {g.name} · {g.memberCount || 0}
            </span>
          ))}
        </div>
      )}

      <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 16 }}>
        <button className="btn" onClick={save} disabled={saving} style={btnPrimary}>
          {saving ? "Сохраняем…" : "Сохранить изменения"}
        </button>
      </div>
    </div>
  );
}

function TariffsTab() {
  const [data, reload] = useAsync(() => api.admin.Tariffs(), []);
  const [, toast] = useToasts();
  if (data.loading) return <div className="muted"><Spinner /> загружаем…</div>;
  const tariffs = data.data || [];

  const save = async (t) => { try { await api.admin.SaveTariff(t); toast("Сохранено", "ok"); reload(); } catch (e) { toast(e.message, "error"); } };
  const del = async (id) => { if (!confirm("Удалить тариф?")) return; try { await api.admin.DeleteTariff(id); reload(); } catch (e) { toast(e.message, "error"); } };

  return (
    <div style={{ maxWidth: 760 }}>
      <div className="muted" style={{ fontSize: 12.5, marginBottom: 12 }}>
        Квоты: лимит файлов на диске и распознавание (месяц / пакет). «Лимит/день» — дневной потолок
        распознаваний для распределения нагрузки: <b>0</b> = авто (10% от месячной квоты), <b>-1</b> = без лимита,
        число = вручную. Серия, не влезающая в дневной остаток, делится на «(1)» (в работу) и «(2)» (на завтра).
      </div>
      <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 13 }}>
        <thead><tr style={{ textAlign: "left", color: "var(--ink-3,#888)", fontSize: 11.5 }}>
          <th style={tdh}>Тариф</th><th style={tdh}>Файлов</th><th style={tdh}>Распозн.</th><th style={tdh}>Лимит/день</th><th style={tdh}>Тип</th><th style={tdh}>Цена</th><th></th>
        </tr></thead>
        <tbody>
          {tariffs.map((t) => <TariffRow key={t.id} t={t} onSave={save} onDel={del} />)}
          <TariffRow t={{ id: 0, name: "", fileLimit: 50, recognitionQuota: 30, recognitionDailyLimit: 0, quotaType: "month", price: 0 }} isNew onSave={save} />
        </tbody>
      </table>
    </div>
  );
}

function TariffRow({ t, isNew, onSave, onDel }) {
  const [v, setV] = React.useState(t);
  React.useEffect(() => setV(t), [t.id]);
  const f = (k) => (e) => setV({ ...v, [k]: k === "name" || k === "quotaType" ? e.target.value : parseInt(e.target.value, 10) || 0 });
  return (
    <tr style={{ borderTop: "1px solid var(--line,#eee)" }}>
      <td style={td}><input style={inp} value={v.name} placeholder="название" onChange={f("name")} /></td>
      <td style={td}><input style={{ ...inp, width: 70 }} type="number" value={v.fileLimit} onChange={f("fileLimit")} /></td>
      <td style={td}><input style={{ ...inp, width: 70 }} type="number" value={v.recognitionQuota} onChange={f("recognitionQuota")} /></td>
      <td style={td}>
        <input style={{ ...inp, width: 60 }} type="number" value={v.recognitionDailyLimit}
               onChange={f("recognitionDailyLimit")}
               title="0 = авто (10% от месячной квоты) · -1 = без дневного лимита · число = вручную" />
        <div className="muted" style={{ fontSize: 9.5, marginTop: 1 }}>
          {v.recognitionDailyLimit === -1 ? "без лимита"
            : v.recognitionDailyLimit > 0 ? "вручную"
            : `авто: ${v.recognitionQuota > 0 ? Math.max(1, Math.ceil(v.recognitionQuota / 10)) : 0}/д`}
        </div>
      </td>
      <td style={td}>
        <select value={v.quotaType} onChange={f("quotaType")} style={inp}>
          <option value="month">в месяц</option><option value="pack">пакет</option>
        </select>
      </td>
      <td style={td}><input style={{ ...inp, width: 70 }} type="number" value={v.price} onChange={f("price")} /></td>
      <td style={td}>
        <button style={btnSm} onClick={() => onSave(v)} disabled={!v.name}>{isNew ? "+ Добавить" : "Сохранить"}</button>
        {!isNew && <button style={{ ...btnSm, color: "var(--danger,#c0392b)" }} onClick={() => onDel(v.id)}>✕</button>}
      </td>
    </tr>
  );
}

function TrialTab() {
  const [data] = useAsync(() => Promise.all([api.admin.TrialSettings(), api.admin.Tariffs()]).then(([ts, tariffs]) => ({ ts, tariffs })), []);
  const [, toast] = useToasts();
  const [v, setV] = React.useState(null);
  React.useEffect(() => { if (data.data) setV(data.data.ts); }, [data.data]);
  if (data.loading || !v) return <div className="muted"><Spinner /> загружаем…</div>;
  const tariffs = data.data.tariffs;
  const save = async () => { try { await api.admin.SaveTrialSettings(v); toast("Сохранено", "ok"); } catch (e) { toast(e.message, "error"); } };

  return (
    <div style={{ maxWidth: 520 }}>
      <div className="muted" style={{ fontSize: 12.5, marginBottom: 16 }}>
        Новый пользователь при регистрации получает пробный период. Кончится раньше — по фото или дням — падает на тариф «по окончании».
      </div>
      <Field label="Тариф на триал">
        <select style={inp} value={v.tariffId} onChange={(e) => setV({ ...v, tariffId: parseInt(e.target.value, 10) })}>
          <option value={0}>—</option>{tariffs.map((t) => <option key={t.id} value={t.id}>{t.name}</option>)}
        </select>
      </Field>
      <Field label="Лимит фото"><input style={inp} type="number" value={v.photos} onChange={(e) => setV({ ...v, photos: parseInt(e.target.value, 10) || 0 })} /></Field>
      <Field label="Срок (дней)"><input style={inp} type="number" value={v.days} onChange={(e) => setV({ ...v, days: parseInt(e.target.value, 10) || 0 })} /></Field>
      <Field label="Тариф «по окончании»">
        <select style={inp} value={v.fallbackTariffId} onChange={(e) => setV({ ...v, fallbackTariffId: parseInt(e.target.value, 10) })}>
          <option value={0}>—</option>{tariffs.map((t) => <option key={t.id} value={t.id}>{t.name}</option>)}
        </select>
      </Field>
      <div style={{ marginTop: 14, padding: "10px 12px", background: "var(--surface-2,#f6f6f4)", borderRadius: 8 }}>
        <label style={{ display: "flex", gap: 8, alignItems: "flex-start", cursor: "pointer" }}>
          <input type="checkbox" style={{ marginTop: 2 }} checked={!!v.requireApproval}
                 onChange={(e) => setV({ ...v, requireApproval: e.target.checked })} />
          <span>
            <b>Требовать ручную активацию новых</b>
            <div className="muted" style={{ fontSize: 12, marginTop: 2 }}>
              Новые пользователи после регистрации заблокированы и НЕ получают триал, пока ты не активируешь их в списке «Пользователи» (кнопка «разблок.»). Триал стартует с момента активации. Выключи — снова авто-триал сразу при регистрации.
            </div>
          </span>
        </label>
      </div>
      <button style={{ ...btnPrimary, marginTop: 14 }} onClick={save}>Сохранить настройки триала</button>
    </div>
  );
}

// ════════════════════════════ Пользователи ════════════════════════════
function AdminUsersScreen({ onImpersonate }) {
  const [data, reload] = useAsync(() => Promise.all([api.admin.Users(), api.admin.Groups(), api.admin.Tariffs()])
    .then(([users, groups, tariffs]) => ({ users, groups, tariffs })), []);
  const [, toast] = useToasts();
  const [q, setQ] = React.useState("");
  const [tariffF, setTariffF] = React.useState("");
  const [groupF, setGroupF] = React.useState("");
  const [editing, setEditing] = React.useState(null); // юзер для модалки групп
  if (data.loading) return <div className="page" style={{ padding: 22 }}><div className="muted"><Spinner /> загружаем…</div></div>;
  if (data.error) return <div className="page" style={{ padding: 22, color: "var(--danger,#c0392b)" }}>{String(data.error.message)}</div>;

  const { users, groups, tariffs } = data.data;
  const filtered = users.filter((u) => {
    if (q && !(u.login + " " + (u.email || "")).toLowerCase().includes(q.toLowerCase())) return false;
    if (tariffF && u.tariffLabel !== tariffF) return false;
    if (groupF && !(u.groupIds || []).includes(parseInt(groupF, 10))) return false;
    return true;
  });
  const tariffLabels = [...new Set(users.map((u) => u.tariffLabel))];

  const block = async (u) => { try { await api.admin.BlockUser(u.id, !u.blocked); reload(); } catch (e) { toast(e.message, "error"); } };
  const saveNote = async (u, note) => { try { await api.admin.SetUserNote(u.id, note); toast("Заметка сохранена", "ok"); reload(); } catch (e) { toast(e.message, "error"); } };
  const setTariff = async (u, tid) => { try { await api.admin.SetUserTariff(u.id, tid); toast("Тариф изменён", "ok"); reload(); } catch (e) { toast(e.message, "error"); } };
  const saveGroups = async (gids) => { try { await api.admin.SetUserGroups(editing.id, gids); toast("Группы обновлены", "ok"); setEditing(null); reload(); } catch (e) { toast(e.message, "error"); } };

  return (
    <div className="page" style={{ padding: 22 }}>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12 }}>
        <h1 style={{ fontSize: 20, margin: 0 }}>Пользователи <span className="muted" style={{ fontSize: 13, fontWeight: 400 }}>· {filtered.length}/{users.length}</span></h1>
        <input style={{ ...inp, width: 220 }} placeholder="Логин или почта…" value={q} onChange={(e) => setQ(e.target.value)} />
      </div>
      <div style={{ display: "flex", gap: 10, marginBottom: 14, fontSize: 12.5, alignItems: "center" }}>
        <span className="muted">Фильтр:</span>
        <select style={inp} value={tariffF} onChange={(e) => setTariffF(e.target.value)}>
          <option value="">все тарифы</option>
          {tariffLabels.map((l) => <option key={l} value={l}>{l}</option>)}
        </select>
        <select style={inp} value={groupF} onChange={(e) => setGroupF(e.target.value)}>
          <option value="">все группы</option>
          {groups.map((g) => <option key={g.id} value={g.id}>{g.name}</option>)}
        </select>
        {(tariffF || groupF || q) && <button style={btnSm} onClick={() => { setTariffF(""); setGroupF(""); setQ(""); }}>сбросить</button>}
      </div>
      <table style={{ width: "100%", borderCollapse: "collapse", fontSize: 12.5 }}>
        <thead><tr style={{ textAlign: "left", color: "var(--ink-3,#888)", fontSize: 11.5 }}>
          <th style={tdh}>Пользователь</th><th style={tdh}>Тариф</th><th style={tdh}>Распознавание</th><th style={tdh}>Файлы</th><th style={tdh}>Группы</th><th style={tdh}>Серий</th><th></th>
        </tr></thead>
        <tbody>
          {filtered.map((u) => (
            <tr key={u.id} style={{ borderTop: "1px solid var(--line,#eee)", opacity: u.blocked ? 0.5 : 1 }}>
              <td style={td}>
                <div style={{ fontWeight: 600 }}>{u.login}{u.isAdmin && <span style={{ fontSize: 10, marginLeft: 6, color: "var(--accent,#3f6f52)" }}>админ</span>}{u.blocked && <span style={{ fontSize: 10, marginLeft: 6, color: "var(--danger,#c0392b)" }}>заблок.</span>}</div>
                <div className="muted" style={{ fontSize: 11 }}>{u.email || "—"}</div>
                {/* Заметка админа (видна только админу): имя/причина бана. Сохраняется по blur/Enter. */}
                <input
                  style={{ marginTop: 3, width: "100%", maxWidth: 200, fontSize: 11, padding: "2px 6px",
                           border: "1px solid var(--line,#e5e5e5)", borderRadius: 5, color: "var(--ink-2,#555)" }}
                  placeholder="заметка: имя / причина…"
                  title="Видно только тебе. Например реальное имя друга под непонятным логином, или за что забанен."
                  defaultValue={u.note || ""}
                  onBlur={(e) => { const t = e.target.value.trim(); if (t !== (u.note || "")) saveNote(u, t); }}
                  onKeyDown={(e) => { if (e.key === "Enter") e.target.blur(); }}
                />
              </td>
              <td style={td}>
                {u.onTrial && <div className="muted" style={{ fontSize: 10, marginBottom: 2 }}>триал: {u.trialDaysLeft}дн · {u.trialFilesLeft ?? "?"}ф</div>}
                {u.isAdmin ? <span className="muted">Админ</span> : (
                  <select style={{ ...inp, fontSize: 11.5 }} value={0} onChange={(e) => setTariff(u, parseInt(e.target.value, 10))}>
                    <option value={0}>{u.tariffLabel} ▾</option>
                    {tariffs.map((t) => <option key={t.id} value={t.id}>→ {t.name}</option>)}
                  </select>
                )}
              </td>
              <td style={td}>
                <QBar used={u.recognitionUsed} limit={u.isAdmin ? -1 : u.recognitionQuota} />
                {!u.isAdmin && <div className="muted" style={{ fontSize: 10 }}>{u.onTrial ? "триал" : u.quotaType === "pack" ? "разовый пакет" : "в месяц"}</div>}
              </td>
              <td style={td}><QBar used={u.filesUsed} limit={u.fileLimit} /></td>
              <td style={td}>
                <div style={{ display: "flex", flexWrap: "wrap", gap: 4, alignItems: "center" }}>
                  {(u.groupNames || []).map((n, i) => <span key={i} style={{ display: "inline-block", padding: "1px 7px", background: "var(--surface-2,#f0f1ee)", borderRadius: 99, fontSize: 11 }}>{n}</span>)}
                  <button style={btnSm} onClick={() => setEditing(u)}>изменить</button>
                </div>
              </td>
              <td style={{ ...td, fontFamily: "monospace" }}>{u.shootCount}</td>
              <td style={td}>
                {!u.isAdmin && <>
                  <button style={btnSm} onClick={() => onImpersonate(u)}>смотреть как</button>
                  <button style={{ ...btnSm, color: u.blocked ? "var(--accent,#3f6f52)" : "var(--danger,#c0392b)" }} onClick={() => block(u)}>{u.blocked ? "разблок." : "блок"}</button>
                </>}
              </td>
            </tr>
          ))}
        </tbody>
      </table>
      {editing && <GroupsModal user={editing} groups={groups} onSave={saveGroups} onClose={() => setEditing(null)} />}
    </div>
  );
}

// Модалка редактирования групп пользователя — мультивыбор add/remove, сохраняет
// весь набор разом (снятая галка = убрать из группы).
function GroupsModal({ user, groups, onSave, onClose }) {
  const [sel, setSel] = React.useState(() => new Set(user.groupIds || []));
  const toggle = (gid) => setSel((s) => { const n = new Set(s); n.has(gid) ? n.delete(gid) : n.add(gid); return n; });
  return (
    <div onClick={onClose} style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,.35)", display: "flex", alignItems: "center", justifyContent: "center", zIndex: 50 }}>
      <div onClick={(e) => e.stopPropagation()} style={{ background: "#fff", borderRadius: 12, padding: 20, width: 360, maxHeight: "80vh", overflow: "auto", boxShadow: "0 10px 40px rgba(0,0,0,.18)" }}>
        <h3 style={{ margin: "0 0 4px", fontSize: 16 }}>Группы · {user.login}</h3>
        <div className="muted" style={{ fontSize: 12, marginBottom: 12 }}>Отметь группы и сохрани. Снятая галка — убрать из группы.</div>
        <div style={{ display: "flex", flexDirection: "column", gap: 7 }}>
          {groups.length === 0 && <div className="muted" style={{ fontSize: 12 }}>Групп ещё нет — создай в «Тарифы и группы».</div>}
          {groups.map((g) => (
            <label key={g.id} style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 13, cursor: "pointer" }}>
              <input type="checkbox" checked={sel.has(g.id)} onChange={() => toggle(g.id)} />
              {g.name}
              {g.groupType === "membership" && <span className="muted" style={{ fontSize: 11 }}>· membership</span>}
            </label>
          ))}
        </div>
        <div style={{ display: "flex", justifyContent: "flex-end", gap: 8, marginTop: 16 }}>
          <button style={btnGhost} onClick={onClose}>Отмена</button>
          <button style={btnPrimary} onClick={() => onSave([...sel])}>Сохранить</button>
        </div>
      </div>
    </div>
  );
}

// ── общие стили/мелочи ──
function Field({ label, children }) {
  return <div style={{ marginBottom: 14 }}><div style={{ fontSize: 12.5, color: "var(--ink-2,#555)", marginBottom: 5 }}>{label}</div>{children}</div>;
}
const inp = { padding: "6px 9px", fontSize: 12.5, border: "1px solid var(--line-2,#d7dad3)", borderRadius: 7, background: "#fff" };
const td = { padding: "8px 10px", verticalAlign: "middle" };
const tdh = { padding: "6px 10px" };
const btnPrimary = { padding: "9px 16px", fontSize: 13, fontWeight: 600, border: "none", borderRadius: 8, background: "var(--accent,#3f6f52)", color: "#fff", cursor: "pointer" };
const btnGhost = { padding: "9px 14px", fontSize: 13, border: "1px solid var(--line-2,#d7dad3)", borderRadius: 8, background: "#fff", cursor: "pointer" };
const btnSm = { padding: "3px 8px", fontSize: 11.5, border: "1px solid var(--line-2,#d7dad3)", borderRadius: 6, background: "#fff", cursor: "pointer", marginRight: 4 };

window.AdminPlansScreen = AdminPlansScreen;
window.AdminUsersScreen = AdminUsersScreen;
