/* P.E.S.T Web App — Admin panel (manage projects + token catalog) */
function Admin() {
  const [tab, setTab] = useState("projects");
  return (
    <div>
      <div className="page-head">
        <div className="eyebrow">Admin</div>
        <h1>Manage the <span className="grad-text">directory</span></h1>
        <p className="sub">Curate what everyone sees. Changes flow to all members' pages and dashboards.</p>
      </div>

      <div className="seg" role="tablist">
        <button className={"seg-opt" + (tab === "projects" ? " on" : "")} onClick={() => setTab("projects")}><window.AIcon.projects /> Projects</button>
        <button className={"seg-opt" + (tab === "tokens" ? " on" : "")} onClick={() => setTab("tokens")}><window.AIcon.dashboard /> Tokens</button>
        <button className={"seg-opt" + (tab === "members" ? " on" : "")} onClick={() => setTab("members")}><window.AIcon.members /> Members</button>
      </div>

      {tab === "projects" ? <ProjectsAdmin /> : tab === "tokens" ? <TokensAdmin /> : <UsersAdmin />}
    </div>
  );
}

/* ---------- shared: multi-category chips ---------- */
function CategoryChips({ value, onChange }) {
  const all = window.PEST_APP.CATEGORIES;
  const sel = new Set(value || []);
  const toggle = (c) => {
    if (sel.has(c)) sel.delete(c); else sel.add(c);
    onChange(Array.from(sel));
  };
  return (
    <div className="cat-row" style={{ flexWrap: "wrap" }}>
      {all.map((c) => (
        <button type="button" key={c} className={"chip" + (sel.has(c) ? " on" : "")} onClick={() => toggle(c)}>{c}</button>
      ))}
    </div>
  );
}

/* ---------- shared: file picker with preview ---------- */
function FilePickField({ file, onFile, currentLogo, label }) {
  const inpRef = useRef(null);
  const preview = file ? URL.createObjectURL(file) : (currentLogo ? window.resolveLogo(currentLogo) : null);
  return (
    <div className="form-row" style={{ gap: 12 }}>
      <div className="logo-preview">
        <div className="logo-tile">
          {preview ? <img src={preview} alt="preview" /> : <span style={{ fontSize: "0.8em", color: "var(--tx-faint)" }}>—</span>}
        </div>
        <div className="ref-slots-note">{label || "Logo"}</div>
      </div>
      <div className="field" style={{ flex: 1 }}>
        <label>Upload logo <span style={{ textTransform: "none", letterSpacing: 0 }}>(PNG/JPG/GIF/WEBP, max 2 MB)</span></label>
        <input ref={inpRef} type="file" accept="image/png,image/jpeg,image/gif,image/webp"
          onChange={(e) => onFile(e.target.files && e.target.files[0])} />
        {file && <button type="button" className="btn btn-sm" style={{ marginTop: 6 }} onClick={() => { onFile(null); if (inpRef.current) inpRef.current.value = ""; }}>Clear</button>}
      </div>
    </div>
  );
}

/* ============================================================
   PROJECTS ADMIN
   ============================================================ */
