// Users, Ranks, HWID, API — wired to backend.

function UsersPage({ me }) {
  const { data: users, refresh } = useApi('/api/users');
  const [filter, setFilter] = React.useState("all");
  const [editing, setEditing] = React.useState(null);
  const [creating, setCreating] = React.useState(false);
  const [adminIdsOpen, setAdminIdsOpen] = React.useState(false);
  const [search, setSearch] = React.useState('');
  const list = users || [];
  const filtered = list.filter(r => {
    if (filter !== 'all' && r.status !== filter && (r.rank||'').toLowerCase() !== filter && r.role !== filter) return false;
    if (search && !`${r.username} ${r.discord_id||''}`.toLowerCase().includes(search.toLowerCase())) return false;
    return true;
  });
  // Reseller accounts are never deleted. To stop access, edit and set status=frozen.

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1>Users</h1>
          <p>{list.length} {list.length===1?'account':'accounts'}.</p>
        </div>
        <div className="flex gap-8">
          <button className="btn" onClick={()=>setAdminIdsOpen(true)}><Ico.Discord className="icon"/> Admin Discord IDs</button>
          <button className="btn btn-primary" onClick={()=>setCreating(true)}><Ico.Plus className="icon"/> New reseller</button>
        </div>
      </div>

      <div className="grid grid-4" style={{ marginBottom: 24 }}>
        <Stat label="Total accounts" value={String(list.length)} icon={<Ico.Users/>}/>
        <Stat label="Active" value={String(list.filter(u=>u.status==='active').length)} icon={<Ico.Zap/>}/>
        <Stat label="Frozen" value={String(list.filter(u=>u.status==='frozen').length)} icon={<Ico.Lock/>}/>
        <Stat label="Admins" value={String(list.filter(u=>u.role==='admin').length)} icon={<Ico.Shield/>}/>
      </div>

      <div className="card">
        <div className="card-head">
          <div className="flex gap-8" style={{ flexWrap: 'wrap' }}>
            {["all", "active", "frozen", "admin", "reseller", "diamond", "platinum", "gold", "silver", "bronze"].map(f => (
              <span key={f} className={`pill ${filter === f ? 'gold' : ''}`} style={{ cursor: 'pointer', padding: '6px 12px', fontSize: 12 }} onClick={() => setFilter(f)}>{f}</span>
            ))}
          </div>
          <div className="topbar" style={{ background: 'var(--bg-card-2)', border: '1px solid var(--border)', borderRadius: 'var(--r-pill)', padding: '4px 12px', position: 'static' }}>
            <Ico.Search width={14} height={14} className="icon"/>
            <input style={{ background: 'none', border: 'none', outline: 'none', color: 'var(--text)', fontSize: 12, width: 200 }} placeholder="Search username or discord id…" value={search} onChange={e=>setSearch(e.target.value)}/>
          </div>
        </div>
        <table className="table">
          <thead><tr><th>User</th><th>Role</th><th>Rank</th><th>Balance</th><th>Total sold</th><th>Last seen</th><th>Status</th><th></th></tr></thead>
          <tbody>
            {filtered.map(r => (
              <tr key={r.id}>
                <td><div className="user"><Avatar name={r.username}/><div><div style={{ fontWeight: 600 }}>{r.username}</div>{r.discord_id && <div className="mono" style={{ fontSize: 10, color:'var(--text-dimmer)' }}>{r.discord_id}</div>}</div></div></td>
                <td>{r.role==='admin' ? <span className="pill amber">admin</span> : <span className="pill">reseller</span>}</td>
                <td>
                  <span className="pill" style={{ background: 'transparent', borderColor: 'var(--border-strong)', color: RANK_COLORS[r.rank]||'var(--text)' }}>
                    <Ico.Crown width={10} height={10}/>{r.rank}
                  </span>
                </td>
                <td className="mono" style={{ fontWeight: 700, color: r.balance > 0 ? 'var(--accent)' : 'var(--text-dimmer)' }}>{fmtMoney(r.balance)}</td>
                <td className="mono">{fmtMoney(r.total_sold)}</td>
                <td style={{ color: 'var(--text-dim)', fontSize: 12 }}>{fmtTime(r.last_seen)}</td>
                <td>
                  {r.status === 'active' && <span className="pill green"><span className="dot"/>active</span>}
                  {r.status === 'frozen' && <span className="pill red"><span className="dot"/>frozen</span>}
                  {r.status === 'pending' && <span className="pill amber"><span className="dot"/>pending</span>}
                </td>
                <td>
                  <button className="btn btn-sm" onClick={()=>setEditing(r)}><Ico.Edit width={12} height={12} className="icon"/> Edit</button>
                </td>
              </tr>
            ))}
            {!filtered.length && <tr><td colSpan="8" style={{ padding: 0 }}>
              <EmptyState icon={<Ico.Users/>} title={list.length?'No accounts match the filter':'No accounts yet'} desc={list.length?'Clear the filter or search to see everyone.':'Create your first reseller from the New reseller button.'} action={!list.length && <button className="btn btn-primary" onClick={()=>setCreating(true)}><Ico.Plus className="icon"/> New reseller</button>}/>
            </td></tr>}
          </tbody>
        </table>
      </div>

      {editing && <EditUserModal user={editing} onClose={()=>setEditing(null)} onSaved={()=>{ setEditing(null); refresh(); }}/>}
      {creating && <NewUserModal onClose={()=>setCreating(false) } onCreated={()=>{ setCreating(false); refresh(); }}/>}
      {adminIdsOpen && <AdminIdsModal onClose={()=>setAdminIdsOpen(false)} onSaved={()=>{ setAdminIdsOpen(false); refresh(); }}/>}
    </div>
  );
}

function AdminIdsModal({ onClose, onSaved }) {
  const { data, refresh } = useApi('/api/settings/admin-ids');
  const [text, setText] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  React.useEffect(()=>{ if (data) setText((data.ui_ids||[]).join('\n')); }, [data]);

  const save = async () => {
    setBusy(true);
    try {
      const ids = text.split(/[\s,]+/).map(s=>s.trim()).filter(Boolean);
      const r = await api.put('/api/settings/admin-ids', { ids });
      toast(`Saved ${r.ui_ids.length} ID${r.ui_ids.length===1?'':'s'}`);
      refresh(); onSaved();
    } catch (e) { toast(e.message, 'err'); }
    setBusy(false);
  };

  return (
    <Modal open onClose={onClose} title="Admin Discord IDs" width={520}>
      <div className="flex-col gap-12">
        <div style={{ padding: 12, background: 'var(--bg-card-2)', border: '1px solid var(--border)', borderRadius: 'var(--r-md)', fontSize: 12.5, color: 'var(--text-dim)', lineHeight: 1.55 }}>
          Discord IDs in this list are <b>auto-promoted to admin</b> on every sign-in. Add the snowflake ID (long number) one per line or comma-separated.
          <div style={{ marginTop: 6, fontSize: 11, color: 'var(--text-dimmer)' }}>
            To find a Discord ID: enable Developer Mode in Discord → right-click profile → Copy ID.
          </div>
        </div>

        {data && data.env_ids?.length > 0 && (
          <div className="field">
            <label>From <code>.env</code> (read-only)</label>
            <div className="flex gap-4" style={{ flexWrap: 'wrap' }}>
              {data.env_ids.map(id => <span key={id} className="pill" style={{ padding: '6px 10px' }}><Ico.Lock width={10} height={10}/> {id}</span>)}
            </div>
          </div>
        )}

        <div className="field">
          <label>Editable list ({(text.match(/\d+/g)||[]).length} ID{(text.match(/\d+/g)||[]).length===1?'':'s'})</label>
          <textarea
            className="textarea mono"
            rows="6"
            value={text}
            onChange={e=>setText(e.target.value)}
            placeholder="237401829&#10;184729302"
          />
          <small style={{ display: 'block', marginTop: 4, fontSize: 11, color: 'var(--text-dimmer)' }}>
            Saving will promote any existing user with a matching Discord ID right away.
          </small>
        </div>

        <div className="flex gap-8" style={{ justifyContent: 'flex-end' }}>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" disabled={busy} onClick={save}>{busy?'Saving…':'Save'}</button>
        </div>
      </div>
    </Modal>
  );
}

