// Players on the LAR API: Game Manager's player list and player view. Every change here is pushed to
// the player's running game by the API, and each panel says whether it reached them.

const LAR_RECENT_KEY = "lar-recent-players";
const LAR_STAFF_ROLES = ["moderator", "developer", "admin"];

function larBanActive(ban) {
  return !ban.revokedAt && (!ban.expiresAt || ban.expiresAt * 1000 > Date.now());
}

function LarRenamePanel({ player, onDone }) {
  const [name, setName] = React.useState(player.name);
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [result, setResult] = React.useState(null);

  const save = async () => {
    setBusy(true); setError(null);
    try {
      setResult(await larApi(`players/${player.id}`, { method: "PATCH", body: { name: name.trim() } }));
      onDone();
    } catch (e) { setError(e.message); }
    setBusy(false);
  };

  return (
    <div className="card card-pad-tight mt-12">
      <div className="tiny" style={{fontWeight:600, marginBottom:8}}>Rename</div>
      <div className="h-stack gap-8">
        <input className="input" value={name} maxLength={16} onChange={e => setName(e.target.value)}/>
        <button className="btn btn-sm btn-accent" disabled={busy || name.trim().length < 3 || name.trim() === player.name} onClick={save}>Save</button>
      </div>
      <div className="tiny muted mt-4">3-16 letters, digits, spaces or underscores. Must be free.</div>
      <LarError error={error}/><LarDelivered result={result}/>
    </div>
  );
}

function LarKickPanel({ player, onDone }) {
  const [reason, setReason] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [result, setResult] = React.useState(null);

  const kick = async () => {
    setBusy(true); setError(null);
    try {
      setResult(await larApi(`players/${player.id}/kick`, { method: "POST", body: { reason: reason.trim() || "Kicked by staff" } }));
      onDone();
    } catch (e) { setError(e.message); }
    setBusy(false);
  };

  return (
    <div className="card card-pad-tight mt-12">
      <div className="tiny" style={{fontWeight:600, marginBottom:8}}>Kick from the game</div>
      <div className="h-stack gap-8">
        <input className="input" value={reason} onChange={e => setReason(e.target.value)} placeholder="Reason (shown in their log)"/>
        <button className="btn btn-sm" disabled={busy} onClick={kick}>Kick</button>
      </div>
      <LarError error={error}/><LarDelivered result={result}/>
    </div>
  );
}

function LarCurrencyTab({ detail, onChange }) {
  const [mode, setMode] = React.useState("add");
  const [amount, setAmount] = React.useState("");
  const [reason, setReason] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [result, setResult] = React.useState(null);
  const id = detail.player.id;

  const apply = async () => {
    const n = Math.floor(Number(amount));
    if (!Number.isFinite(n)) return;
    const body = { reason: reason.trim() || "dashboard" };
    if (mode === "set") body.gold = n;
    else body.goldDelta = mode === "add" ? n : -n;
    setBusy(true); setError(null);
    try {
      setResult(await larApi(`players/${id}`, { method: "PATCH", body }));
      setAmount("");
      onChange();
    } catch (e) { setError(e.message === "insufficient_funds" ? "That would take the balance below zero." : e.message); }
    setBusy(false);
  };

  return (
    <div>
      <div className="card card-pad-tight">
        <div className="kpi-label"><Icon name="coin" size={13}/>Gold (GD)</div>
        <div className="lar-balance">{fmt.num(detail.player.gold)}</div>
      </div>

      {can("currency.edit") && (
        <div className="card card-pad-tight mt-12">
          <div className="h-stack gap-6 mb-8">
            {[["add", "Add"], ["subtract", "Subtract"], ["set", "Set to"]].map(([k, lab]) => (
              <button key={k} className="filter-pill" data-active={mode === k} onClick={() => setMode(k)}>{lab}</button>
            ))}
          </div>
          <div className="lar-grid-form">
            <div className="field"><label>Amount</label><input className="input mono" type="number" min="0" value={amount} onChange={e => setAmount(e.target.value)}/></div>
            <div className="field"><label>Reason (kept in the history)</label><input className="input" value={reason} onChange={e => setReason(e.target.value)} placeholder="refund, event prize…"/></div>
          </div>
          <button className="btn btn-sm btn-accent mt-8" disabled={busy || amount === ""} onClick={apply}>Apply</button>
          <LarError error={error}/><LarDelivered result={result}/>
        </div>
      )}

      <div className="tiny muted mt-20 mb-4" style={{fontWeight:600}}>History (last 50)</div>
      <table className="tbl">
        <thead><tr><th>Change</th><th>Reason</th><th>Ref</th><th style={{textAlign:"right"}}>When</th></tr></thead>
        <tbody>
          {detail.ledger.map((row, i) => (
            <tr key={i}>
              <td className="mono" style={{color: row.delta >= 0 ? "var(--success)" : "var(--danger)", fontWeight:600}}>{row.delta >= 0 ? "+" : ""}{fmt.num(row.delta)}</td>
              <td>{row.reason}</td>
              <td className="tiny muted mono">{row.ref || ""}</td>
              <td className="tiny muted" style={{textAlign:"right"}}>{timeSince(larIso(row.at))}</td>
            </tr>
          ))}
          {detail.ledger.length === 0 && <tr><td colSpan={4}><div className="empty"><h3>No history</h3><p>No gold has moved on this account here yet.</p></div></td></tr>}
        </tbody>
      </table>
    </div>
  );
}