function ProjectsAdmin() {
  const store = useStore();
  const blank = { name: "", categories: [], desc: "", chains: "", website: "", x: "", discord: "", telegram: "" };
  const [f, setF] = useState(blank);
  const [file, setFile] = useState(null);
  const [busy, setBusy] = useState(false);
  const [editing, setEditing] = useState(null);
  const [q, setQ] = useState("");
  const [catFilter, setCatFilter] = useState("All");
  const set = (k, v) => setF((x) => ({ ...x, [k]: v }));

  const add = async () => {
    if (!f.name.trim()) { toast("Project needs a name", "err"); return; }
    if (!f.categories.length) { toast("Pick at least one category", "err"); return; }
    setBusy(true);
    try {
      const id = f.name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") + "-" + Math.random().toString(36).slice(2, 5);
      const links = {};
      ["website", "x", "discord", "telegram"].forEach((k) => { if (f[k].trim()) links[k] = f[k].trim(); });
      let logo = null;
      if (file) {
        const r = await window.PEST_APP.api.uploadProjectLogo(file, id);
        logo = r.path;
      }
      const ok = await store.addProject({
        id, name: f.name.trim(), categories: f.categories, desc: f.desc.trim(),
        chains: f.chains.trim(), logo, links, addedAt: Date.now()
      });
      if (ok) { setF(blank); setFile(null); }
    } catch (e) { toast(e.message || "Upload failed", "err"); }
    setBusy(false);
  };

  const catsOf = (p) => (p.categories && p.categories.length) ? p.categories : (p.category ? [p.category] : []);
  const cats = ["All", ...window.PEST_APP.CATEGORIES];
  const ql = q.trim().toLowerCase();
  const sorted = [...store.projects]
    .filter((p) => catFilter === "All" || catsOf(p).indexOf(catFilter) !== -1)
    .filter((p) => !ql || (p.name + " " + (p.desc || "") + " " + catsOf(p).join(" ")).toLowerCase().includes(ql))
    .sort((a, b) => b.addedAt - a.addedAt);

  return (
    <div className="admin-grid">
      {/* add form */}
      <div className="card panel cut">
        <div className="panel-head"><div><div className="eyebrow">New</div><h2>Add a project</h2></div></div>
        <div className="form-row">
          <FilePickField file={file} onFile={setFile} currentLogo={null} label="Logo preview" />
          <div className="field">
            <label>Name</label>
            <input className="input" value={f.name} onChange={(e) => set("name", e.target.value)} placeholder="Project name" />
          </div>
          <div className="field">
            <label>Categories <span style={{ textTransform: "none", letterSpacing: 0 }}>(pick one or more)</span></label>
            <CategoryChips value={f.categories} onChange={(v) => set("categories", v)} />
          </div>
          <div className="field">
            <label>Description</label>
            <textarea className="input" value={f.desc} onChange={(e) => set("desc", e.target.value)} placeholder="One or two lines on what it is." />
          </div>
          <div className="field">
            <label>Chains / Scope <span style={{ textTransform: "none", letterSpacing: 0 }}>(optional)</span></label>
            <input className="input mono" value={f.chains} onChange={(e) => set("chains", e.target.value)} placeholder="Solana, ETH L1, Base, …" />
          </div>
          <div className="form-2">
            <div className="field"><label>Website</label><input className="input mono" value={f.website} onChange={(e) => set("website", e.target.value)} placeholder="https://" /></div>
            <div className="field"><label>X</label><input className="input mono" value={f.x} onChange={(e) => set("x", e.target.value)} placeholder="https://x.com/" /></div>
          </div>
          <div className="form-2">
            <div className="field"><label>Discord</label><input className="input mono" value={f.discord} onChange={(e) => set("discord", e.target.value)} placeholder="https://discord.gg/" /></div>
            <div className="field"><label>Telegram</label><input className="input mono" value={f.telegram} onChange={(e) => set("telegram", e.target.value)} placeholder="https://t.me/" /></div>
          </div>
          <div className="form-actions">
            <button className="btn btn-primary btn-block" onClick={add} disabled={busy}>
              <window.AIcon.plus /> {busy ? "Saving…" : "Add project"}
            </button>
          </div>
        </div>
      </div>

      {/* list */}
      <div>
        <div className="panel-head" style={{ marginBottom: 14 }}>
          <div><div className="eyebrow">Directory</div><h2>All projects</h2></div>
          <span className="count-pill">{sorted.length === store.projects.length ? store.projects.length + " total" : sorted.length + " / " + store.projects.length}</span>
        </div>
        <div className="proj-toolbar" style={{ marginBottom: 14 }}>
          <div className="search-wrap">
            <window.AIcon.search />
            <input className="input" placeholder="Search projects…" value={q} onChange={(e) => setQ(e.target.value)} />
          </div>
          <div className="cat-row">
            {cats.map((c) => (
              <button key={c} className={"chip" + (catFilter === c ? " on" : "")} onClick={() => setCatFilter(c)}>{c}</button>
            ))}
          </div>
        </div>
        <div className="admin-list admin-scroll">
          {sorted.map((p) => {
            const pc = catsOf(p);
            return (
              <div className="admin-row" key={p.id}>
                <LogoTile project={p} />
                <div className="ar" style={{ minWidth: 0 }}>
                  <b style={{ display: "block", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{p.name}</b>
                  <div className="ar-cats">
                    {pc.length ? pc.map((c) => <span className="mini-tag" key={c}>{c}</span>) : <span className="mini-tag muted">no category</span>}
                  </div>
                  {p.desc ? <span className="ar-desc">{p.desc}</span> : null}
                  <span>{p.chains ? p.chains + " · " : ""}added {window.timeAgo(p.addedAt)}</span>
                </div>
                <button className="btn btn-sm" onClick={() => setEditing(p)} title="Edit"><window.AIcon.edit /></button>
                <button className="del" onClick={() => { if (confirm("Delete " + p.name + "? This also unpins it for everyone.")) store.deleteProject(p.id, p.name); }} title="Delete"><window.AIcon.trash /></button>
              </div>
            );
          })}
          {sorted.length === 0 && <div className="empty-note">No projects match your filter.</div>}
        </div>
      </div>

      {editing && <EditProjectModal project={editing} onClose={() => setEditing(null)} />}
    </div>
  );
}

function EditProjectModal({ project, onClose }) {
  const store = useStore();
  const init = {
    name: project.name || "",
    categories: (project.categories && project.categories.length) ? project.categories.slice() : (project.category ? [project.category] : []),
    desc: project.desc || "",
    chains: project.chains || "",
    website: (project.links && project.links.website) || "",
    x:       (project.links && project.links.x) || "",
    discord: (project.links && project.links.discord) || "",
    telegram:(project.links && project.links.telegram) || "",
  };
  const [f, setF] = useState(init);
  const [file, setFile] = useState(null);
  const [busy, setBusy] = useState(false);
  const set = (k, v) => setF((x) => ({ ...x, [k]: v }));

  useEffect(() => {
    const k = (e) => e.key === "Escape" && onClose();
    window.addEventListener("keydown", k);
    return () => window.removeEventListener("keydown", k);
  }, []);

  const save = async () => {
    if (!f.name.trim()) { toast("Name can't be empty", "err"); return; }
    if (!f.categories.length) { toast("Pick at least one category", "err"); return; }
    setBusy(true);
    try {
      let logo = project.logo;
      if (file) {
        const r = await window.PEST_APP.api.uploadProjectLogo(file, project.id);
        logo = r.path;
      }
      const links = {};
      ["website", "x", "discord", "telegram"].forEach((k) => { if (f[k].trim()) links[k] = f[k].trim(); });
      const ok = await store.updateProject({
        id: project.id, name: f.name.trim(), categories: f.categories,
        desc: f.desc.trim(), chains: f.chains.trim(), logo, links,
      });
      if (ok) onClose();
    } catch (e) { toast(e.message || "Save failed", "err"); }
    setBusy(false);
  };

  return (
    <div className="modal-back" onClick={onClose}>
      <div className="modal cut" style={{ width: "min(560px,100%)" }} onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div><div className="eyebrow">Edit</div><h3 style={{ margin: "4px 0 0" }}>{project.name}</h3></div>
          <button className="icon-x" onClick={onClose}><window.AIcon.close /></button>
        </div>
        <div className="modal-body">
          <div className="form-row">
            <FilePickField file={file} onFile={setFile} currentLogo={project.logo} label="Current logo" />
            <div className="field"><label>Name</label><input className="input" value={f.name} onChange={(e) => set("name", e.target.value)} /></div>
            <div className="field">
              <label>Categories</label>
              <CategoryChips value={f.categories} onChange={(v) => set("categories", v)} />
            </div>
            <div className="field"><label>Description</label><textarea className="input" value={f.desc} onChange={(e) => set("desc", e.target.value)} /></div>
            <div className="field"><label>Chains / Scope</label><input className="input mono" value={f.chains} onChange={(e) => set("chains", e.target.value)} /></div>
            <div className="form-2">
              <div className="field"><label>Website</label><input className="input mono" value={f.website} onChange={(e) => set("website", e.target.value)} /></div>
              <div className="field"><label>X</label><input className="input mono" value={f.x} onChange={(e) => set("x", e.target.value)} /></div>
            </div>
            <div className="form-2">
              <div className="field"><label>Discord</label><input className="input mono" value={f.discord} onChange={(e) => set("discord", e.target.value)} /></div>
              <div className="field"><label>Telegram</label><input className="input mono" value={f.telegram} onChange={(e) => set("telegram", e.target.value)} /></div>
            </div>
            <div className="form-actions">
              <button className="btn btn-primary btn-block" onClick={save} disabled={busy}>
                <window.AIcon.check /> {busy ? "Saving…" : "Save changes"}
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   TOKENS ADMIN
   ============================================================ */
function TokensAdmin() {
  const store = useStore();
  const blank = { symbol: "", name: "", gecko: "", price: "", change: "", chains: "", desc: "", website: "", x: "", discord: "", telegram: "" };
  const [f, setF] = useState(blank);
  const [file, setFile] = useState(null);
  const [busy, setBusy] = useState(false);
  const [editing, setEditing] = useState(null);
  const set = (k, v) => setF((x) => ({ ...x, [k]: v }));

  const add = async () => {
    const sym = f.symbol.trim().toUpperCase();
    if (!sym) { toast("Token needs a symbol", "err"); return; }
    if (!f.name.trim()) { toast("Token needs a name", "err"); return; }
    const id = sym.toLowerCase().replace(/[^a-z0-9]+/g, "");
    if (!id) { toast("Symbol must contain letters or numbers", "err"); return; }
    if (store.tokens.some((t) => t.id === id)) { toast(sym + " already exists", "err"); return; }
    const price = parseFloat(f.price);
    if (!isFinite(price) || price <= 0) { toast("Enter a valid price", "err"); return; }
    const change = f.change.trim() === "" ? 0 : parseFloat(f.change);

    setBusy(true);
    try {
      let logo = null;
      if (file) {
        const r = await window.PEST_APP.api.uploadTokenLogo(file, id);
        logo = r.path;
      }
      const links = {};
      ["website", "x", "discord", "telegram"].forEach((k) => { if (f[k].trim()) links[k] = f[k].trim(); });
      const ok = await store.addToken({
        symbol: sym, name: f.name.trim(), gecko: f.gecko.trim(),
        price, change24h: isFinite(change) ? change : 0,
        chains: f.chains.trim(), desc: f.desc.trim(), logo, links,
      });
      if (ok) { setF(blank); setFile(null); }
    } catch (e) { toast(e.message || "Upload failed", "err"); }
    setBusy(false);
  };

  return (
    <div className="admin-grid">
      {/* add form */}
      <div className="card panel cut">
        <div className="panel-head"><div><div className="eyebrow">New</div><h2>Add a token</h2></div></div>
        <div className="form-row">
          <FilePickField file={file} onFile={setFile} currentLogo={null} label="Logo preview" />
          <div className="form-2">
            <div className="field"><label>Symbol</label><input className="input mono" value={f.symbol} onChange={(e) => set("symbol", e.target.value)} placeholder="SOL" /></div>
            <div className="field"><label>Name</label><input className="input" value={f.name} onChange={(e) => set("name", e.target.value)} placeholder="Solana" /></div>
          </div>
          <div className="field">
            <label>CoinGecko ID <span style={{ textTransform: "none", letterSpacing: 0 }}>(powers the real price feed)</span></label>
            <input className="input mono" value={f.gecko} onChange={(e) => set("gecko", e.target.value)} placeholder="solana" />
          </div>
          <div className="form-2">
            <div className="field"><label>Price (USD)</label><input className="input mono" type="number" min="0" step="any" value={f.price} onChange={(e) => set("price", e.target.value)} placeholder="172.40" /></div>
            <div className="field"><label>24h change %</label><input className="input mono" type="number" step="any" value={f.change} onChange={(e) => set("change", e.target.value)} placeholder="3.4" /></div>
          </div>
          <div className="field"><label>Chains / Scope</label><input className="input mono" value={f.chains} onChange={(e) => set("chains", e.target.value)} placeholder="Solana, ETH L1, …" /></div>
          <div className="field"><label>Description</label><textarea className="input" value={f.desc} onChange={(e) => set("desc", e.target.value)} placeholder="What this token is." /></div>
          <div className="form-2">
            <div className="field"><label>Website</label><input className="input mono" value={f.website} onChange={(e) => set("website", e.target.value)} /></div>
            <div className="field"><label>X</label><input className="input mono" value={f.x} onChange={(e) => set("x", e.target.value)} /></div>
          </div>
          <div className="form-2">
            <div className="field"><label>Discord</label><input className="input mono" value={f.discord} onChange={(e) => set("discord", e.target.value)} /></div>
            <div className="field"><label>Telegram</label><input className="input mono" value={f.telegram} onChange={(e) => set("telegram", e.target.value)} /></div>
          </div>
          <p className="tok-note" style={{ margin: 0 }}>Price &amp; change are starting values — overwritten on next CoinGecko pull (every 5 min).</p>
          <div className="form-actions">
            <button className="btn btn-primary btn-block" onClick={add} disabled={busy}>
              <window.AIcon.plus /> {busy ? "Saving…" : "Add token"}
            </button>
          </div>
        </div>
      </div>

      {/* list */}
      <div>
        <div className="panel-head" style={{ marginBottom: 14 }}>
          <div><div className="eyebrow">Catalog</div><h2>All tokens</h2></div>
          <span className="count-pill">{store.tokens.length} total</span>
        </div>
        <div className="admin-list admin-scroll">
          {store.tokens.map((t) => (
            <div className="admin-row" key={t.id}>
              <TokenTile token={t} />
              <div className="ar" style={{ minWidth: 0 }}>
                <b style={{ display: "block", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>{t.symbol} <span style={{ color: "var(--tx-faint)", fontWeight: 400 }}>· {t.name}</span></b>
                <span>{t.gecko ? t.gecko : "no feed id"} · {t.watchers} watching</span>
              </div>
              <button className="btn btn-sm" onClick={() => setEditing(t)} title="Edit"><window.AIcon.edit /></button>
              <button className="del" onClick={() => { if (confirm("Delete " + t.symbol + "? This removes it from everyone's watchlist.")) store.deleteToken(t.id, t.symbol); }} title="Delete"><window.AIcon.trash /></button>
            </div>
          ))}
        </div>
      </div>

      {editing && <EditTokenModal token={editing} onClose={() => setEditing(null)} />}
    </div>
  );
}

function EditTokenModal({ token, onClose }) {
  const store = useStore();
  const [f, setF] = useState({
    symbol: token.symbol || "", name: token.name || "", gecko: token.gecko || "",
    chains: token.chains || "", desc: token.desc || "",
    website: (token.links && token.links.website) || "",
    x: (token.links && token.links.x) || "",
    discord: (token.links && token.links.discord) || "",
    telegram: (token.links && token.links.telegram) || "",
  });
  const [file, setFile] = useState(null);
  const [busy, setBusy] = useState(false);
  const set = (k, v) => setF((x) => ({ ...x, [k]: v }));

  useEffect(() => {
    const k = (e) => e.key === "Escape" && onClose();
    window.addEventListener("keydown", k);
    return () => window.removeEventListener("keydown", k);
  }, []);

  const save = async () => {
    if (!f.symbol.trim() || !f.name.trim()) { toast("Symbol and name are required", "err"); return; }
    setBusy(true);
    try {
      let logo = token.logo;
      if (file) {
        const r = await window.PEST_APP.api.uploadTokenLogo(file, token.id);
        logo = r.path;
      }
      const links = {};
      ["website", "x", "discord", "telegram"].forEach((k) => { if (f[k].trim()) links[k] = f[k].trim(); });
      const ok = await store.updateToken({
        id: token.id, symbol: f.symbol.trim().toUpperCase(), name: f.name.trim(),
        gecko: f.gecko.trim(), chains: f.chains.trim(), desc: f.desc.trim(), logo, links,
      });
      if (ok) onClose();
    } catch (e) { toast(e.message || "Save failed", "err"); }
    setBusy(false);
  };

  return (
    <div className="modal-back" onClick={onClose}>
      <div className="modal cut" style={{ width: "min(560px,100%)" }} onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div><div className="eyebrow">Edit token</div><h3 style={{ margin: "4px 0 0" }}>{token.symbol}</h3></div>
          <button className="icon-x" onClick={onClose}><window.AIcon.close /></button>
        </div>
        <div className="modal-body">
          <div className="form-row">
            <FilePickField file={file} onFile={setFile} currentLogo={token.logo} label="Current logo" />
            <div className="form-2">
              <div className="field"><label>Symbol</label><input className="input mono" value={f.symbol} onChange={(e) => set("symbol", e.target.value)} /></div>
              <div className="field"><label>Name</label><input className="input" value={f.name} onChange={(e) => set("name", e.target.value)} /></div>
            </div>
            <div className="field"><label>CoinGecko ID</label><input className="input mono" value={f.gecko} onChange={(e) => set("gecko", e.target.value)} /></div>
            <div className="field"><label>Chains / Scope</label><input className="input mono" value={f.chains} onChange={(e) => set("chains", e.target.value)} /></div>
            <div className="field"><label>Description</label><textarea className="input" value={f.desc} onChange={(e) => set("desc", e.target.value)} /></div>
            <div className="form-2">
              <div className="field"><label>Website</label><input className="input mono" value={f.website} onChange={(e) => set("website", e.target.value)} /></div>
              <div className="field"><label>X</label><input className="input mono" value={f.x} onChange={(e) => set("x", e.target.value)} /></div>
            </div>
            <div className="form-2">
              <div className="field"><label>Discord</label><input className="input mono" value={f.discord} onChange={(e) => set("discord", e.target.value)} /></div>
              <div className="field"><label>Telegram</label><input className="input mono" value={f.telegram} onChange={(e) => set("telegram", e.target.value)} /></div>
            </div>
            <div className="form-actions">
              <button className="btn btn-primary btn-block" onClick={save} disabled={busy}>
                <window.AIcon.check /> {busy ? "Saving…" : "Save changes"}
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

/* ============================================================
   MEMBERS / LOGINS ADMIN  — create + reset + manage accounts
   ============================================================ */
function UsersAdmin() {
  const store = useStore();
  const blank = { nick: "", username: "", password: "", isAdmin: false };
  const [f, setF] = useState(blank);
  const [busy, setBusy] = useState(false);
  const [editing, setEditing] = useState(null);
  const [q, setQ] = useState("");
  const set = (k, v) => setF((x) => ({ ...x, [k]: v }));

  const users = store.users || [];
  const loginNicks = new Set(users.map((u) => u.nick.toLowerCase()));
  const membersNoLogin = (store.members || []).filter((m) => !loginNicks.has((m.nick || "").toLowerCase()));

  const genPw = () => {
    const cs = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";
    let s = ""; for (let i = 0; i < 10; i++) s += cs[Math.floor(Math.random() * cs.length)];
    set("password", s);
  };

  const create = async () => {
    if (!f.nick.trim()) { toast("Pick a member nick", "err"); return; }
    if (!f.username.trim()) { toast("Username is required", "err"); return; }
    if (f.password.trim().length < 6) { toast("Password needs 6+ characters", "err"); return; }
    setBusy(true);
    const ok = await store.createUser({
      nick: f.nick.trim(), username: f.username.trim(),
      password: f.password.trim(), isAdmin: f.isAdmin,
    });
    if (ok) setF(blank);
    setBusy(false);
  };

  const ql = q.trim().toLowerCase();
  const shown = users.filter((u) => !ql || u.nick.toLowerCase().includes(ql) || (u.username || "").toLowerCase().includes(ql));

  return (
    <div className="admin-grid">
      {/* create login */}
      <div className="card panel cut">
        <div className="panel-head"><div><div className="eyebrow">New</div><h2>Create a login</h2></div></div>
        <div className="form-row">
          <div className="field">
            <label>Member nick <span style={{ textTransform: "none", letterSpacing: 0 }}>(pick from the directory)</span></label>
            <input className="input" list="member-nicks" value={f.nick} onChange={(e) => set("nick", e.target.value)} placeholder="e.g. Continental" />
            <datalist id="member-nicks">{membersNoLogin.map((m) => <option key={m.nick} value={m.nick} />)}</datalist>
            <span className="ref-slots-note" style={{ marginTop: 4 }}>{membersNoLogin.length} member{membersNoLogin.length === 1 ? "" : "s"} without a login yet</span>
          </div>
          <div className="field">
            <label>Login username</label>
            <input className="input mono" value={f.username} onChange={(e) => set("username", e.target.value)} placeholder="login name (case-insensitive)" />
          </div>
          <div className="field">
            <label>Password</label>
            <div className="form-row" style={{ flexDirection: "row", gap: 8, alignItems: "stretch" }}>
              <input className="input mono" value={f.password} onChange={(e) => set("password", e.target.value)} placeholder="min. 6 characters" style={{ flex: 1 }} />
              <button type="button" className="btn btn-sm" onClick={genPw} title="Generate"><window.AIcon.key /> Generate</button>
            </div>
          </div>
          <label className="admin-check">
            <input type="checkbox" checked={f.isAdmin} onChange={(e) => set("isAdmin", e.target.checked)} />
            <span>Grant admin rights</span>
          </label>
          <div className="form-actions">
            <button className="btn btn-primary btn-block" onClick={create} disabled={busy}>
              <window.AIcon.plus /> {busy ? "Creating…" : "Create login"}
            </button>
          </div>
        </div>
      </div>

      {/* existing logins */}
      <div>
        <div className="panel-head" style={{ marginBottom: 14 }}>
          <div><div className="eyebrow">Accounts</div><h2>All logins</h2></div>
          <span className="count-pill">{users.length} login{users.length === 1 ? "" : "s"}</span>
        </div>
        <div className="search-wrap" style={{ marginBottom: 12 }}>
          <window.AIcon.search />
          <input className="input" placeholder="Search logins…" value={q} onChange={(e) => setQ(e.target.value)} />
        </div>
        <div className="admin-list admin-scroll">
          {shown.map((u) => {
            const isMe = u.nick === store.user.nick;
            return (
              <div className="admin-row" key={u.nick}>
                <div className="logo-tile" style={{ width: 38, height: 38, display: "grid", placeItems: "center" }}><window.AIcon.user /></div>
                <div className="ar" style={{ minWidth: 0 }}>
                  <b style={{ display: "block", whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }}>
                    {u.nick}{isMe && <span style={{ color: "var(--tx-faint)", fontWeight: 400 }}> · you</span>}
                    {u.isAdmin && <span className="admin-badge">ADMIN</span>}
                  </b>
                  <span>login: {u.username}</span>
                </div>
                <button className="btn btn-sm" onClick={() => setEditing(u)} title="Edit / reset"><window.AIcon.edit /></button>
                <button className="del" disabled={isMe} onClick={() => { if (!isMe && confirm("Delete login for " + u.nick + "? They can no longer sign in.")) store.deleteUser(u.nick); }} title={isMe ? "You can't delete yourself" : "Delete login"}><window.AIcon.trash /></button>
              </div>
            );
          })}
          {shown.length === 0 && <div className="empty-note">No logins match “{q}”.</div>}
        </div>
      </div>

      {editing && <EditUserModal user={editing} onClose={() => setEditing(null)} />}
    </div>
  );
}

function EditUserModal({ user, onClose }) {
  const store = useStore();
  const isMe = user.nick === store.user.nick;
  const [username, setUsername] = useState(user.username || "");
  const [password, setPassword] = useState("");
  const [isAdmin, setIsAdmin] = useState(!!user.isAdmin);
  const [busy, setBusy] = useState(false);

  useEffect(() => {
    const k = (e) => e.key === "Escape" && onClose();
    window.addEventListener("keydown", k);
    return () => window.removeEventListener("keydown", k);
  }, []);

  const genPw = () => {
    const cs = "abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789";
    let s = ""; for (let i = 0; i < 10; i++) s += cs[Math.floor(Math.random() * cs.length)];
    setPassword(s);
  };

  const save = async () => {
    const patch = { nick: user.nick };
    if (username.trim() && username.trim() !== user.username) patch.username = username.trim();
    if (password.trim()) {
      if (password.trim().length < 6) { toast("Password needs 6+ characters", "err"); return; }
      patch.password = password.trim();
    }
    if (isAdmin !== !!user.isAdmin) patch.isAdmin = isAdmin;
    if (Object.keys(patch).length === 1) { toast("Nothing changed"); onClose(); return; }
    setBusy(true);
    const ok = await store.adminUpdateUser(patch, "Saved changes for " + user.nick);
    setBusy(false);
    if (ok) onClose();
  };

  return (
    <div className="modal-back" onClick={onClose}>
      <div className="modal cut" style={{ width: "min(460px,100%)" }} onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div><div className="eyebrow">Edit login</div><h3 style={{ margin: "4px 0 0" }}>{user.nick}</h3></div>
          <button className="icon-x" onClick={onClose}><window.AIcon.close /></button>
        </div>
        <div className="modal-body">
          <div className="form-row">
            <div className="field"><label>Login username</label><input className="input mono" value={username} onChange={(e) => setUsername(e.target.value)} /></div>
            <div className="field">
              <label>New password <span style={{ textTransform: "none", letterSpacing: 0 }}>(leave blank to keep)</span></label>
              <div className="form-row" style={{ flexDirection: "row", gap: 8 }}>
                <input className="input mono" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="••••••" style={{ flex: 1 }} />
                <button type="button" className="btn btn-sm" onClick={genPw} title="Generate"><window.AIcon.key /></button>
              </div>
            </div>
            <label className="admin-check">
              <input type="checkbox" checked={isAdmin} disabled={isMe} onChange={(e) => setIsAdmin(e.target.checked)} />
              <span>Admin rights{isMe ? " (can't change your own)" : ""}</span>
            </label>
            <div className="form-actions">
              <button className="btn btn-primary btn-block" onClick={save} disabled={busy}>
                <window.AIcon.check /> {busy ? "Saving…" : "Save changes"}
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

window.Admin = Admin;