function NewUserModal({ onClose, onCreated }) {
  const [f, setF] = React.useState({ username: '', password: '', role: 'reseller', rank: 'Bronze', balance: 0, status: 'active', discord_id: '' });
  const [busy, setBusy] = React.useState(false);
  const create = async () => {
    setBusy(true);
    try {
      await api.post('/api/users', { ...f, balance: +f.balance });
      toast(`${f.role==='admin'?'Admin':'Reseller'} created`);
      onCreated();
    } catch (e) { toast(e.message,'err'); }
    setBusy(false);
  };
  const genPw = () => {
    const a = 'abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    let p = ''; for (let i=0;i<14;i++) p += a[Math.floor(Math.random()*a.length)];
    setF({...f, password: p});
  };
  return (
    <Modal open onClose={onClose} title="Create reseller account" width={520}>
      <div className="flex-col gap-12">
        <div className="grid grid-2" style={{ gap: 12 }}>
          <div className="field"><label>Username *</label><input className="input mono" value={f.username} onChange={e=>setF({...f,username:e.target.value})} autoFocus placeholder="lowercase, letters/numbers/_.-"/></div>
          <div className="field"><label>Discord ID (optional)</label><input className="input mono" value={f.discord_id} onChange={e=>setF({...f,discord_id:e.target.value})} placeholder="237401829…"/></div>
        </div>
        <div className="field">
          <label>Initial password * <a style={{ float:'right', color:'var(--accent)', cursor:'pointer', fontSize: 11 }} onClick={genPw}>generate strong</a></label>
          <input className="input mono" value={f.password} onChange={e=>setF({...f,password:e.target.value})} placeholder="at least 8 characters"/>
          <small style={{ display:'block', marginTop: 4, fontSize: 11, color: 'var(--text-dimmer)' }}>Give this to the user — they can rotate it from the sidebar lock icon after sign-in.</small>
        </div>
        <div className="grid grid-2" style={{ gap: 12 }}>
          <div className="field"><label>Role</label>
            <select className="select" value={f.role} onChange={e=>setF({...f,role:e.target.value})}>
              <option value="reseller">Reseller</option>
              <option value="admin">Admin</option>
            </select>
          </div>
          <div className="field"><label>Starting rank</label>
            <select className="select" value={f.rank} onChange={e=>setF({...f,rank:e.target.value})}>
              {['Bronze','Silver','Gold','Platinum','Diamond'].map(r=><option key={r} value={r}>{r}</option>)}
            </select>
          </div>
        </div>
        <div className="grid grid-2" style={{ gap: 12 }}>
          <div className="field"><label>Starting balance ($)</label><NumberInput value={f.balance} onChange={v=>setF({...f,balance:v})} min={0} step={1} prefix="$" presets={[0,25,50,100,250]}/></div>
          <div className="field"><label>Status</label>
            <select className="select" value={f.status} onChange={e=>setF({...f,status:e.target.value})}>
              <option value="active">Active</option>
              <option value="frozen">Frozen</option>
            </select>
          </div>
        </div>
        <div className="flex gap-8" style={{ justifyContent: 'flex-end' }}>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" disabled={busy} onClick={create}>{busy?'Creating…':'Create account'}</button>
        </div>
      </div>
    </Modal>
  );
}

function UserAccessSection({ userId, role }) {
  const { data: products } = useApi('/api/products');
  const { data: granted, refresh } = useApi(`/api/users/${userId}/access`);
  const list = products || [];
  const grant = async (pid, body, msg) => {
    try { await api.post(`/api/users/${userId}/access/${pid}`, body); toast(msg); refresh(); }
    catch (e) { toast(e.message,'err'); }
  };
  if (role === 'admin') {
    return <div style={{ fontSize: 12, color: 'var(--text-dimmer)' }}>Admins automatically have access to every product.</div>;
  }
  return (
    <div className="flex-col gap-8">
      <div style={{ fontWeight: 700, fontSize: 13 }}>Per-product access</div>
      <div style={{ maxHeight: 220, overflow: 'auto', border: '1px solid var(--border)', borderRadius: 'var(--r-md)' }}>
        {list.map(p => {
          const g = (granted||[]).find(x => x.product_id === p.id);
          const state = g ? (g.lifetime ? <span className="pill" style={{ color:'#b9f2ff', borderColor:'#b9f2ff44' }}>♾ lifetime</span> : (g.active ? <span className="pill green">{g.days_left}d</span> : <span className="pill red">expired</span>)) : <span className="pill">none</span>;
          return (
            <div key={p.id} style={{ display:'grid', gridTemplateColumns: '1fr auto auto', gap: 8, alignItems:'center', padding: '8px 12px', borderBottom: '1px solid var(--border)' }}>
              <div>
                <div style={{ fontSize: 13, fontWeight: 600 }}>{p.name}</div>
                <div style={{ fontSize: 11, color: 'var(--text-dimmer)' }}>{p.panel_price_monthly > 0 ? `${fmtMoney(p.panel_price_monthly)}/mo` : 'free under global panel'}</div>
              </div>
              <div>{state}</div>
              <div className="flex gap-4">
                <button type="button" className="btn btn-sm" onClick={()=>grant(p.id, { months: 1 }, '+1mo')} title="+1 month">+1mo</button>
                <button type="button" className="btn btn-sm" onClick={()=>grant(p.id, { months: 3 }, '+3mo')}>+3mo</button>
                <button type="button" className="btn btn-sm" style={{ color:'#b9f2ff' }} onClick={()=>grant(p.id, { lifetime: true }, 'Lifetime granted')}><Ico.Crown width={11} height={11} className="icon"/></button>
                <button type="button" className="btn btn-sm btn-ghost" style={{ color:'var(--danger)' }} onClick={()=>grant(p.id, { revoke: true }, 'Revoked')}><Ico.Trash width={11} height={11} className="icon"/></button>
              </div>
            </div>
          );
        })}
        {!list.length && <div style={{ padding: 14, color: 'var(--text-dimmer)', fontSize: 12, textAlign: 'center' }}>No products yet.</div>}
      </div>
    </div>
  );
}

function EditUserModal({ user, onClose, onSaved }) {
  const [role, setRole] = React.useState(user.role);
  const [rank, setRank] = React.useState(user.rank);
  const [status, setStatus] = React.useState(user.status);
  const [delta, setDelta] = React.useState(0);
  const [note, setNote] = React.useState('');
  const [busy, setBusy] = React.useState(false);

  const save = async () => {
    setBusy(true);
    try {
      const body = { role, rank, status };
      if (+delta) { body.balanceDelta = +delta; body.note = note||'admin adjustment'; }
      await api.patch('/api/users/'+user.id, body);
      toast('User updated'); onSaved();
    } catch (e) { toast(e.message,'err'); }
    setBusy(false);
  };

  return (
    <Modal open onClose={onClose} title={`Edit ${user.username}`} width={620}>
      <div className="flex-col gap-12">
        <div className="grid grid-2" style={{ gap: 12 }}>
          <div className="field"><label>Role</label>
            <select className="select" value={role} onChange={e=>setRole(e.target.value)}>
              <option value="reseller">Reseller</option>
              <option value="admin">Admin</option>
            </select>
          </div>
          <div className="field"><label>Status</label>
            <select className="select" value={status} onChange={e=>setStatus(e.target.value)}>
              <option value="active">Active</option>
              <option value="frozen">Frozen</option>
              <option value="pending">Pending</option>
            </select>
          </div>
        </div>
        <div className="field"><label>Rank</label>
          <select className="select" value={rank} onChange={e=>setRank(e.target.value)}>
            {['Bronze','Silver','Gold','Platinum','Diamond'].map(r=><option key={r} value={r}>{r}</option>)}
          </select>
        </div>

        <div className="divider"/>
        <div style={{ fontWeight: 700, fontSize: 13 }}>Adjust balance (current: {fmtMoney(user.balance)})</div>
        <div className="grid grid-2" style={{ gap: 12 }}>
          <div className="field"><label>Delta ($, +/-)</label><NumberInput value={delta} onChange={setDelta} step={1} prefix="$" presets={[-50,-10,0,10,50,100]}/></div>
          <div className="field"><label>Note</label><input className="input" value={note} onChange={e=>setNote(e.target.value)}/></div>
        </div>

        <div className="divider"/>
        <UserAccessSection userId={user.id} role={role}/>

        <div className="flex gap-8" style={{ justifyContent: 'flex-end' }}>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" disabled={busy} onClick={save}>{busy?'Saving…':'Save'}</button>
        </div>
      </div>
    </Modal>
  );
}