function LarGrantPanel({ playerId, owned, onDone }) {
  const [catalog, setCatalog] = React.useState(null);
  const [query, setQuery] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [result, setResult] = React.useState(null);

  React.useEffect(() => {
    if (!can("catalog.view")) { setCatalog([]); return; }
    larApi("catalog").then(c => setCatalog(c.items)).catch(e => setError(e.message));
  }, []);

  const grant = async (itemId, catalogName) => {
    setBusy(true); setError(null);
    try {
      const res = await larApi(`players/${playerId}/items`, { method: "POST", body: { grant: [{ itemId, catalog: catalogName }] } });
      if (res.missing?.length) setError(`Not in the catalog: ${res.missing.join(", ")}`);
      setResult(res);
      onDone();
    } catch (e) { setError(e.message); }
    setBusy(false);
  };

  const q = query.trim().toLowerCase();
  const matches = catalog && q
    ? catalog.filter(it => it.itemId.toLowerCase().includes(q) || (it.displayName || "").toLowerCase().includes(q)).slice(0, 10)
    : [];

  return (
    <div className="card card-pad-tight mt-12">
      <div className="tiny" style={{fontWeight:600, marginBottom:8}}>Grant item</div>
      <div className="search-field">
        <Icon name="search" size={14}/>
        <input placeholder={catalog ? "Search the catalog by item id or name…" : "Loading catalog…"} value={query} onChange={e => setQuery(e.target.value)} disabled={!catalog || busy}/>
      </div>
      <LarError error={error}/><LarDelivered result={result}/>
      {matches.length > 0 && (
        <div className="stack gap-6 mt-8">
          {matches.map(it => {
            const has = owned.has(`${it.catalog}/${it.itemId}`);
            return (
              <div key={`${it.catalog}/${it.itemId}`} className="h-stack gap-10" style={{padding:"4px 0"}}>
                <LarItemMark id={it.itemId} name={it.displayName} size={24}/>
                <div style={{flex:1, minWidth:0}}>
                  <div style={{fontSize:12.5, fontWeight:500}}>{it.displayName || it.itemId}</div>
                  <div className="tiny muted mono">{it.catalog} · {it.itemId}{it.itemClass ? ` · ${it.itemClass}` : ""}</div>
                </div>
                <button className="btn btn-sm" disabled={busy || (has && !it.uses)} onClick={() => grant(it.itemId, it.catalog)}>
                  {has && !it.uses ? "Owned" : <><Icon name="plus" size={11}/> Grant</>}
                </button>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

function LarInventoryTab({ detail, onChange }) {
  const [showGrant, setShowGrant] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const [filter, setFilter] = React.useState("");
  const [result, setResult] = React.useState(null);
  const id = detail.player.id;
  const owned = new Set(detail.inventory.map(i => `${i.catalog}/${i.itemId}`));

  const revoke = async (item) => {
    if (!confirm(`Take ${item.itemId} away from this player?`)) return;
    setBusy(true);
    try {
      setResult(await larApi(`players/${id}/items`, { method: "POST", body: { revoke: [{ itemId: item.itemId, catalog: item.catalog }] } }));
      onChange();
    } catch (e) { alert(e.message); }
    setBusy(false);
  };

  const f = filter.trim().toLowerCase();
  const items = detail.inventory.filter(i => !f || i.itemId.toLowerCase().includes(f) || (i.itemClass || "").toLowerCase().includes(f));

  return (
    <div>
      <div className="spread mb-8">
        <span className="muted tiny">{detail.inventory.length} items owned</span>
        {can("inventory.grant") && (
          <button className="btn btn-sm" onClick={() => setShowGrant(s => !s)}><Icon name="plus" size={11}/> Grant item</button>
        )}
      </div>
      {showGrant && <LarGrantPanel playerId={id} owned={owned} onDone={onChange}/>}
      <LarDelivered result={result}/>
      <div className="search-field mt-12"><Icon name="search" size={14}/><input placeholder="Filter by item id or class…" value={filter} onChange={e => setFilter(e.target.value)}/></div>
      <div className="stack gap-6 mt-8">
        {items.map(it => (
          <div key={`${it.catalog}/${it.itemId}`} className="h-stack gap-10" style={{padding:"6px 0", borderBottom:"1px solid var(--border)"}}>
            <LarItemMark id={it.itemId} name={it.itemId}/>
            <div style={{flex:1, minWidth:0}}>
              <div style={{fontSize:12.5, fontWeight:500}}>{it.itemId}{it.uses != null && <span className="tag" style={{marginLeft:6}}>×{it.uses}</span>}</div>
              <div className="tiny muted mono">{it.catalog}{it.itemClass ? ` · ${it.itemClass}` : ""}</div>
            </div>
            <span className="tiny muted">{timeSince(larIso(it.acquiredAt))}</span>
            {can("inventory.revoke") && (
              <button className="btn btn-sm btn-ghost" disabled={busy} title="Revoke" onClick={() => revoke(it)}><Icon name="close" size={12}/></button>
            )}
          </div>
        ))}
        {items.length === 0 && <div className="empty"><h3>{detail.inventory.length ? "No match" : "Empty inventory"}</h3><p>{detail.inventory.length ? "Nothing matches that filter." : "This player owns no items."}</p></div>}
      </div>
    </div>
  );
}

function LarDataTab({ detail }) {
  const [open, setOpen] = React.useState(null);
  const [values, setValues] = React.useState({});

  const toggle = async (key) => {
    if (open === key) { setOpen(null); return; }
    setOpen(key);
    if (values[key] === undefined) {
      try {
        const row = await larApi(`players/${detail.player.id}/data/${encodeURIComponent(key)}`);
        setValues(v => ({ ...v, [key]: row.value }));
      } catch (e) { setValues(v => ({ ...v, [key]: `(failed: ${e.message})` })); }
    }
  };

  if (!detail.data.length) return <div className="empty"><h3>No player data</h3><p>No keys on this account (quest state lives here once they play).</p></div>;
  return (
    <div className="stack gap-8">
      {detail.data.map(d => (
        <div key={d.key} className="card card-pad-tight lar-row-click" onClick={() => toggle(d.key)}>
          <div className="spread">
            <span className="mono" style={{fontWeight:600, fontSize:12}}>{d.key}{d.internal ? <span className="tag" style={{marginLeft:6}}>internal</span> : null}</span>
            <span className="tiny muted">{fmt.num(d.bytes)} bytes · {timeSince(larIso(d.updatedAt))}</span>
          </div>
          {open === d.key && <div className="mt-8" onClick={e => e.stopPropagation()}>{values[d.key] === undefined ? <span className="tiny muted">Loading…</span> : <LarJson value={values[d.key]}/>}</div>}
        </div>
      ))}
    </div>
  );
}

function LarBanPanel({ playerId, onDone }) {
  const [reason, setReason] = React.useState("");
  const [hours, setHours] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);

  const ban = async () => {
    setBusy(true); setError(null);
    try {
      const body = { reason: reason.trim() };
      if (hours) body.hours = Number(hours);
      await larApi(`players/${playerId}/ban`, { method: "POST", body });
      onDone();
    } catch (e) { setError(e.message); setBusy(false); }
  };

  return (
    <div className="card card-pad-tight mt-12" style={{borderColor:"var(--danger-soft)"}}>
      <div className="tiny" style={{fontWeight:600, marginBottom:8, color:"var(--danger)"}}>Ban player</div>
      <div className="lar-grid-form">
        <div className="field"><label>Reason</label><input className="input" value={reason} onChange={e => setReason(e.target.value)} placeholder="Cheating, toxicity…"/></div>
        <div className="field"><label>Duration in hours (empty = permanent)</label><input className="input" type="number" min="1" value={hours} onChange={e => setHours(e.target.value)} placeholder="permanent"/></div>
      </div>
      <div className="tiny muted mt-4">Disconnects them right away, ends every session, and refuses Photon reconnects.</div>
      <LarError error={error}/>
      <button className="btn btn-sm btn-danger mt-8" disabled={busy || reason.trim().length < 3} onClick={ban}><Icon name="ban" size={12}/> {busy ? "Banning…" : "Confirm ban"}</button>
    </div>
  );
}

function LarBansTab({ detail, onChange }) {
  const [showBan, setShowBan] = React.useState(false);
  const [busy, setBusy] = React.useState(false);
  const active = detail.bans.filter(larBanActive);

  const lift = async () => {
    if (!confirm("Lift every active ban on this player?")) return;
    setBusy(true);
    try { await larApi(`players/${detail.player.id}/ban`, { method: "DELETE" }); onChange(); } catch (e) { alert(e.message); }
    setBusy(false);
  };

  return (
    <div>
      {can("players.ban") && (
        <div className="h-stack gap-8 mb-8">
          <button className="btn btn-sm btn-danger" onClick={() => setShowBan(s => !s)}><Icon name="ban" size={12}/> Ban</button>
          {active.length > 0 && <button className="btn btn-sm" disabled={busy} onClick={lift}><Icon name="check" size={12}/> Lift all bans</button>}
        </div>
      )}
      {showBan && <LarBanPanel playerId={detail.player.id} onDone={() => { setShowBan(false); onChange(); }}/>}
      <div className="stack gap-12 mt-12">
        {detail.bans.length === 0 && <div className="empty"><h3>Clean record</h3><p>No bans on this account.</p></div>}
        {detail.bans.map(ban => {
          const on = larBanActive(ban);
          return (
            <div key={ban.id} className="card card-pad-tight" style={on ? {borderColor:"var(--danger-soft)", background:"var(--danger-soft)"} : {}}>
              <div className="spread">
                <span style={{fontWeight:600, fontSize:12.5, color: on ? "var(--danger)" : "var(--ink)"}}>{ban.reason}</span>
                {on ? <span className="tag tag-danger"><span className="tag-dot"/>Active</span> : <span className="tag">{ban.revokedAt ? "Lifted" : "Expired"}</span>}
              </div>
              <div className="tiny muted mt-4">
                By {ban.createdBy} · {fmtDate(larIso(ban.createdAt))} · {ban.expiresAt ? `until ${fmtDate(larIso(ban.expiresAt))}` : "permanent"}
                {ban.revokedAt ? ` · lifted ${fmtDate(larIso(ban.revokedAt))}` : ""}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function LarStaffTab({ detail, onChange }) {
  const [roles, setRoles] = React.useState(new Set(detail.roles));
  const [busy, setBusy] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [saved, setSaved] = React.useState(false);
  const editable = can("staff.manage");

  const toggle = (role) => {
    const next = new Set(roles);
    next.has(role) ? next.delete(role) : next.add(role);
    setRoles(next); setSaved(false);
  };

  const save = async () => {
    setBusy(true); setError(null);
    try { await larApi(`players/${detail.player.id}/roles`, { method: "PUT", body: { roles: [...roles] } }); setSaved(true); onChange(); }
    catch (e) { setError(e.message); }
    setBusy(false);
  };

  return (
    <div>
      <p className="tiny muted" style={{marginTop:0}}>
        In-game staff powers, checked by the server: moderator can file staff reports, developer can grant items from the LARMenu,
        admin can do both. Anyone with a role is added to KICK_WHITELIST automatically. Staff title cosmetics are separate.
      </p>
      <div className="stack gap-8 mt-12">
        {LAR_STAFF_ROLES.map(role => (
          <label key={role} className="lar-check">
            <input type="checkbox" checked={roles.has(role)} disabled={!editable || busy} onChange={() => toggle(role)}/>
            <RoleTag role={role === "admin" ? "owner" : role}/> <span style={{textTransform:"capitalize"}}>{role}</span>
          </label>
        ))}
      </div>
      {editable && <button className="btn btn-sm btn-accent mt-12" disabled={busy} onClick={save}>Save roles</button>}
      {saved && <div className="lar-note lar-note-ok">Saved. KICK_WHITELIST was rebuilt and pushed to everyone online.</div>}
      <LarError error={error}/>
    </div>
  );
}

function LarActivityTab({ playerId }) {
  const { data, error, loading } = useLarLoad(() => larApi(`audit?target=${playerId}&limit=100`), [playerId]);
  if (loading) return <div className="empty"><h3>Loading…</h3></div>;
  if (error) return <div className="empty"><h3>Failed to load</h3><p>{error}</p></div>;
  if (!data.entries.length) return <div className="empty"><h3>No activity</h3><p>Nothing has been done to this account yet.</p></div>;
  return (
    <div className="stack gap-8">
      {data.entries.map(e => (
        <div key={e.id} className="h-stack gap-10" style={{padding:"6px 0", borderBottom:"1px solid var(--border)", alignItems:"flex-start"}}>
          <span className="tag" style={{minWidth:86, justifyContent:"center"}}>{e.action}</span>
          <div style={{flex:1, minWidth:0}}>
            <div className="tiny">{e.actor}</div>
            {e.detail && <div className="tiny muted mono" style={{wordBreak:"break-all"}}>{typeof e.detail === "string" ? e.detail : JSON.stringify(e.detail)}</div>}
          </div>
          <span className="tiny muted">{timeSince(larIso(e.at))}</span>
        </div>
      ))}
    </div>
  );
}

function larPushRecent(detail) {
  const p = detail.player;
  const entry = { id: p.id, name: p.name, lastLoginAt: p.lastLoginAt, createdAt: p.createdAt, viewedAt: Date.now() };
  let list = [];
  try { list = JSON.parse(localStorage.getItem(LAR_RECENT_KEY)) || []; } catch { /* empty */ }
  localStorage.setItem(LAR_RECENT_KEY, JSON.stringify([entry, ...list.filter(r => r.id !== p.id)].slice(0, 25)));
}

function LarPlayerDrawer({ playerId, onClose }) {
  const [detail, setDetail] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [tab, setTab] = React.useState("overview");
  const [panel, setPanel] = React.useState(null);

  const load = async () => {
    try {
      const d = await larApi(`players/${playerId}`);
      setDetail(d); larPushRecent(d);
    } catch (e) { setError(e.message); }
  };
  React.useEffect(() => { setDetail(null); setError(null); setTab("overview"); setPanel(null); load(); }, [playerId]);

  const p = detail?.player;
  const activeBans = (detail?.bans || []).filter(larBanActive);
  const TABS = [
    ["overview", "Overview"], ["currency", "Virtual Currency"], ["inventory", "Inventory"], ["data", "Player Data"],
    ["stats", "Statistics"], ["bans", "Bans"], ["staff", "Staff Roles"], ["activity", "Activity"],
  ];

  return (
    <>
      <div className="scrim" onClick={onClose}/>
      <div className="drawer" style={{width:"min(640px, 100vw)"}}>
        <div className="drawer-head">
          <div className="h-stack gap-10">
            <PAvatar name={p?.name || playerId} seed={playerId} size={36} radius={10}/>
            <div>
              <div style={{fontWeight:600, fontSize:13.5}}>{p?.name || "…"}</div>
              <div className="tiny muted mono">{playerId}</div>
            </div>
          </div>
          <button className="icon-btn" onClick={onClose}><Icon name="close" size={14}/></button>
        </div>

        <div className="drawer-body">
          {error && <div className="empty"><h3>Failed to load</h3><p>{error}</p></div>}
          {!detail && !error && <div className="empty"><h3>Loading…</h3><p>Fetching the player from the LAR API.</p></div>}

          {detail && (
            <>
              <div className="profile-head">
                <PAvatar name={p.name} seed={p.id} size={64} radius={14}/>
                <div style={{flex:1}}>
                  <div className="profile-name">{p.name}</div>
                  <div className="profile-meta">
                    <span className="mono" style={{cursor:"pointer"}} title="Click to copy" onClick={() => navigator.clipboard.writeText(p.id)}>{p.id}</span>
                    <span>·</span><LarOnline online={detail.online}/>
                    {activeBans.length > 0 && <><span>·</span><StateTag state="banned"/></>}
                  </div>
                </div>
              </div>

              <div className="tabs" style={{flexWrap:"wrap"}}>
                {TABS.map(([id, label]) => (
                  <button key={id} className="tab" data-active={tab === id} onClick={() => setTab(id)}>
                    {label}
                    {id === "bans" && activeBans.length > 0 && <span className="badge" style={{marginLeft:6}}>{activeBans.length}</span>}
                  </button>
                ))}
              </div>

              <div className="mt-20">
                {tab === "overview" && (
                  <div>
                    <dl className="kv">
                      <dt>Display name</dt><dd>{p.name}</dd>
                      <dt>Player ID (PlayFab ID)</dt><dd className="mono">{p.id}</dd>
                      <dt>Oculus ID</dt><dd className="mono">{p.oculusId}</dd>
                      <dt>Account</dt><dd><LarSourceTag player={{ source: p.source, claimedAt: detail.claimedAt }}/></dd>
                      <dt>Created</dt><dd>{fmtDate(larIso(p.createdAt))}</dd>
                      <dt>Last login</dt><dd>{p.lastLoginAt ? <>{timeSince(larIso(p.lastLoginAt))} <span className="muted tiny">({fmtDate(larIso(p.lastLoginAt))})</span></> : "never on the LAR API"}</dd>
                      <dt>Copied from PlayFab</dt><dd>{p.importedAt ? fmtDate(larIso(p.importedAt)) : "—"}</dd>
                      <dt>First LAR API login</dt><dd>{detail.claimedAt ? fmtDate(larIso(detail.claimedAt)) : "not yet"}</dd>
                      <dt>Gold</dt><dd className="mono">{fmt.num(p.gold)}</dd>
                      <dt>Items</dt><dd>{detail.inventory.length}</dd>
                      <dt>Staff roles</dt><dd>{detail.roles.length ? detail.roles.join(", ") : "none"}</dd>
                      <dt>Discord</dt><dd className="mono">{detail.links.find(l => l.service === "discord")?.externalId || "not linked"}</dd>
                    </dl>
                    <div className="h-stack gap-8 mt-20">
                      {can("players.rename") && <button className="btn btn-sm" onClick={() => setPanel(panel === "rename" ? null : "rename")}><Icon name="edit" size={12}/> Rename</button>}
                      {can("players.kick") && <button className="btn btn-sm" onClick={() => setPanel(panel === "kick" ? null : "kick")}><Icon name="logout" size={12}/> Kick</button>}
                    </div>
                    {panel === "rename" && <LarRenamePanel player={p} onDone={load}/>}
                    {panel === "kick" && <LarKickPanel player={p} onDone={load}/>}
                  </div>
                )}
                {tab === "currency" && <LarCurrencyTab detail={detail} onChange={load}/>}
                {tab === "inventory" && <LarInventoryTab detail={detail} onChange={load}/>}
                {tab === "data" && <LarDataTab detail={detail}/>}
                {tab === "stats" && (
                  detail.stats.length
                    ? <dl className="kv">{detail.stats.map(s => <React.Fragment key={s.name}><dt className="mono">{s.name}</dt><dd className="mono">{fmt.num(s.value)} <span className="tiny muted">· {timeSince(larIso(s.updatedAt))}</span></dd></React.Fragment>)}</dl>
                    : <div className="empty"><h3>No statistics</h3><p>No stats recorded for this player.</p></div>
                )}
                {tab === "bans" && <LarBansTab detail={detail} onChange={load}/>}
                {tab === "staff" && <LarStaffTab detail={detail} onChange={load}/>}
                {tab === "activity" && <LarActivityTab playerId={p.id}/>}
              </div>
            </>
          )}
        </div>
      </div>
    </>
  );
}

function LarPlayersPage() {
  const [query, setQuery] = React.useState("");
  const [rows, setRows] = React.useState(null);
  const [label, setLabel] = React.useState("Recently active");
  const [error, setError] = React.useState(null);
  const [selected, setSelected] = React.useState(() => {
    const focus = sessionStorage.getItem("lar-focus-player");
    if (focus) sessionStorage.removeItem("lar-focus-player");
    return focus || null;
  });

  const run = async (q) => {
    setError(null); setRows(null);
    try {
      const { players } = await larApi(q ? `players?q=${encodeURIComponent(q)}` : "players");
      setRows(players);
      setLabel(q ? `${players.length} match${players.length === 1 ? "" : "es"} for "${q}"` : "Recently active");
    } catch (e) { setError(e.message); setRows([]); }
  };
  React.useEffect(() => { run(""); }, []);

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1 className="page-title">Players</h1>
          <p className="page-subtitle">Search by Player ID, Oculus ID or the start of a name. Changes reach the player's game while they are online.</p>
        </div>
      </div>

      <div className="card card-flush">
        <form className="toolbar" onSubmit={e => { e.preventDefault(); run(query.trim()); }}>
          <div className="search-field" style={{flex:1, maxWidth:460}}>
            <Icon name="search" size={14}/>
            <input placeholder="Player ID, Oculus ID or name…" value={query} onChange={e => setQuery(e.target.value)}/>
          </div>
          <button className="btn btn-sm" type="submit">Search</button>
          {query && <button className="btn btn-sm btn-ghost" type="button" onClick={() => { setQuery(""); run(""); }}>Clear</button>}
          <div style={{flex:1}}/>
          <span className="tiny muted">{label}</span>
        </form>
        {error && <div className="tiny" style={{color:"var(--danger)", padding:"0 16px 12px"}}>{error}</div>}

        <table className="tbl">
          <thead><tr><th>Player</th><th>Player ID</th><th>Gold</th><th>Last login</th><th>Account</th></tr></thead>
          <tbody>
            {(rows || []).map(p => (
              <tr key={p.id} className="lar-row-click" onClick={() => setSelected(p.id)}>
                <td><div className="p-row"><PAvatar name={p.name} seed={p.id}/><div className="p-name">{p.name}</div></div></td>
                <td className="mono">{p.id}</td>
                <td className="mono">{fmt.short(p.gold)}</td>
                <td className="muted">{p.lastLoginAt ? timeSince(larIso(p.lastLoginAt)) : "never"}</td>
                <td><LarSourceTag player={p}/></td>
              </tr>
            ))}
            {rows && rows.length === 0 && <tr><td colSpan={5}><div className="empty"><h3>No players</h3><p>{query ? "Nothing matched." : "Nobody has logged in through the LAR API yet."}</p></div></td></tr>}
            {!rows && <tr><td colSpan={5}><div className="empty"><h3>Loading…</h3></div></td></tr>}
          </tbody>
        </table>
      </div>

      {selected && <LarPlayerDrawer playerId={selected} onClose={() => { setSelected(null); run(query.trim()); }}/>}
    </div>
  );
}

/** Other pages open a player by id: switch to Players with that player's view open. */
window.larOpenPlayer = (id) => {
  sessionStorage.setItem("lar-focus-player", id);
  window.location.hash = "players";
};

Object.assign(window, { LarPlayersPage, LarPlayerDrawer });