function RanksPage({ me, isAdmin }) {
  const { data: ranks, refresh: refreshRanks } = useApi('/api/ranks');
  const { data: users } = useApi('/api/users');
  const list = ranks || [];
  const [editing, setEditing] = React.useState(null);
  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1>Ranks</h1>
          <p>Cosmetic tiers for your resellers.</p>
        </div>
      </div>

      <div className="card card-glow">
        <div className="card-pad">
          <div className="flex-between" style={{ alignItems: 'flex-start', flexWrap: 'wrap', gap: 16 }}>
            <div>
              <div className="eyebrow" style={{ color: 'var(--text-dim)' }}>Your rank</div>
              <div style={{ display: 'flex', alignItems: 'center', gap: 12, marginTop: 6 }}>
                <Ico.Crown width={36} height={36} style={{ color: RANK_COLORS[me?.rank]||'#FFB800' }}/>
                <div>
                  <div style={{ fontSize: 32, fontWeight: 800, letterSpacing: '-0.02em' }}>{me?.rank}</div>
                  <div style={{ color: 'var(--text-dim)', fontSize: 13 }}>{me?.rank} reseller</div>
                </div>
              </div>
            </div>
            <div style={{ textAlign: 'right' }}>
              <div className="eyebrow" style={{ color: 'var(--text-dim)' }}>Sold all-time</div>
              <div className="mono" style={{ fontSize: 32, fontWeight: 800, color: 'var(--accent)' }}>{fmtMoney(me?.total_sold||0)}</div>
            </div>
          </div>
        </div>
      </div>

      <div className="grid mt-24" style={{ gridTemplateColumns: 'repeat(5, 1fr)', gap: 16 }}>
        {list.map(r => {
          const isCurrent = r.name === me?.rank;
          return (
            <div key={r.name} className="card" style={{
              borderColor: isCurrent ? 'color-mix(in oklab, var(--accent) 60%, transparent)' : 'var(--border)',
              boxShadow: isCurrent ? '0 0 30px -8px var(--accent-glow)' : 'none',
            }}>
              <div style={{
                height: 100, background: `linear-gradient(180deg, color-mix(in oklab, ${r.color} 25%, transparent), transparent)`,
                borderBottom: '1px solid var(--border)', display: 'grid', placeItems: 'center',
              }}>
                <Ico.Crown width={48} height={48} style={{ color: r.color, filter: `drop-shadow(0 0 16px ${r.color})` }}/>
              </div>
              <div className="card-pad">
                <div style={{ fontSize: 18, fontWeight: 800, color: r.color, letterSpacing: '-0.01em' }}>{r.name}</div>
                <div className="mono" style={{ fontSize: 11, color: 'var(--text-dimmer)', marginTop: 2 }}>Unlocks at {fmtMoney(r.unlock_at)} sold</div>
                <div style={{ display: 'flex', alignItems: 'baseline', gap: 4, marginTop: 12 }}>
                  <span className="mono" style={{ fontSize: 28, fontWeight: 800, color: 'var(--accent)' }}>{r.discount}%</span>
                  <span style={{ fontSize: 12, color: 'var(--text-dim)' }}>discount</span>
                </div>
                <div className="divider"/>
                <ul style={{ listStyle: 'none', padding: 0, margin: 0, display: 'flex', flexDirection: 'column', gap: 6 }}>
                  {(r.perks||[]).map(p => (
                    <li key={p} style={{ display: 'flex', gap: 8, alignItems: 'flex-start', fontSize: 12, color: 'var(--text-dim)' }}>
                      <Ico.Check width={12} height={12} style={{ color: r.color, flexShrink: 0, marginTop: 2 }}/>
                      <span>{p}</span>
                    </li>
                  ))}
                </ul>
                <div className="divider"/>
                <div className="flex-between" style={{ fontSize: 11, color: 'var(--text-dimmer)' }}>
                  <span>{r.rps} {r.rps===1?'reseller':'resellers'}</span>
                  <div className="flex gap-4">
                    {isCurrent && <span className="pill gold">you</span>}
                    {isAdmin && <button className="btn btn-sm btn-ghost" onClick={()=>setEditing(r)} title="Edit rank"><Ico.Edit width={11} height={11} className="icon"/></button>}
                  </div>
                </div>
              </div>
            </div>
          );
        })}
      </div>

      {editing && <EditRankModal rank={editing} onClose={()=>setEditing(null)} onSaved={()=>{ setEditing(null); refreshRanks(); }}/>}

      <div className="card mt-24">
        <div className="card-head"><h3>Top sellers</h3></div>
        <table className="table">
          <thead><tr><th>#</th><th>Reseller</th><th>Rank</th><th>Total sold</th><th>Balance</th></tr></thead>
          <tbody>
            {(users||[]).slice().sort((a,b)=>b.total_sold-a.total_sold).slice(0,8).map((r, i) => (
              <tr key={r.id}>
                <td className="mono" style={{ color: 'var(--text-dimmer)' }}>#{i + 1}</td>
                <td><div className="user"><Avatar name={r.username}/><span style={{ fontWeight: 600 }}>{r.username}</span></div></td>
                <td>
                  <span className="pill" style={{ background: 'transparent', borderColor: 'var(--border-strong)', color: RANK_COLORS[r.rank]||'var(--text)' }}>
                    <Ico.Crown width={10} height={10}/>{r.rank}
                  </span>
                </td>
                <td className="mono">{fmtMoney(r.total_sold)}</td>
                <td className="mono" style={{ color: 'var(--accent)' }}>{fmtMoney(r.balance)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function EditRankModal({ rank, onClose, onSaved }) {
  const [f, setF] = React.useState({
    discount: rank.discount || 0,
    unlock_at: rank.unlock_at || 0,
    color: rank.color || '#FFB800',
    perks: (rank.perks||[]).join('\n'),
  });
  const [busy, setBusy] = React.useState(false);
  const save = async () => {
    setBusy(true);
    try {
      await api.put('/api/ranks/'+encodeURIComponent(rank.name), {
        discount: +f.discount, unlock_at: +f.unlock_at, color: f.color,
        perks: f.perks.split('\n').map(s=>s.trim()).filter(Boolean),
      });
      toast('Rank saved'); onSaved();
    } catch (e) { toast(e.message,'err'); }
    setBusy(false);
  };
  return (
    <Modal open onClose={onClose} title={`Edit ${rank.name} rank`}>
      <div className="flex-col gap-12">
        <div className="grid grid-2" style={{ gap: 12 }}>
          <div className="field"><label>Discount (%)</label><input className="input mono" type="number" min="0" max="100" value={f.discount} onChange={e=>setF({...f,discount:e.target.value})}/></div>
          <div className="field"><label>Unlocks at lifetime sales ($)</label><input className="input mono" type="number" min="0" step="0.01" value={f.unlock_at} onChange={e=>setF({...f,unlock_at:e.target.value})}/></div>
        </div>
        <div className="field">
          <label>Accent color</label>
          <div className="flex gap-8" style={{ alignItems: 'center' }}>
            <input type="color" value={f.color} onChange={e=>setF({...f,color:e.target.value})} style={{ width: 50, height: 38, border: '1px solid var(--border)', borderRadius: 'var(--r-md)', background: 'transparent' }}/>
            <input className="input mono" style={{ flex: 1 }} value={f.color} onChange={e=>setF({...f,color:e.target.value})}/>
          </div>
        </div>
        <div className="field">
          <label>Perks (one per line)</label>
          <textarea className="textarea" rows="6" value={f.perks} onChange={e=>setF({...f,perks:e.target.value})}/>
        </div>
        <div className="flex gap-8" style={{ justifyContent: 'flex-end' }}>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" disabled={busy} onClick={save}>{busy?'Saving…':'Save changes'}</button>
        </div>
      </div>
    </Modal>
  );
}

function HwidPage({ isAdmin }) {
  const { data: keys, refresh } = useApi('/api/keys/active');
  const list = keys || [];
  const reset = async (key) => {
    if (!confirm(`Reset HWID for ${key}? Free if quota available, else $1.00.`)) return;
    try { const r = await api.post(`/api/keys/${encodeURIComponent(key)}/hwid/reset`); toast(r.cost?`Reset — charged $${r.cost.toFixed(2)}`:'Reset (free quota)'); refresh(); }
    catch (e) { toast(e.message,'err'); }
  };
  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1>HWID reset</h1>
          <p>{isAdmin ? "Reset the hardware lock on any key." : "Reset the hardware lock on a key you own."}</p>
        </div>
      </div>

      <div className="grid grid-4" style={{ marginBottom: 24 }}>
        <Stat label="Active keys" value={String(list.length)} icon={<Ico.Key/>}/>
        <Stat label="Resets used" value={String(list.reduce((a,k)=>a+k.resets_used,0))} icon={<Ico.Reset/>}/>
        <Stat label="Locked" value={String(list.filter(k=>k.hwid).length)} icon={<Ico.Lock/>}/>
        <Stat label="Unlocked" value={String(list.filter(k=>!k.hwid).length)} icon={<Ico.Shield/>}/>
      </div>

      <div className="card">
        <div className="card-head"><h3>Active keys</h3><button className="btn btn-sm btn-ghost" onClick={refresh}><Ico.Refresh className="icon" width={12} height={12}/></button></div>
        <table className="table">
          <thead><tr><th>Key</th><th>Product</th>{isAdmin&&<th>Owner</th>}<th>Duration</th><th>HWID</th><th>Resets</th><th>Action</th></tr></thead>
          <tbody>
            {list.map(k => (
              <tr key={k.id}>
                <td className="mono" style={{ fontWeight: 600 }}>{k.key_value}</td>
                <td>{k.product_name}</td>
                {isAdmin&&<td className="mono" style={{ fontSize: 12 }}>{k.owner||'—'}</td>}
                <td><span className="tag">{k.duration}</span></td>
                <td className="mono" style={{ fontSize: 12, color: 'var(--text-dimmer)' }}>{k.hwid||'(unbound)'}</td>
                <td><span className="mono" style={{ fontSize: 12 }}>{k.resets_used}/{k.resets_max}</span></td>
                <td><button className="btn btn-sm btn-primary" onClick={()=>reset(k.key_value)}><Ico.Reset className="icon" width={12} height={12}/> Reset</button></td>
              </tr>
            ))}
            {!list.length && <tr><td colSpan={isAdmin?7:6} style={{ textAlign:'center', color:'var(--text-dimmer)', padding:32 }}>No active keys yet. Generate some in Stocking Keys.</td></tr>}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function ApiPage() {
  const { data: keys, refresh } = useApi('/api/apikeys');
  const [open, setOpen] = React.useState(false);
  const [revealed, setRevealed] = React.useState(null);

  const create = async (label, scopes) => {
    try {
      const r = await api.post('/api/apikeys', { label, scopes });
      setRevealed({ token: r.token, label });
      refresh();
    } catch (e) { toast(e.message, 'err'); }
  };
  const remove = async (id) => {
    if (!confirm('Delete this API key?')) return;
    try { await api.del('/api/apikeys/'+id); refresh(); } catch (e) { toast(e.message,'err'); }
  };

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1>API</h1>
          <p>Use the panel from your own apps.</p>
        </div>
        <button className="btn btn-primary" onClick={()=>setOpen(true)}><Ico.Plus className="icon"/> New API key</button>
      </div>

      <div className="card">
        <div className="card-head"><h3>Your API keys</h3></div>
        <table className="table">
          <thead><tr><th>Label</th><th>Token</th><th>Scopes</th><th>Created</th><th>Last used</th><th>Requests</th><th></th></tr></thead>
          <tbody>
            {(keys||[]).map(k => (
              <tr key={k.id}>
                <td style={{ fontWeight: 600 }}>{k.label}</td>
                <td className="mono" style={{ fontSize: 12, color: 'var(--text-dim)' }}>{k.token.slice(0,12)}…{k.token.slice(-6)}</td>
                <td>{k.scopes.split(',').map(s => <span key={s} className="tag" style={{ marginRight: 4 }}>{s.trim()}</span>)}</td>
                <td style={{ color: 'var(--text-dim)', fontSize: 12 }}>{fmtDate(k.created_at)}</td>
                <td style={{ color: 'var(--text-dim)', fontSize: 12 }}>{k.last_used?fmtTime(k.last_used):'never'}</td>
                <td className="mono">{k.request_count.toLocaleString()}</td>
                <td>
                  <div className="flex gap-4">
                    <button className="btn btn-sm btn-ghost" onClick={()=>{navigator.clipboard.writeText(k.token); toast('Copied');}}><Ico.Copy className="icon" width={12} height={12}/></button>
                    <button className="btn btn-sm btn-ghost" style={{ color: 'var(--danger)' }} onClick={()=>remove(k.id)}><Ico.Trash className="icon" width={12} height={12}/></button>
                  </div>
                </td>
              </tr>
            ))}
            {(!keys || !keys.length) && <tr><td colSpan="7" style={{ textAlign:'center', color:'var(--text-dimmer)', padding:32 }}>No API keys yet.</td></tr>}
          </tbody>
        </table>
      </div>

      <ApiDocs/>

      {open && <NewApiKeyModal onClose={()=>setOpen(false)} onCreate={async (l,s)=>{ await create(l,s); setOpen(false); }}/>}
      {revealed && <Modal open title="Your new API key" onClose={()=>setRevealed(null)}>
        <p style={{ fontSize: 13, color: 'var(--text-dim)' }}>Copy this token now — it will not be shown again in full.</p>
        <div className="mono" style={{ padding: 14, background: '#000', border: '1px solid var(--border)', borderRadius: 'var(--r-md)', wordBreak: 'break-all', fontSize: 13 }}>{revealed.token}</div>
        <div className="flex gap-8 mt-12" style={{ justifyContent: 'flex-end' }}>
          <button className="btn btn-sm" onClick={()=>{navigator.clipboard.writeText(revealed.token); toast('Copied');}}><Ico.Copy className="icon" width={12} height={12}/> Copy</button>
          <button className="btn btn-primary" onClick={()=>setRevealed(null)}>Done</button>
        </div>
      </Modal>}
    </div>
  );
}

function ApiDocs() {
  const base = typeof location !== 'undefined' ? location.origin : 'https://your-domain';
  const endpoints = [
    { m:'GET',  p:'/v1/health',                    d:'Public liveness check. No auth.', noauth: true },
    { m:'GET',  p:'/v1/me',                        d:'Your account: id, username, role, rank, balance, total_sold.' },
    { m:'GET',  p:'/v1/balance',                   d:'Just balance + rank + username.' },
    { m:'GET',  p:'/v1/products',                  d:'List live products with their durations + your panel access status.' },
    { m:'GET',  p:'/v1/keys',                      d:'Every key you own — most recent first.' },
    { m:'POST', p:'/v1/keys/generate',             d:'Mint keys. Body: product_id, duration, quantity (1-500), prefix? (1-8 A-Z/0-9). Free with panel, otherwise debits balance.' },
    { m:'GET',  p:'/v1/license/:key',              d:'Live status of a key you own (frozen, banned, expiry, HWID, etc.).' },
    { m:'POST', p:'/v1/license/:key/compensate',   d:'Add days to one of your keys. Body: days (1-3650), reason (≥5 chars).' },
    { m:'POST', p:'/v1/license/:key/freeze',       d:'Freeze (or unfreeze) one of your keys. Body: freeze? (default true), reason?.' },
    { m:'POST', p:'/v1/hwid/reset',                d:'Reset a key\'s HWID. Body: key, reason?.' },
  ];

  const example = {
    cmd: `curl -X POST ${base}/v1/keys/generate \\
  -H "Authorization: Bearer emu_live_..." \\
  -H "Content-Type: application/json" \\
  -d '{"product_id": 1, "duration": "1 Month", "quantity": 25, "prefix": "TEMP"}'`,
    res: `{
  "batch_id": 42,
  "keys": ["TEMP-9F7K2L1MXQ4P9N3R", "..."],
  "cost": 0,
  "balance_after": 250.00,
  "free": true
}`,
  };

  return (
    <div className="card mt-24">
      <div className="card-head">
        <h3>API reference</h3>
        <span className="pill"><Ico.Code width={11} height={11}/> v1 · Bearer token</span>
      </div>
      <div className="card-pad flex-col gap-16">
        <div style={{ fontSize: 13, color: 'var(--text-dim)', lineHeight: 1.6 }}>
          Every endpoint (except <code>/v1/health</code>) requires <code style={{ background: 'var(--bg-card-2)', padding: '2px 6px', borderRadius: 4 }}>Authorization: Bearer &lt;your-token&gt;</code>.
          Tokens are <b>user-scoped</b> — they can only see and act on the resources owned by the user that created them.
          <br/>
          Limits per token: <b>60 writes/min</b>, <b>300 reads/min</b>. CORS is open; call <code>/v1/*</code> from any origin.
          The <code>/api/*</code> surface is browser-only (session cookie) and never accepts Bearer tokens.
        </div>

        <div style={{ border: '1px solid var(--border)', borderRadius: 'var(--r-md)', overflow: 'hidden' }}>
          {endpoints.map((e, i) => (
            <div key={i} style={{ display: 'grid', gridTemplateColumns: '70px 1fr', gap: 12, padding: '10px 14px', borderTop: i>0?'1px solid var(--border)':'none', alignItems: 'baseline' }}>
              <span className="pill" style={{ background: e.m==='GET'?'rgba(34,211,238,0.15)':'rgba(15,168,95,0.18)', color: e.m==='GET'?'#22D3EE':'#0fa85f', fontWeight: 700, fontSize: 11, justifySelf: 'start' }}>{e.m}</span>
              <div>
                <code className="mono" style={{ fontSize: 12.5, color: 'var(--text)', fontWeight: 600 }}>{e.p}</code>
                <div style={{ fontSize: 12, color: 'var(--text-dim)', marginTop: 3 }}>{e.d}</div>
              </div>
            </div>
          ))}
        </div>

        <div className="grid grid-2" style={{ gap: 12 }}>
          <div>
            <div className="eyebrow" style={{ color: 'var(--text-dim)', marginBottom: 6 }}>Example request</div>
            <div className="code-block">{example.cmd}</div>
          </div>
          <div>
            <div className="eyebrow" style={{ color: 'var(--text-dim)', marginBottom: 6 }}>Example response</div>
            <div className="code-block">{example.res}</div>
          </div>
        </div>

        <div style={{ padding: 12, background: 'var(--bg-card-2)', border: '1px solid var(--border)', borderRadius: 'var(--r-md)', fontSize: 12, color: 'var(--text-dim)', lineHeight: 1.55 }}>
          <b>Errors</b> are always JSON: <code>{`{"error":"message"}`}</code>. Status codes follow the obvious mapping —
          {' '}<code>400</code> bad input,
          {' '}<code>401</code> missing/bad token,
          {' '}<code>402</code> insufficient balance,
          {' '}<code>404</code> not found,
          {' '}<code>429</code> rate-limited (<code>Retry-After</code> header set),
          {' '}<code>5xx</code> license backend / server error.
        </div>
      </div>
    </div>
  );
}

function NewApiKeyModal({ onClose, onCreate }) {
  const [label, setLabel] = React.useState('My app');
  const [scopes, setScopes] = React.useState('keys.write,balance.read');
  return (
    <Modal open onClose={onClose} title="Create API key">
      <div className="flex-col gap-12">
        <div className="field"><label>Label</label><input className="input" value={label} onChange={e=>setLabel(e.target.value)}/></div>
        <div className="field"><label>Scopes (comma-separated)</label><input className="input mono" value={scopes} onChange={e=>setScopes(e.target.value)}/></div>
        <div className="flex gap-8" style={{ justifyContent: 'flex-end' }}>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" onClick={()=>onCreate(label, scopes)}>Create</button>
        </div>
      </div>
    </Modal>
  );
}

// ============================================================ License management
// Reseller sees only their own keys (server enforces). Admin sees everything plus sessions/activity/bulk tools.
function LicensesPage({ isAdmin, me }) {
  // ----- All hooks must come first (React rule: same order every render) -----
  const [tab, setTab] = React.useState('keys');
  const { data: status, loading: loadingStatus } = useApi('/api/license/status');
  const { data: keysData, refresh: refreshKeys, loading } = useApi('/api/license');
  const { data: sessionsData, refresh: refreshSessions } = useApi(isAdmin && tab==='sessions' ? '/api/license/sessions' : null, [tab]);
  const { data: activityData, refresh: refreshActivity } = useApi(isAdmin && tab==='activity' ? '/api/license/activity' : null, [tab]);
  const [search, setSearch] = React.useState('');
  const [appFilter, setAppFilter] = React.useState('all');
  const [statusFilter, setStatusFilter] = React.useState('all');
  const [picked, setPicked] = React.useState(null);
  const [bulkOpen, setBulkOpen] = React.useState(null);
  const [bulkReqOpen, setBulkReqOpen] = React.useState(false);

  // ----- Derived state below the hooks -----
  const keys = keysData?.keys || [];
  const apps = Array.from(new Set(keys.map(k => k.product_name || k.app_id || 'unassigned')));
  const daysLeft = (k) => {
    if (k.expires_at) return Math.ceil((k.expires_at*1000 - Date.now())/86400000);
    return null;
  };
  const filtered = keys.filter(k => {
    const label = k.product_name || k.app_id || 'unassigned';
    if (appFilter !== 'all' && label !== appFilter) return false;
    if (statusFilter === 'locked' && !k.hwid) return false;
    if (statusFilter === 'unbound' && k.hwid) return false;
    if (statusFilter === 'expiring') {
      const d = daysLeft(k);
      if (d === null || d < 0 || d > 7) return false;
    }
    if (search) {
      const blob = [k.key, k.product_name, k.app_id, k.owner, k.hwid].filter(Boolean).join(' ').toLowerCase();
      if (!blob.includes(search.toLowerCase())) return false;
    }
    return true;
  });
  const stats = {
    total: keys.length,
    bound: keys.filter(k => k.hwid).length,
    unbound: keys.filter(k => !k.hwid).length,
    expiring: keys.filter(k => { const d = daysLeft(k); return d !== null && d >= 0 && d <= 7; }).length,
    resets: keys.reduce((a,k)=>a+(k.resets_used||0), 0),
    products: apps.length,
  };
  const tabs = isAdmin
    ? [{ id:'keys', label:'Licenses' }, { id:'requests', label:'Bulk requests' }, { id:'sessions', label:'Sessions' }, { id:'activity', label:'Activity' }]
    : [{ id:'keys', label:'My licenses' }, { id:'requests', label:'Bulk requests' }];
  const deleteKey = async (k) => {
    if (!confirm(`Delete ${k.key}? This will revoke it and cannot be undone.`)) return;
    try { await api.del(`/api/license/${encodeURIComponent(k.key)}`); toast('Key deleted'); refreshKeys(); }
    catch (e) { toast(e.message, 'err'); }
  };

  // ----- Early-return placeholders (after hooks) -----
  if (loadingStatus) {
    return (
      <div className="page">
        <div className="page-head"><div><h1>{isAdmin?'Licenses':'My keys'}</h1></div></div>
        <div className="card card-pad" style={{ textAlign: 'center', color: 'var(--text-dim)', padding: 48 }}>Loading…</div>
      </div>
    );
  }
  if (false && status && status.enabled === false) {
    return (
      <div className="page">
        <div className="page-head"><div><h1>{isAdmin?'Licenses':'My keys'}</h1></div></div>
        <div className="card"><EmptyState
          icon={<Ico.Shield/>}
          title="License backend not configured"
          desc={isAdmin
            ? <>Set <code>WORKER_URL</code> and <code>WORKER_ADMIN_TOKEN</code> in your <code>.env</code>, then restart. Keys generate locally without it but live status / freeze / compensate need the worker.</>
            : 'The admin hasn’t hooked the license backend up yet. Check back soon.'}
        /></div>
      </div>
    );
  }

  return (
    <div className="page">
      <div className="page-head">
        <div>
          <h1>{isAdmin ? 'Licenses' : 'My keys'}</h1>
          <p>{isAdmin ? 'Every key, session, and worker event.' : 'Keys you generated. Add time or reset HWIDs.'}</p>
        </div>
        <div className="flex gap-8">
          <CopyBtn value={filtered.map(k=>k.key).join('\n')} label={`Copy ${filtered.length}`} size="" kind="" successMsg={`${filtered.length} keys copied`}/>
          <button className="btn" onClick={()=>{
            const csv = 'key,product,duration,issued_at\n' + filtered.map(k=>[k.key,(k.product_name||k.app_id||''),k.duration,k.issued_at?new Date(k.issued_at*1000).toISOString():''].join(',')).join('\n');
            const blob = new Blob([csv],{type:'text/csv'}); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = `licenses-${Date.now()}.csv`; a.click(); URL.revokeObjectURL(a.href);
          }}><Ico.Down className="icon"/> Export CSV</button>
          <button className="btn" onClick={()=>setBulkReqOpen(true)}><Ico.Plus className="icon"/> Bulk compensate</button>
          <button className="btn btn-ghost" onClick={()=>{refreshKeys(); refreshSessions(); refreshActivity();}}><Ico.Refresh className="icon"/> Refresh</button>
          {isAdmin && <button className="btn" onClick={()=>setBulkOpen('compensate')}><Ico.Zap className="icon"/> Bulk tools</button>}
        </div>
      </div>

      {status && status.enabled === false && (
        <div style={{ marginBottom: 16, padding: 12, background: 'rgba(255,184,0,0.08)', border: '1px solid rgba(255,184,0,0.3)', borderRadius: 'var(--r-md)', color: '#ffd17a', fontSize: 12.5 }}>
          License backend is not configured. Local key listing and deletion still work; live status, freeze, compensation, and HWID actions need <code>WORKER_URL</code> and <code>WORKER_ADMIN_TOKEN</code>.
        </div>
      )}

      <div className="grid grid-4" style={{ marginBottom: 24, gridTemplateColumns: 'repeat(6, 1fr)' }}>
        <Stat label="Total keys" value={String(stats.total)} icon={<Ico.Key/>}/>
        <Stat label="HWID bound" value={String(stats.bound)} icon={<Ico.Lock/>}/>
        <Stat label="Unbound" value={String(stats.unbound)} icon={<Ico.Shield/>}/>
        <Stat label="Expiring (7d)" value={String(stats.expiring)} icon={<Ico.Bell/>}/>
        <Stat label="Total resets" value={String(stats.resets)} icon={<Ico.Reset/>}/>
        <Stat label="Products" value={String(stats.products)} icon={<Ico.Box/>}/>
      </div>

      <div className="flex gap-8" style={{ marginBottom: 16 }}>
        {tabs.map(t => (
          <span key={t.id} className={`pill ${tab===t.id?'gold':''}`} style={{ cursor:'pointer', padding:'8px 16px' }} onClick={()=>setTab(t.id)}>{t.label}</span>
        ))}
      </div>

      {tab === 'keys' && (
        <div className="card">
          <div className="card-head" style={{ flexWrap: 'wrap', gap: 12 }}>
            <div className="flex-col gap-8" style={{ flex: 1, minWidth: 0 }}>
              <div className="flex gap-8" style={{ flexWrap: 'wrap', alignItems: 'center' }}>
                <span style={{ fontSize: 11, color: 'var(--text-dimmer)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>Status</span>
                {['all','locked','unbound','expiring'].map(f => (
                  <span key={f} className={`pill ${statusFilter===f?'gold':''}`} style={{ cursor:'pointer', padding:'6px 12px', fontSize: 12 }} onClick={()=>setStatusFilter(f)}>{f}</span>
                ))}
              </div>
              {apps.length > 1 && (
                <div className="flex gap-8" style={{ flexWrap: 'wrap', alignItems: 'center' }}>
                  <span style={{ fontSize: 11, color: 'var(--text-dimmer)', textTransform: 'uppercase', letterSpacing: '0.05em', fontWeight: 600 }}>Product</span>
                  <span className={`pill ${appFilter==='all'?'gold':''}`} style={{ cursor:'pointer', padding:'6px 12px', fontSize: 12 }} onClick={()=>setAppFilter('all')}>all</span>
                  {apps.map(a => (
                    <span key={a} className={`pill ${appFilter===a?'gold':''}`} style={{ cursor:'pointer', padding:'6px 12px', fontSize: 12 }} onClick={()=>setAppFilter(a)}>{a}</span>
                  ))}
                </div>
              )}
            </div>
            <div className="topbar" style={{ background: 'var(--bg-card-2)', border: '1px solid var(--border)', borderRadius: 'var(--r-pill)', padding: '4px 12px', position: 'static' }}>
              <Ico.Search width={14} height={14} className="icon"/>
              <input style={{ background: 'none', border: 'none', outline: 'none', color: 'var(--text)', fontSize: 12, width: 200 }} placeholder="Search key, product, HWID…" value={search} onChange={e=>setSearch(e.target.value)}/>
            </div>
          </div>
          <table className="table">
            <thead><tr><th>Key</th><th>Product</th>{isAdmin && <th>Owner</th>}<th>Duration</th><th>HWID</th><th>Resets</th><th>Issued</th><th></th></tr></thead>
            <tbody>
              {loading && <tr><td colSpan={isAdmin?8:7} style={{ textAlign: 'center', color: 'var(--text-dimmer)', padding: 32 }}>Loading…</td></tr>}
              {!loading && filtered.map(k => (
                <tr key={k.key} style={{ cursor:'pointer' }} onClick={()=>setPicked(k)}>
                  <td onClick={e=>e.stopPropagation()}>
                    <div className="flex gap-8" style={{ alignItems: 'center' }}>
                      <span className="mono" style={{ fontSize: 12, fontWeight: 600, userSelect: 'all' }}>{k.key}</span>
                      <CopyBtn value={k.key} label="" size="sm" kind="btn-ghost" successMsg="Key copied"/>
                    </div>
                  </td>
                  <td><span className="tag">{k.product_name||k.app_id||'—'}</span></td>
                  {isAdmin && <td className="mono" style={{ fontSize: 12 }}>{k.owner||'—'}</td>}
                  <td><span className="pill">{k.duration}</span></td>
                  <td className="mono" style={{ fontSize: 11, color: k.hwid?'var(--text-dim)':'var(--text-dimmer)' }}>
                    {k.hwid ? (k.hwid.slice(0,16)+(k.hwid.length>16?'…':'')) : '(unbound)'}
                  </td>
                  <td><span className="mono" style={{ fontSize: 12 }}>{k.resets_used||0}/{k.resets_max||1}</span></td>
                  <td style={{ fontSize: 12, color: 'var(--text-dim)' }}>{fmtTime(k.issued_at)}</td>
                  <td onClick={e=>e.stopPropagation()}>
                    <div className="flex gap-4">
                      <button className="btn btn-sm btn-primary" onClick={()=>setPicked(k)}><Ico.Eye width={12} height={12} className="icon"/> Manage</button>
                      <button className="btn btn-sm" onClick={()=>deleteKey(k)} style={{ color:'var(--danger)' }} title="Delete key"><Ico.Trash width={12} height={12} className="icon"/></button>
                    </div>
                  </td>
                </tr>
              ))}
              {!loading && !filtered.length && <tr><td colSpan={isAdmin?8:7} style={{ padding: 0 }}>
                <EmptyState
                  icon={<Ico.Key/>}
                  title={
                    keys.length
                      ? 'No keys match the filters'
                      : (isAdmin ? 'No keys in the system yet' : 'No keys yet')
                  }
                  desc={
                    keys.length
                      ? 'Clear the search or status filter to see everything.'
                      : (isAdmin
                          ? 'Once any reseller generates keys, every one appears here.'
                          : 'Open a product, hit Generate, and your keys land in this list.')
                  }
                  action={!keys.length && (
                    isAdmin
                      ? <button className="btn btn-primary" onClick={()=>window.goTo('products')}><Ico.Box className="icon"/> Go to products</button>
                      : <button className="btn btn-primary" onClick={()=>window.goTo('products')}><Ico.Zap className="icon"/> Browse products</button>
                  )}
                />
              </td></tr>}
            </tbody>
          </table>
          <div className="flex-between" style={{ padding: '10px 18px', borderTop: '1px solid var(--border)', color: 'var(--text-dimmer)', fontSize: 11 }}>
            <span>{filtered.length} of {keys.length} keys{!isAdmin && ' · scoped to your account by the server'}</span>
            {!isAdmin && <span className="flex gap-4" style={{ alignItems:'center' }}><Ico.Shield width={11} height={11}/> only your keys are visible</span>}
          </div>
        </div>
      )}

      {tab === 'sessions' && (
        <div className="card">
          <div className="card-head"><h3>Active sessions</h3><span className="pill gold">{sessionsData?.sessions?.length || 0}</span></div>
          <table className="table">
            <thead><tr><th>Session</th><th>Key</th><th>App</th><th>Discord</th><th>Idle</th><th>Expires in</th><th></th></tr></thead>
            <tbody>
              {(sessionsData?.sessions || []).map(s => (
                <tr key={s.session_token}>
                  <td className="mono" style={{ fontSize: 11, color: 'var(--text-dim)' }}>{(s.session_token||'').slice(0,12)}…</td>
                  <td className="mono" style={{ fontSize: 12, fontWeight: 600 }}>{s.key||'—'}</td>
                  <td>{s.app_id||'unassigned'}</td>
                  <td>{s.discord_username||'—'}</td>
                  <td className="mono" style={{ fontSize: 12, color: 'var(--text-dim)' }}>{Math.floor((s.idle_seconds||0)/60)}m</td>
                  <td><span className="pill green">{Math.floor((s.expires_in_seconds||0)/60)}m</span></td>
                  <td><button className="btn btn-sm" onClick={async()=>{ if(!confirm('Revoke this session?')) return; try{ await api.post('/api/license/session/revoke',{key:s.key}); toast('Revoked'); refreshSessions(); }catch(e){toast(e.message,'err');} }}>Revoke</button></td>
                </tr>
              ))}
              {!sessionsData?.sessions?.length && <tr><td colSpan="7" style={{ textAlign:'center', color:'var(--text-dimmer)', padding:32 }}>No active sessions.</td></tr>}
            </tbody>
          </table>
        </div>
      )}

      {tab === 'requests' && <BulkRequestsTab isAdmin={isAdmin}/>}

      {tab === 'activity' && (
        <div className="card">
          <div className="card-head"><h3>Worker activity</h3></div>
          <table className="table">
            <thead><tr><th>When</th><th>Action</th><th>Key</th><th>App</th><th>Reason</th></tr></thead>
            <tbody>
              {(activityData?.activity || []).map((a, i) => (
                <tr key={i}>
                  <td style={{ color: 'var(--text-dim)', fontSize: 12 }}>{a.at ? new Date(a.at).toLocaleString() : '—'}</td>
                  <td className="mono" style={{ fontSize: 12, color: 'var(--accent)' }}>{a.action||'—'}</td>
                  <td className="mono" style={{ fontSize: 12 }}>{a.key||'—'}</td>
                  <td>{a.app_id||'—'}</td>
                  <td style={{ color: 'var(--text-dim)', fontSize: 12 }}>{a.reason||a.message||''}</td>
                </tr>
              ))}
              {!activityData?.activity?.length && <tr><td colSpan="5" style={{ textAlign:'center', color:'var(--text-dimmer)', padding:32 }}>No activity recorded.</td></tr>}
            </tbody>
          </table>
        </div>
      )}

      {picked && <LicenseDetailModal
        lic={picked}
        isAdmin={isAdmin}
        onClose={()=>setPicked(null)}
        onChange={refreshKeys}
      />}
      {bulkOpen && isAdmin && <BulkToolsModal appOptions={Array.from(new Set(keys.map(k => k.app_id))).filter(Boolean).map(id => ({ id, label: (keys.find(k=>k.app_id===id)?.product_name) || id }))} onClose={()=>setBulkOpen(null)} onChange={refreshKeys}/>}
      {bulkReqOpen && <BulkRequestModal
        keys={keys}
        presetProductName={typeof bulkReqOpen === 'string' ? bulkReqOpen : null}
        onClose={()=>setBulkReqOpen(false)}
        onCreated={()=>{ setBulkReqOpen(false); setTab('requests'); }}
      />}
    </div>
  );
}

// ============================================================ Bulk request flow
function BulkRequestModal({ keys, presetProductName, onClose, onCreated }) {
  const { data: allProducts } = useApi('/api/products');
  const products = React.useMemo(() => {
    const counts = new Map();
    for (const k of keys||[]) {
      const name = k.product_name || k.app_id;
      if (!name) continue;
      counts.set(name, (counts.get(name)||0) + 1);
    }
    return (allProducts||[])
      .filter(p => counts.has(p.name))
      .map(p => ({ id: p.id, label: p.name, count: counts.get(p.name) }));
  }, [keys, allProducts]);
  const [pid, setPid] = React.useState('');
  React.useEffect(() => {
    if (pid) return;
    if (presetProductName) {
      const m = products.find(p => p.label === presetProductName);
      if (m) { setPid(m.id); return; }
    }
    if (products.length) setPid(products[0].id);
  }, [products, presetProductName]);
  const [days, setDays] = React.useState(7);
  const [reason, setReason] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const chosen = products.find(p => p.id === +pid);

  const submit = async () => {
    if (reason.trim().length < 5) return toast('Reason needs at least 5 characters', 'warn');
    setBusy(true);
    try {
      await api.post('/api/bulk-requests', { product_id: +pid, days: +days, reason: reason.trim() });
      toast('Request submitted — admin will review');
      onCreated();
    } catch (e) { toast(e.message, 'err'); }
    setBusy(false);
  };

  return (
    <Modal open onClose={onClose} title="Compensate all my keys for a product" width={520}>
      <div className="flex-col gap-12">
        {!products.length ? (
          <div style={{ padding: 14, color: 'var(--text-dim)', fontSize: 13 }}>
            You don't have any keys yet. Generate some first, then you can request a bulk compensation.
          </div>
        ) : (
          <>
            <div className="field">
              <label>Product</label>
              <select className="select" value={pid} onChange={e=>setPid(+e.target.value)}>
                {products.map(p => <option key={p.id} value={p.id}>{p.label} ({p.count} key{p.count===1?'':'s'})</option>)}
              </select>
            </div>
            <div className="field">
              <label>Days to add per key</label>
              <NumberInput value={days} onChange={setDays} min={1} max={365} step={1} suffix="d" presets={[1,3,7,14,30]}/>
            </div>
            <div className="field">
              <label>Reason · required *</label>
              <textarea
                className="textarea"
                rows="3"
                value={reason}
                onChange={e=>setReason(e.target.value)}
                placeholder="e.g. loader was down for 5 hours during the Apex update"
                style={{ borderColor: reason && reason.trim().length < 5 ? 'var(--danger)' : undefined }}
              />
              <small style={{ display:'block', marginTop: 4, fontSize: 11, color: 'var(--text-dimmer)' }}>
                Admin will review. On approval, {chosen?.count || 0} key{chosen?.count===1?'':'s'} will get <b>+{days}d</b>.
              </small>
            </div>
            <div style={{ padding: 10, background: 'var(--bg-card-2)', border: '1px solid var(--border)', borderRadius: 'var(--r-md)', fontSize: 12, color: 'var(--text-dim)' }}>
              <Ico.Shield width={12} height={12} style={{ display:'inline', marginRight: 4, verticalAlign: 'middle' }}/>
              Bulk compensations always need admin review to prevent abuse.
            </div>
            <div className="flex gap-8" style={{ justifyContent: 'flex-end' }}>
              <button className="btn" onClick={onClose}>Cancel</button>
              <button className="btn btn-primary" disabled={busy||reason.trim().length<5||!chosen?.count} onClick={submit}>
                {busy ? 'Submitting…' : 'Submit for review'}
              </button>
            </div>
          </>
        )}
      </div>
    </Modal>
  );
}

function BulkRequestsTab({ isAdmin }) {
  const { data: requests, refresh } = useApi('/api/bulk-requests');
  const [reviewing, setReviewing] = React.useState(null);
  const list = requests || [];

  const review = async (id, approve, note='') => {
    try {
      const r = await api.post(`/api/bulk-requests/${id}/${approve?'approve':'reject'}`, { note });
      if (approve) toast(`Approved: ${r.ok_count} key${r.ok_count===1?'':'s'} compensated${r.fail_count?`, ${r.fail_count} failed`:''}`);
      else toast('Rejected');
      refresh();
    } catch (e) { toast(e.message, 'err'); }
  };

  return (
    <div className="card">
      <div className="card-head">
        <h3>{isAdmin ? 'Bulk compensation requests' : 'My bulk requests'}</h3>
        <span className="pill amber">{list.filter(r => r.status==='pending').length} pending</span>
      </div>
      <table className="table">
        <thead><tr><th>#</th>{isAdmin && <th>Reseller</th>}<th>Product</th><th>Days</th><th>Affected</th><th>Reason</th><th>Status</th><th>When</th><th></th></tr></thead>
        <tbody>
          {list.map(r => (
            <tr key={r.id}>
              <td className="mono" style={{ fontSize: 12, fontWeight: 600 }}>#{r.id}</td>
              {isAdmin && <td>{r.username}</td>}
              <td>{r.product_name}</td>
              <td className="mono">+{r.days}d</td>
              <td className="mono">{r.affected_count}</td>
              <td style={{ maxWidth: 280, fontSize: 12, color: 'var(--text-dim)' }}>{r.reason}</td>
              <td>
                {r.status==='pending'  && <span className="pill amber"><span className="dot"/>pending</span>}
                {r.status==='approved' && <span className="pill green"><span className="dot"/>approved</span>}
                {r.status==='rejected' && <span className="pill red"><span className="dot"/>rejected</span>}
              </td>
              <td style={{ color: 'var(--text-dim)', fontSize: 11 }}>{fmtTime(r.created_at)}</td>
              <td>
                {isAdmin && r.status === 'pending' ? (
                  <div className="flex gap-4">
                    <button className="btn btn-sm btn-primary" onClick={()=>review(r.id, true)}>Approve</button>
                    <button className="btn btn-sm" onClick={()=>review(r.id, false)} style={{ color: 'var(--danger)' }}>Reject</button>
                  </div>
                ) : r.reviewer_username ? (
                  <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>by {r.reviewer_username}</span>
                ) : null}
              </td>
            </tr>
          ))}
          {!list.length && <tr><td colSpan={isAdmin?9:8} style={{ padding: 0 }}>
            <EmptyState
              icon={<Ico.Zap/>}
              title={isAdmin?'No bulk requests yet':'No requests yet'}
              desc={isAdmin?'Reseller bulk-compensate requests will appear here for review.':'Open the "Bulk compensate" button on the licenses tab to submit one.'}
            />
          </td></tr>}
        </tbody>
      </table>
    </div>
  );
}

function Field({ label, value }) {
  return (
    <div>
      <div className="eyebrow" style={{ color:'var(--text-dim)' }}>{label}</div>
      <div style={{ fontSize: 13, marginTop: 2 }}>{value}</div>
    </div>
  );
}

function HistoryAccordion({ comp, resets }) {
  const [open, setOpen] = React.useState(false);
  const cn = (comp||[]).length, rn = (resets||[]).length;
  if (!cn && !rn) return null;
  return (
    <div style={{ border: '1px solid var(--border)', borderRadius: 'var(--r-md)', overflow: 'hidden' }}>
      <div onClick={()=>setOpen(o=>!o)} style={{ padding: '10px 14px', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 8, background: 'var(--bg-card-2)', fontSize: 12.5 }}>
        <Ico.List width={12} height={12}/>
        <b>Activity history</b>
        <span className="pill">{cn} comp.</span>
        <span className="pill">{rn} HWID resets</span>
        <div style={{ flex: 1 }}/>
        <span style={{ fontSize: 11, color: 'var(--text-dim)' }}>{open?'▲ hide':'▼ show'}</span>
      </div>
      {open && (
        <div style={{ padding: '8px 14px 12px', fontSize: 12 }}>
          {cn > 0 && (
            <>
              <div className="eyebrow" style={{ color: 'var(--text-dim)', margin: '6px 0' }}>Compensations</div>
              {comp.slice().reverse().map((c,i) => (
                <div key={i} style={{ padding: '6px 0', borderBottom: i < cn-1 ? '1px solid var(--border)' : 'none' }}>
                  <span className="mono" style={{ color: 'var(--accent)', fontWeight: 700 }}>+{c.days_added}d</span>
                  {' · '}<span style={{ color: 'var(--text-dim)' }}>{c.reason || 'no reason'}</span>
                  {' · '}<span style={{ color: 'var(--text-dimmer)', fontSize: 11 }}>{c.date ? new Date(c.date).toLocaleString() : ''}{c.admin?` (by ${c.admin})`:''}</span>
                </div>
              ))}
            </>
          )}
          {rn > 0 && (
            <>
              <div className="eyebrow" style={{ color: 'var(--text-dim)', margin: '10px 0 6px' }}>HWID resets</div>
              {resets.slice().reverse().map((r,i) => (
                <div key={i} style={{ padding: '6px 0', borderBottom: i < rn-1 ? '1px solid var(--border)' : 'none' }}>
                  <span style={{ color: 'var(--text-dim)' }}>{r.reason || 'no reason'}</span>
                  {' · '}<span style={{ color: 'var(--text-dimmer)', fontSize: 11 }}>{r.date ? new Date(r.date).toLocaleString() : ''}{r.admin?` (by ${r.admin})`:''}</span>
                </div>
              ))}
            </>
          )}
        </div>
      )}
    </div>
  );
}

function LicenseDetailModal({ lic, isAdmin, onClose, onChange, onCompensateAll }) {
  const [days, setDays] = React.useState(7);
  const [reason, setReason] = React.useState('');
  const [hwid, setHwid] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [live, setLive] = React.useState(null);
  const [liveErr, setLiveErr] = React.useState(null);

  // Fetch live status from worker on open
  React.useEffect(() => {
    let cancel = false;
    setLive(null); setLiveErr(null);
    api.get(`/api/license/${encodeURIComponent(lic.key)}`)
      .then(r => { if (!cancel) setLive(r.license || r); })
      .catch(e => { if (!cancel) setLiveErr(e.message); });
    return () => { cancel = true; };
  }, [lic.key]);

  const refreshLive = () => {
    setLive(null); setLiveErr(null);
    api.get(`/api/license/${encodeURIComponent(lic.key)}`)
      .then(r => setLive(r.license || r)).catch(e => setLiveErr(e.message));
  };

  const days_left = live?.expiry ? Math.ceil((Date.parse(live.expiry) - Date.now())/86400000) : null;
  const wrap = async (fn, msg) => { setBusy(true); try { const r = await fn(); if (r?.error) throw new Error(r.error); toast(msg||'Done'); refreshLive(); onChange(); } catch (e) { toast(e.message,'err'); } setBusy(false); };

  const compensate = () => {
    if (reason.trim().length < 5) { toast('Reason required (min 5 chars)', 'warn'); return; }
    wrap(() => api.post(`/api/license/${encodeURIComponent(lic.key)}/compensate`, { days: +days, reason: reason.trim(), add_to_expiry: true }), `Added ${days}d`);
  };
  const reset = () => wrap(() => api.post(`/api/license/${encodeURIComponent(lic.key)}/reset-hwid`, { reason: reason.trim()||'Manual reset' }), 'HWID reset');
  const toggleFreeze = () => wrap(() => api.post(`/api/license/${encodeURIComponent(lic.key)}/freeze`, { freeze: !live?.frozen, reason: reason.trim() }), live?.frozen?'Unfrozen':'Frozen');
  const toggleBan = () => wrap(() => api.post(`/api/license/${encodeURIComponent(lic.key)}/ban`, { ban: !live?.banned, reason: reason.trim() }), live?.banned?'Unbanned':'Banned');
  const lock = () => wrap(() => api.post(`/api/license/${encodeURIComponent(lic.key)}/lock`, { lock: true, hwid }), 'HWID locked');
  const unlock = () => wrap(() => api.post(`/api/license/${encodeURIComponent(lic.key)}/lock`, { lock: false }), 'Unlocked');
  const del = () => {
    if (!confirm(`Delete ${lic.key}? This will revoke it on the worker and cannot be undone.`)) return;
    wrap(async () => { await api.del(`/api/license/${encodeURIComponent(lic.key)}`); onClose(); return { success: true }; }, 'Key deleted');
  };

  return (
    <Modal open onClose={onClose} title={`License ${lic.key}`} width={680}>
      <div className="flex-col gap-16">
        {/* Status pills bar */}
        <div className="flex gap-8" style={{ flexWrap:'wrap', minHeight: 24, alignItems: 'center' }}>
          {!live && !liveErr && <span style={{ fontSize: 11, color: 'var(--text-dimmer)' }}>Fetching live status from license backend…</span>}
          {liveErr && <span className="pill red"><span className="dot"/>live status unavailable</span>}
          {live && live.active && !live.frozen && !live.banned && !live.is_expired && <span className="pill green"><span className="dot"/>active</span>}
          {live?.frozen && <span className="pill amber"><span className="dot"/>frozen</span>}
          {live?.banned && <span className="pill red"><span className="dot"/>banned</span>}
          {live?.locked_hwid && <span className="pill" style={{ background:'var(--bg-card-2)', color:'var(--text-dim)' }}><Ico.Lock width={10} height={10}/> hwid-locked</span>}
          {live?.is_expired && <span className="pill red"><span className="dot"/>expired</span>}
          {live?.session_active && <span className="pill gold">session active</span>}
          {live?.expiry_paused_remaining_seconds !== null && live?.expiry_paused_remaining_seconds !== undefined &&
            <span className="pill" style={{ background:'var(--bg-card-2)', color:'var(--text-dim)' }}>paused</span>}
          <div style={{ flex: 1 }}/>
          <button className="btn btn-sm btn-ghost" onClick={refreshLive} title="Refetch from license backend"><Ico.Refresh width={11} height={11} className="icon"/></button>
        </div>

        {/* Core fields */}
        <div className="grid grid-2" style={{ gap: 12, fontSize: 13 }}>
          <Field label="Product"      value={lic.product_name || lic.app_id || 'unassigned'}/>
          <Field label="App ID"       value={<span className="mono" style={{ fontSize: 12 }}>{live?.app_id || lic.app_id || '—'}</span>}/>
          <Field label="Duration"     value={lic.duration}/>
          <Field label="Expires"      value={live?.expiry ? `${days_left}d · ${new Date(live.expiry).toLocaleString()}` : (live ? 'lifetime' : '—')}/>
          <Field label="HWID"         value={<span className="mono" style={{ fontSize: 11 }}>{(live?.hwid ?? lic.hwid) || '(unbound)'}</span>}/>
          <Field label="Locked HWID"  value={<span className="mono" style={{ fontSize: 11 }}>{live?.locked_hwid || '(unlocked)'}</span>}/>
          <Field label="Discord user" value={live?.discord_username || '—'}/>
          <Field label="Activated"    value={live?.activated_at ? new Date(live.activated_at).toLocaleString() : 'never'}/>
          <Field label="Created"      value={live?.created_at ? new Date(live.created_at).toLocaleString() : (lic.issued_at ? new Date(lic.issued_at*1000).toLocaleString() : '—')}/>
          <Field label="Compensation" value={`${live?.total_compensation_days || 0}d total`}/>
          <Field label="HWID resets"  value={`${live?.total_hwid_resets || 0} on worker`}/>
          {isAdmin && <Field label="Owner" value={lic.owner || '—'}/>}
        </div>

        {/* Frozen / banned reasons */}
        {(live?.frozen_reason || live?.ban_reason) && (
          <div className="grid grid-2" style={{ gap: 12, fontSize: 12 }}>
            {live?.frozen_reason && <div style={{ padding: 10, background: 'rgba(255,184,0,0.08)', border: '1px solid rgba(255,184,0,0.3)', borderRadius: 'var(--r-md)' }}>
              <div className="eyebrow" style={{ color:'var(--warn)' }}>Frozen — reason</div>
              <div style={{ color: 'var(--text)', marginTop: 4 }}>{live.frozen_reason}</div>
              {live.frozen_at && <div style={{ fontSize: 11, color: 'var(--text-dimmer)', marginTop: 2 }}>{new Date(live.frozen_at).toLocaleString()}</div>}
            </div>}
            {live?.ban_reason && <div style={{ padding: 10, background: 'rgba(255,77,98,0.08)', border: '1px solid rgba(255,77,98,0.3)', borderRadius: 'var(--r-md)' }}>
              <div className="eyebrow" style={{ color:'var(--danger)' }}>Banned — reason</div>
              <div style={{ color: 'var(--text)', marginTop: 4 }}>{live.ban_reason}</div>
              {live.banned_at && <div style={{ fontSize: 11, color: 'var(--text-dimmer)', marginTop: 2 }}>{new Date(live.banned_at).toLocaleString()}</div>}
            </div>}
          </div>
        )}

        {/* Active session info */}
        {live?.session && (
          <div style={{ padding: 10, background: 'rgba(255,184,0,0.08)', border: '1px solid rgba(255,184,0,0.3)', borderRadius: 'var(--r-md)', fontSize: 12 }}>
            <div className="eyebrow" style={{ color: 'var(--accent)' }}>Active session</div>
            <div className="mono" style={{ color: 'var(--text-dim)', marginTop: 4 }}>{live.session.session_token || '—'}</div>
            <div style={{ color: 'var(--text-dim)', marginTop: 2 }}>HWID: <code>{live.session.hwid || '—'}</code></div>
          </div>
        )}

        {/* History */}
        <HistoryAccordion comp={live?.compensation_history} resets={live?.hwid_reset_history}/>

        <div className="divider"/>

        <div className="card-pad" style={{ padding: 0 }}>
          <div className="eyebrow" style={{ color:'var(--text-dim)', marginBottom: 8 }}>Compensate time · reason required</div>
          <div className="flex-col gap-8">
            <NumberInput value={days} onChange={setDays} min={1} max={3650} step={1} suffix="d" presets={[1,7,14,30,90]}/>
            <input
              className="input"
              value={reason}
              onChange={e=>setReason(e.target.value)}
              placeholder="e.g. loader was down for 3 hours"
              style={{ borderColor: reason && reason.trim().length < 5 ? 'var(--danger)' : undefined }}
            />
            {reason && reason.trim().length < 5 && <small style={{ fontSize: 11, color: 'var(--danger)' }}>Reason needs at least 5 characters.</small>}
          </div>
          <button className="btn btn-primary btn-sm mt-8" disabled={busy||reason.trim().length<5} onClick={compensate}><Ico.Plus className="icon" width={12} height={12}/> Add {days}d to this key</button>
        </div>

        <div className="divider"/>

        <div className="flex gap-8" style={{ flexWrap:'wrap' }}>
          <button className="btn" disabled={busy} onClick={reset}><Ico.Reset className="icon" width={12} height={12}/> Reset HWID</button>
          <button className="btn" disabled={busy||!live} onClick={toggleFreeze}><Ico.Lock className="icon" width={12} height={12}/> {live?.frozen?'Unfreeze':'Freeze key'}</button>
          <button className="btn" disabled={busy} onClick={del} style={{ color:'var(--danger)' }}><Ico.Trash className="icon" width={12} height={12}/> Delete key</button>
          {isAdmin && (
            <>
              <button className="btn" disabled={busy||!live} onClick={toggleBan}>{live?.banned?'Unban':'Ban'}</button>
            </>
          )}
        </div>
        {!isAdmin && (
          <small style={{ fontSize: 11, color: 'var(--text-dimmer)' }}>
            Deleting revokes this key and removes it from your active list. A database backup and audit snapshot are saved first.
          </small>
        )}

        {isAdmin && (
          <>
            <div className="divider"/>
            <div className="eyebrow" style={{ color:'var(--text-dim)' }}>HWID lock (admin only)</div>
            <div className="grid grid-2" style={{ gap: 8 }}>
              <input className="input mono" value={hwid} onChange={e=>setHwid(e.target.value)} placeholder="HWID to lock"/>
              <div className="flex gap-8">
                <button className="btn btn-sm" disabled={busy||!hwid} onClick={lock}><Ico.Lock className="icon" width={12} height={12}/> Lock</button>
                <button className="btn btn-sm" disabled={busy} onClick={unlock}>Unlock</button>
              </div>
            </div>
          </>
        )}
      </div>
    </Modal>
  );
}

function BulkToolsModal({ appOptions, onClose, onChange }) {
  const [tool, setTool] = React.useState('compensate');
  const [app_id, setApp] = React.useState(appOptions[0]?.id || '');
  const [days, setDays] = React.useState(7);
  const [reason, setReason] = React.useState('');
  const [affected_only, setAff] = React.useState('all');
  const [confirm, setConfirm] = React.useState(false);
  const [busy, setBusy] = React.useState(false);

  const submit = async () => {
    if (!confirm) return toast('Tick confirm to run','warn');
    setBusy(true);
    try {
      let res;
      if (tool === 'compensate') res = await api.post('/api/license/bulk/compensate', { app_id, days: +days, reason: reason||'Bulk compensation', affected_only });
      if (tool === 'reset-hwid') res = await api.post('/api/license/bulk/reset-hwid', { app_id, affected_only, reason: reason||'Bulk HWID reset' });
      if (tool === 'delete') res = await api.post('/api/license/bulk/delete', { app_id });
      if (res?.error) throw new Error(res.error);
      toast(res?.message || 'Done'); onChange(); onClose();
    } catch (e) { toast(e.message,'err'); }
    setBusy(false);
  };

  return (
    <Modal open onClose={onClose} title="Bulk tools (admin)" width={560}>
      <div className="flex-col gap-12">
        <div className="flex gap-8">
          {[['compensate','Compensate time'],['reset-hwid','Reset HWIDs'],['delete','Delete keys']].map(([id,label]) => (
            <span key={id} className={`pill ${tool===id?'gold':''}`} style={{ cursor:'pointer', padding:'6px 12px' }} onClick={()=>setTool(id)}>{label}</span>
          ))}
        </div>
        <div className="field"><label>Product</label>
          <select className="select" value={app_id} onChange={e=>setApp(e.target.value)}>
            {appOptions.map(a => <option key={a.id} value={a.id}>{a.label}</option>)}
          </select>
        </div>
        {tool === 'compensate' && <div className="field"><label>Days</label><NumberInput value={days} onChange={setDays} min={1} max={3650} suffix="d" presets={[1,7,30,90]}/></div>}
        {tool !== 'delete' && (
          <>
            <div className="field"><label>Reason</label><input className="input" value={reason} onChange={e=>setReason(e.target.value)}/></div>
            <div className="field"><label>Affected scope</label>
              <select className="select" value={affected_only} onChange={e=>setAff(e.target.value)}>
                <option value="all">All keys in this app</option>
                <option value="active">Only active</option>
                <option value="affected">Only with active sessions</option>
              </select>
            </div>
          </>
        )}
        <label style={{ display:'flex', gap:8, alignItems:'center', fontSize: 13, color: tool==='delete'?'var(--danger)':'var(--text-dim)' }}>
          <input type="checkbox" checked={confirm} onChange={e=>setConfirm(e.target.checked)}/>
          {tool==='delete' ? 'I understand this will permanently delete every key in this app.' : 'Confirm bulk operation'}
        </label>
        <div className="flex gap-8" style={{ justifyContent: 'flex-end' }}>
          <button className="btn" onClick={onClose}>Cancel</button>
          <button className="btn btn-primary" disabled={busy||!confirm} onClick={submit}>{busy?'Running…':'Run'}</button>
        </div>
      </div>
    </Modal>
  );
}


Object.assign(window, { UsersPage, RanksPage, HwidPage, ApiPage, LicensesPage });
