import React, { useCallback, useEffect, useRef, useState } from 'react';
import { api } from './api.js';
import { copyToast } from './toast.js';
import ChatPanel, { SessionLog } from './Chat.jsx';
import { GameDialog } from './GameDialog.jsx';

const STATUS_COLOR = { online: '#36f0b0', pending: '#f2b85f', lost: '#ff9f5a', down: '#ff7a8c' };
const TAG_EMOJI = { backend: '🛠️', model: '🧠', frontend: '🎨', ui: '🖌️', web: '🌐', data: '📊', devops: '⚙️', ml: '🤖', security: '🔐', api: '🔌', bot: '🤖', chat: '💬', test: '🧪', docs: '📄', design: '✏️', ops: '🚦', research: '🔬' };
const classEmoji = (c) => { for (const t of (c.tags || [])) if (TAG_EMOJI[t]) return TAG_EMOJI[t]; return '🗡️'; };
function avatarStyle(name) { let h = 0; for (const ch of String(name)) h = (h * 31 + ch.charCodeAt(0)) >>> 0; const a = h % 360, b = (a + 70 + h % 90) % 360; return { background: `linear-gradient(135deg, hsl(${a} 68% 46%), hsl(${b} 64% 32%))` }; }

// One agent card, used identically in Browse Agents and My Legion.
export function AgentCard({ c, onClick }) {
  return (
    <button className="ap-card" onClick={onClick}>
      <div className="ap-card-head2">
        <span className="ap-avatar" style={avatarStyle(c.name)}>{classEmoji(c)}</span>
        <div className="ap-card-titles"><strong>{c.name}</strong><span className="ap-kind">{c.kind}{c.managed ? ' · amerc-managed' : ''}</span></div>
      </div>
      <p className="ap-desc">{c.description || 'No description.'}</p>
      <div className="ap-card-tags">{(c.tags || []).map((t) => <span key={t}>{t}</span>)}</div>
      <div className="ap-card-foot">
        <span>by {c.owner}</span>
        <span className="ap-online"><i className={c.onlineCount ? 'pulse' : ''} style={{ background: c.onlineCount ? STATUS_COLOR.online : '#555' }} />{c.onlineCount}/{c.instanceCount} online</span>
      </div>
    </button>
  );
}

// ---- Browse Agents: all public classes, filter by tag --------------------
export function AgentBrowse({ auth, setRoute }) {
  const [classes, setClasses] = useState([]);
  const [tags, setTags] = useState({});
  const [tag, setTag] = useState('');
  const [q, setQ] = useState('');
  const [open, setOpen] = useState(null);
  const [err, setErr] = useState('');
  const [loaded, setLoaded] = useState(false);

  const load = useCallback(async () => {
    setErr('');
    try {
      const [c, s] = await Promise.all([api(`/agents/classes${tag ? `?tag=${encodeURIComponent(tag)}` : ''}`), api('/agents/stats')]);
      setClasses((c.classes || []).filter((x) => !q || (x.name + x.tags.join() + x.description).toLowerCase().includes(q.toLowerCase())));
      setTags(s.tags || {});
    } catch (e) { setErr(e.message); } finally { setLoaded(true); }
  }, [tag, q]);
  useEffect(() => { load(); }, [load]);

  // shareable agent URLs: #/agents/<slug> opens that agent's modal (bookmark/share/refresh-safe)
  const didInit = useRef(false);
  const openClass = useCallback((c) => {
    setOpen(c.id);
    if (c.slug) window.history.pushState(null, '', `${window.location.pathname}${window.location.search}#/agents/${c.slug}`);
  }, []);
  const closeClass = useCallback(() => {
    setOpen(null);
    if (window.location.hash !== '#/agents') window.history.pushState(null, '', `${window.location.pathname}${window.location.search}#/agents`);
    load();
  }, [load]);
  useEffect(() => {
    if (didInit.current || !loaded) return;
    didInit.current = true;
    const slug = window.location.hash.replace(/^#\/?/, '').split('/')[1];
    if (slug) { const hit = classes.find((x) => x.slug === slug); if (hit) setOpen(hit.id); }
  }, [loaded, classes]);
  useEffect(() => {
    const onHash = () => {
      const parts = window.location.hash.replace(/^#\/?/, '').split('/');
      if (parts[0] !== 'agents') return;
      if (!parts[1]) { setOpen(null); return; }
      const hit = classes.find((x) => x.slug === parts[1]);
      if (hit) setOpen(hit.id);
    };
    window.addEventListener('hashchange', onHash);
    window.addEventListener('popstate', onHash);
    return () => { window.removeEventListener('hashchange', onHash); window.removeEventListener('popstate', onHash); };
  }, [classes]);

  return (
    <div className="ap apb">
      <div className="apb-doorway">
        <div className="apb-door" aria-hidden="true">
          <span className="apb-door-leaf l" /><span className="apb-door-leaf r" />
          <span className="apb-door-light" />
        </div>
        <div className="apb-doorhead">
          <h2>⚔ The mercenary roster</h2>
          <p>Open the door and the mercenaries come down. Hire one — chat, embed, or drive its terminal. {loaded ? <b>{classes.length} answering the call.</b> : 'Mustering…'}</p>
          <input className="ap-search" placeholder="search the roster…" value={q} onChange={(e) => setQ(e.target.value)} />
        </div>
      </div>
      <div className="ap-tags">
        <button className={`ap-tag${!tag ? ' on' : ''}`} onClick={() => setTag('')}>★ all</button>
        {Object.entries(tags).sort((a, b) => b[1] - a[1]).map(([t, n]) => (
          <button key={t} className={`ap-tag${tag === t ? ' on' : ''}`} onClick={() => setTag(t)}>{TAG_EMOJI[t] || '🗡️'} {t} <em>{n}</em></button>
        ))}
      </div>
      {err && <p className="px-auth-err">{err}</p>}
      <div className="apb-stage" key={tag || 'all'}>
        {!loaded && Array.from({ length: 4 }).map((_, i) => (
          <div key={`sk${i}`} className="apb-merc apb-skel" aria-hidden="true" style={{ '--d': `${i * 80}ms` }}>
            <span className="apb-bub sk-p" /><span className="apb-portrait sk-av" />
          </div>
        ))}
        {loaded && classes.map((c, i) => (
          <button key={c.id} className="apb-merc" style={{ '--d': `${Math.min(i, 14) * 75}ms` }} onClick={() => openClass(c)}>
            <span className="apb-bub">
              {c.description || 'A ready blade, awaiting orders.'}
              <i className="apb-bub-tail" aria-hidden="true" />
            </span>
            <span className="apb-rope" aria-hidden="true" />
            <span className="apb-portrait" style={avatarStyle(c.name)}>
              <span className="apb-emoji">{classEmoji(c)}</span>
              <span className={`apb-life ${c.onlineCount ? 'on' : 'off'}`} title={c.onlineCount ? 'online' : 'offline'} />
              {!!c.managed && <span className="apb-crest" title="amerc-managed">🛡</span>}
            </span>
            <strong className="apb-name">{c.name}</strong>
            <span className="apb-meta"><span className="apb-tag-chip">{c.kind}</span> {c.onlineCount}/{c.instanceCount} online · by {c.owner}</span>
          </button>
        ))}
        {loaded && classes.length > 0 && (
          <button className="apb-merc apb-merc-add" style={{ '--d': `${Math.min(classes.length, 14) * 75}ms` }} onClick={() => setRoute('legion')}>
            <span className="apb-bub">Bring your own blade — it joins the roster the moment your broker checks in.<i className="apb-bub-tail" aria-hidden="true" /></span>
            <span className="apb-rope" aria-hidden="true" />
            <span className="apb-portrait apb-portrait-add">＋</span>
            <strong className="apb-name">Publish your own</strong>
            <span className="apb-meta">Open My Legion →</span>
          </button>
        )}
        {loaded && !classes.length && (q || tag) && (
          <div className="ap-empty-search">
            <span className="ap-empty-search-ico" aria-hidden="true">🔍</span>
            <h3>No mercenaries answer {q ? `“${q}”` : `#${tag}`}.</h3>
            <button className="px-action" onClick={() => { setQ(''); setTag(''); }}>Clear search</button>
          </div>
        )}
        {loaded && !classes.length && !q && !tag && (
          <div className="ap-empty-start">
            <h3>The hall is empty — be the first blade.</h3>
            <ol>
              <li><b>Publish a class</b> in <button className="ap-link" onClick={() => setRoute('legion')}>My Legion</button> — name it, tag it.</li>
              <li><b>Field an instance</b> — your broker connects and it goes <span style={{ color: STATUS_COLOR.online }}>online</span>.</li>
              <li><b>Use it</b> — chat, embed, or drive its terminal.</li>
            </ol>
            <button className="px-action" onClick={() => setRoute('legion')}>Open My Legion →</button>
          </div>
        )}
      </div>
      {open && <ClassModal id={open} auth={auth} onClose={closeClass} />}
    </div>
  );
}

function ClassModal({ id, auth, onClose }) {
  const [data, setData] = useState(null);
  const [sub, setSub] = useState(null);
  const [use, setUse] = useState(null); // instance to use
  const [err, setErr] = useState('');
  const load = useCallback(() => api(`/agents/classes/${id}`).then(setData).catch((e) => setErr(e.message)), [id]);
  useEffect(() => { load(); }, [load]);
  useEffect(() => { const onKey = (e) => { if (e.key === 'Escape') onClose(); }; window.addEventListener('keydown', onKey); return () => window.removeEventListener('keydown', onKey); }, [onClose]);
  const subscribe = async () => { try { const r = await api(`/agents/classes/${id}/subscribe`, { method: 'POST' }); setSub(r.token); } catch (e) { setErr(e.message); } };
  if (!data) return null;
  const c = data.class;
  return (
    <GameDialog title={c.name} tag={`⚔ ${c.kind}`} wide onClose={onClose}>
      <div className="cm-top">
        <span className="ap-avatar cm-avatar" style={avatarStyle(c.name)}>{classEmoji(c)}</span>
        <div className="cm-top-body">
          <span className="ap-by">by {c.owner}</span>
          <p className="ap-desc">{c.description || 'No description.'}</p>
          <div className="ap-card-tags">{c.tags.map((t) => <span key={t}>{t}</span>)}</div>
        </div>
      </div>
      <div className="ap-props">
        {!!c.managed && <span className="ap-prop managed">🛡 amerc-managed · amerc skills loaded</span>}
        <span className="ap-prop">{c.terminal_mode === 'shared' ? '🖥 one shared terminal across sessions' : '🖥 fresh terminal per session'}</span>
        {c.terminal_mode === 'shared' && <span className="ap-prop">{c.skills_reload ? '↻ client skills reload each session' : '⏸ client skills persist between sessions'}</span>}
      </div>
      {err && <p className="px-auth-err">{err}</p>}
      <div className="ap-sub">
        {auth.user ? <button className="px-action" onClick={subscribe}>Get subscription token</button> : <span className="ap-hint">Log in to subscribe.</span>}
        {sub && <code className="ap-token" onClick={() => copyToast(sub)}>{sub} ⧉</code>}
      </div>
      <h4 className="ap-h4">🛡 The squad — {data.instances.length} {data.instances.length === 1 ? 'soldier' : 'soldiers'}</h4>
      <div className="cm-squad">
        {data.instances.map((i) => (
          <div key={i.id} className={`cm-sol ${i.status === 'online' ? 'alive' : 'fallen'}`}>
            <span className="cm-sol-ico" aria-hidden="true">{i.status === 'online' ? '🪖' : '💀'}</span>
            <div className="cm-sol-body">
              <strong>#{i.id} {i.name}</strong>
              <span className="cm-sol-meta"><span className="cm-sol-dot" style={{ background: STATUS_COLOR[i.status] }} />{i.status}{i.broker ? ` · ${i.broker}` : ''} · {i.visibility}</span>
            </div>
            <button className="px-action" disabled={i.status !== 'online'} onClick={() => setUse(i)}>{i.status === 'online' ? 'Chat ▸' : 'down'}</button>
          </div>
        ))}
        {!data.instances.length && <p className="ap-hint">No soldiers fielded yet. The owner can field one in My Legion.</p>}
      </div>
      {use && <UsePanel instance={use} onClose={() => setUse(null)} />}
    </GameDialog>
  );
}

// ---- Use an instance: native chat + web-pty -------------------------------
export function UsePanel({ instance, onClose }) {
  const [mode, setMode] = useState('chatbox');
  const [sess, setSess] = useState(null);
  const [accesskey, setAccesskey] = useState(instance.accesskey || '');
  const [err, setErr] = useState('');
  const needKey = !instance.accesskey && instance.visibility !== 'public';
  const [keyOk, setKeyOk] = useState(!needKey);
  const start = useCallback(async () => {
    setErr('');
    try {
      const r = await api(`/agents/instances/${instance.id}/sessions`, { method: 'POST', body: { connector: mode, accesskey: accesskey || undefined } });
      setSess(r);
    } catch (e) { setErr(e.message); }
  }, [instance.id, mode, accesskey]);

  return (
    <div className="ap-use">
      <div className="ap-use-head">
        <div className="ap-use-tabs">
          <button className={mode === 'chatbox' ? 'on' : ''} onClick={() => { setMode('chatbox'); setSess(null); }}>Chatbox</button>
          <button className={mode === 'webpty' ? 'on' : ''} onClick={() => { setMode('webpty'); setSess(null); }}>Web PTY</button>
        </div>
        <button className="ap-use-x" onClick={onClose}>close ✕</button>
      </div>
      {needKey && !keyOk && (
        <div className="ap-use-start">
          <input placeholder="instance accesskey (private)" value={accesskey} onChange={(e) => setAccesskey(e.target.value)} />
          <button className="px-action" onClick={() => setKeyOk(true)}>Connect</button>
        </div>
      )}
      {mode === 'chatbox' && keyOk && <ChatPanel instance={instance} accesskey={accesskey || undefined} onClose={onClose} />}
      {mode === 'webpty' && keyOk && !sess && (
        <div className="ap-use-start">
          <button className="px-action" onClick={start}>Connect webpty</button>
          {err && <p className="px-auth-err">{err}</p>}
        </div>
      )}
      {mode === 'webpty' && sess && (
        <>
          <iframe title="agent terminal" className="ap-iframe" src={`https://webagent.amerc.ai/chat_session?session=${encodeURIComponent(sess.relayToken)}`} />
          <p className="ap-hint ap-pty-note">🖥️ The web terminal rides the webagent relay; chat no longer does.</p>
        </>
      )}
    </div>
  );
}

// ---- My Legion: publish classes + instances, manage ------------------------
export function KeysCard() {
  const [keys, setKeys] = useState([]);
  const [name, setName] = useState('');
  const [created, setCreated] = useState(null);
  const [err, setErr] = useState('');
  const load = useCallback(() => api('/keys').then((d) => setKeys(d.keys || [])).catch((e) => setErr(e.message)), []);
  useEffect(() => { load(); }, [load]);
  const mint = async (e) => { e.preventDefault(); setErr(''); try { const r = await api('/keys', { method: 'POST', body: { name } }); setCreated(r); setName(''); load(); } catch (e2) { setErr(e2.message); } };
  const del = async (id) => { try { await api(`/keys/${id}`, { method: 'DELETE' }); load(); } catch (e2) { setErr(e2.message); } };
  return (
    <div className="ap-keys">
      <h4 className="ap-h4">🔑 My agent keys</h4>
      <p className="ap-hint">Mint a key for your own agent to read/write amerc docs, netdisk and the agent platform. Send it as <code>Authorization: Bearer &lt;key&gt;</code>.</p>
      <form className="ap-keys-mint" onSubmit={mint}>
        <input placeholder="key name (e.g. my-bot)" value={name} onChange={(e) => setName(e.target.value)} required />
        <button className="px-action" type="submit">+ Mint key</button>
      </form>
      {created && (
        <div className="mn-created">
          <p>✅ minted — copy now, shown once:</p>
          <code className="ap-token" onClick={() => copyToast(created.key)}>{created.key} ⧉</code>
          <p className="ap-keys-try">Try it — your key drives the whole API:</p>
          <div className="ap-keys-curl">
            <code>{`curl -H "Authorization: Bearer ${created.key}" https://amerc.ai/api/agents/classes`}</code>
            <button type="button" className="ap-keys-copy" onClick={() => copyToast(`curl -H "Authorization: Bearer ${created.key}" https://amerc.ai/api/agents/classes`, 'Copied')}>copy</button>
          </div>
          <p className="ap-hint">Full endpoints in the <a href="https://docs.amerc.ai/#/api-reference">API reference</a>.</p>
        </div>
      )}
      {err && <p className="px-auth-err">{err}</p>}
      {keys.map((k) => (
        <div key={k.id} className="ap-mine-inst">
          <code className="ap-token">{k.prefix}…</code> <em>{k.name}</em>
          <span className="ap-hint" style={{ marginLeft: 'auto' }}>{k.last_used ? `used ${new Date(k.last_used).toLocaleDateString()}` : 'never used'}</span>
          <button className="px-action" onClick={() => del(k.id)}>revoke</button>
        </div>
      ))}
      {!keys.length && <p className="ap-hint">No keys yet — mint one above.</p>}
    </div>
  );
}

// A fielded instance = a soldier: alive (online), standing-by (pending), fallen (lost/down).
const SOL_FIG = { online: '🪖', pending: '🧍', lost: '🛡️', down: '💀' };
function relHb(at) { if (!at) return 'never'; const s = Math.floor((Date.now() - at) / 1000); if (s < 60) return `${s}s ago`; const m = Math.floor(s / 60); if (m < 60) return `${m}m ago`; return `${Math.floor(m / 60)}h ago`; }
function Soldier({ i, onOpen }) {
  const klass = i.status === 'online' ? 'alive' : i.status === 'pending' ? 'pending' : 'fallen';
  return (
    <button className={`lg-sol ${klass}`} onClick={() => onOpen(i)} title={`#${i.id} · ${i.status}`}>
      <span className="lg-sol-fig" aria-hidden="true">{SOL_FIG[i.status] || '💀'}</span>
      <span className="lg-sol-id">#{i.id}</span>
      <span className="lg-sol-status">{i.status}</span>
    </button>
  );
}

// Soldier detail: live status + terminal snapshot, and chat with your own soldier.
function SoldierDialog({ instance, onClose, onChanged }) {
  const [detail, setDetail] = useState(null);
  const [tab, setTab] = useState(instance.status === 'online' ? 'chat' : 'status');
  useEffect(() => {
    let alive = true;
    const f = () => api(`/agents/instances/${instance.id}${instance.accesskey ? `?accesskey=${encodeURIComponent(instance.accesskey)}` : ''}`).then((d) => { if (alive && d.instance) setDetail({ ...d.instance, tui: d.tui }); }).catch(() => {});
    f(); const t = setInterval(f, 5000); return () => { alive = false; clearInterval(t); };
  }, [instance]);
  const live = detail || instance;
  const online = live.status === 'online';
  const hb = `while true; do curl -s -XPOST https://amerc.ai/api/agents/instances/${instance.id}/heartbeat -H 'content-type: application/json' -d '{"accesskey":"${instance.accesskey}","status":"online","broker":"my-broker"}' >/dev/null; sleep 10; done`;
  const standDown = async () => {
    if (!window.confirm('Stand this soldier down? (shuts the instance down)')) return;
    try { await api(`/agents/instances/${instance.id}`, { method: 'DELETE', body: { accesskey: instance.accesskey } }); onChanged?.(); onClose(); } catch { /* ignore */ }
  };
  return (
    <GameDialog title={live.name || `Soldier #${instance.id}`} tag={online ? '🪖 ON DUTY' : live.status === 'pending' ? '🧍 STANDING BY' : '💀 FALLEN'} wide onClose={onClose}>
      <div className="lg-sd-head">
        <span className={`ch-dot ${live.status}`} />
        <div className="lg-sd-meta">
          <strong>#{instance.id} {live.name}</strong>
          <span>{live.status}{live.broker ? ` · broker ${live.broker}` : ''} · heartbeat {relHb(live.last_heartbeat)}</span>
        </div>
        {instance.accesskey && <code className="ap-token lg-sd-key" onClick={() => copyToast(instance.accesskey)} title="broker accesskey">{instance.accesskey.slice(0, 14)}… ⧉</code>}
        <button className="px-action lg-standdown" onClick={standDown}>stand down</button>
      </div>
      <div className="lg-sd-tabs">
        <button className={tab === 'chat' ? 'on' : ''} onClick={() => setTab('chat')}>Chat</button>
        <button className={tab === 'status' ? 'on' : ''} onClick={() => setTab('status')}>Status</button>
      </div>
      {tab === 'chat' && (online
        ? <ChatPanel instance={live} accesskey={instance.accesskey} onClose={onClose} />
        : <p className="ap-hint" style={{ padding: '16px 4px' }}>This soldier is {live.status}. Rouse it from the Status tab, then chat.</p>)}
      {tab === 'status' && (
        <div className="lg-sd-status">
          {live.skills?.length > 0 && <div className="ch-skills"><span className="ch-skills-label">skills loaded</span>{live.skills.map((s) => <span key={s} className="ch-skill">{s}</span>)}</div>}
          <div className="lg-tui">
            <div className="lg-tui-head">live terminal · refreshes every 5s</div>
            <pre>{live.tui || '(no terminal snapshot yet — your broker can post one via the heartbeat `tui` field)'}</pre>
          </div>
          {!online && (
            <div className="lg-rouse">
              <p className="ap-hint">Fallen until a broker checks in. Paste this anywhere with <code>curl</code> to rouse it:</p>
              <div className="ap-codeblock"><code>{hb}</code><button className="px-action" onClick={() => copyToast(hb, 'Heartbeat loop copied')}>copy</button></div>
            </div>
          )}
        </div>
      )}
    </GameDialog>
  );
}

export function LegionDashboard({ auth, setRoute }) {
  const [classes, setClasses] = useState([]);
  const [insts, setInsts] = useState([]);
  const [err, setErr] = useState('');
  const [form, setForm] = useState({ name: '', tags: '', description: '', kind: 'codex', command: 'codex --yolo', visibility: 'public', managed: false, terminalMode: 'new', skillsReload: true });
  const [newInst, setNewInst] = useState(null);
  const [logOpen, setLogOpen] = useState(null); // chatlog session to reopen
  const [openCls, setOpenCls] = useState(null); // class modal (same as Browse)
  const [soldier, setSoldier] = useState(null); // soldier (instance) detail
  const [loaded, setLoaded] = useState(false);
  useEffect(() => {
    if (!newInst) return;
    const onKey = (e) => { if (e.key === 'Escape') setNewInst(null); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [newInst]);

  const load = useCallback(async () => {
    setErr('');
    try {
      const c = await api('/agents/classes?mine=1');
      setClasses(c.classes || []);
      const all = await Promise.all((c.classes || []).map((x) => api(`/agents/instances?classId=${x.id}`).then((r) => r.instances).catch(() => [])));
      setInsts(all.flat());
    } catch (e) { setErr(e.message); } finally { setLoaded(true); }
  }, []);
  useEffect(() => { if (!auth.user) return undefined; load(); const t = setInterval(load, 15000); return () => clearInterval(t); }, [auth.user, load]);

  if (!auth.ready) return <div className="ap"><p>Loading…</p></div>;
  if (!auth.user) return (
    <div className="ap ap-gate ap-gate-rich">
      <span className="ap-gate-ico" aria-hidden="true">🗡️</span>
      <h2>Your legion awaits</h2>
      <p>Log in to publish agent classes, run instances, and put your mercenaries to work — as an embedded chatbox, a live web terminal, or behind a public URL.</p>
      <div className="ap-gate-cta">
        <button className="gm-cta-btn gm-cta-primary" onClick={() => setRoute('signin')}>Log in / Sign up</button>
        <button className="gm-cta-btn gm-cta-secondary" onClick={() => setRoute('agents')}>Browse agents</button>
      </div>
    </div>
  );

  const publishClass = async (e) => {
    e.preventDefault();
    try { await api('/agents/classes', { method: 'POST', body: { ...form, tags: form.tags.split(',').map((t) => t.trim()).filter(Boolean) } }); setForm({ ...form, name: '', tags: '', description: '' }); load(); }
    catch (e2) { setErr(e2.message); }
  };
  const publishInstance = async (classId) => {
    try { const r = await api('/agents/instances', { method: 'POST', body: { classId, visibility: 'public' } }); setNewInst(r); load(); }
    catch (e2) { setErr(e2.message); }
  };
  return (
    <div className="ap">
      <div className="ap-head"><h2>My Legion</h2><span className="ap-by">{auth.user.handle}</span></div>
      {err && <p className="px-auth-err">{err}</p>}
      <div className="ap-booth-grid">
        <form className="ap-publish" onSubmit={publishClass}>
          <h4 className="ap-h4">Publish an agent class</h4>
          <label className="ap-pub-field"><span>Class name</span>
            <input placeholder="e.g. Backend Helper" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })} required />
          </label>
          <label className="ap-pub-field"><span>Tags</span>
            <input placeholder="comma-separated: backend, model" value={form.tags} onChange={(e) => setForm({ ...form, tags: e.target.value })} />
          </label>
          <label className="ap-pub-field"><span>Description</span>
            <textarea placeholder="What does it do for users?" value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} />
          </label>
          <div className="ap-row">
            <label className="ap-pub-field"><span>Kind</span>
              <input placeholder="codex" value={form.kind} onChange={(e) => setForm({ ...form, kind: e.target.value })} />
            </label>
            <label className="ap-pub-field"><span>Visibility</span>
              <select value={form.visibility} onChange={(e) => setForm({ ...form, visibility: e.target.value })}><option value="public">public</option><option value="private">private publish</option></select>
            </label>
          </div>
          <label className="ap-pub-field"><span>Launch command</span>
            <input placeholder="codex --yolo" value={form.command} onChange={(e) => setForm({ ...form, command: e.target.value })} />
          </label>
          <p className="ap-hint">Kind is your agent's runtime; the launch command is how your broker starts it.</p>
          <div className="ap-pub-props">
            <label className="ap-check"><input type="checkbox" checked={form.managed} onChange={(e) => setForm({ ...form, managed: e.target.checked })} /> amerc-managed — load amerc skills into the agent</label>
            <label className="ap-pub-field"><span>Terminal per session</span>
              <select value={form.terminalMode} onChange={(e) => setForm({ ...form, terminalMode: e.target.value })}>
                <option value="new">new terminal for each session</option>
                <option value="shared">same terminal, shared across sessions</option>
              </select>
            </label>
            {form.terminalMode === 'shared' && (
              <label className="ap-check"><input type="checkbox" checked={form.skillsReload} onChange={(e) => setForm({ ...form, skillsReload: e.target.checked })} /> reload the client's skills on each new session</label>
            )}
          </div>
          <button className="px-action px-auth-submit" type="submit">Publish class</button>
        </form>
        <div className="ap-mine">
          {!loaded ? (
            <div className="ap-mine-skel" aria-hidden="true">
              <span className="sk-p" style={{ width: '40%', height: 12 }} />
              <div className="ap-skel-row"><span className="sk-av" /><span className="sk-p" style={{ flex: 1 }} /></div>
              <div className="ap-skel-row"><span className="sk-av" /><span className="sk-p" style={{ flex: 1 }} /></div>
              <span className="sk-p" style={{ width: '30%', height: 12, marginTop: 14 }} />
              <div className="ap-skel-row"><span className="sk-p" style={{ flex: 1 }} /></div>
            </div>
          ) : !classes.length ? (
            <div className="ap-booth-welcome">
              <span className="ap-booth-welcome-ico" aria-hidden="true">🗡️</span>
              <h3>Welcome to your legion, {auth.user.handle}.</h3>
              <p>This is where you arm and dispatch your mercenaries. Three steps to go live:</p>
              <ol>
                <li><b>Publish a class</b> — name your agent and tag its skills, in the form on the left.</li>
                <li><b>Run an instance</b> — your broker connects and it turns <span className="ap-on-word">online</span>.</li>
                <li><b>Deliver it</b> — embed a chatbox, drive a web terminal, or expose a local port via <b>My Mansion</b>.</li>
              </ol>
              <p className="ap-hint">Your broker authenticates with an instance accesskey (shown when you publish one) or an agent key — mint one below.</p>
            </div>
          ) : (
            <>
              <h4 className="ap-h4">⚔ The war camp — tap a banner for details, a soldier to command</h4>
              <div className="lg-camp">
                {classes.map((c) => {
                  const squad = insts.filter((i) => i.class_id === c.id);
                  return (
                    <div key={c.id} className="lg-unit">
                      <button className="lg-banner" onClick={() => setOpenCls(c.id)} title={`${c.name} — open details`}>
                        <span className="lg-banner-crest" style={avatarStyle(c.name)}>{classEmoji(c)}</span>
                        <strong>{c.name}</strong>
                        <span className="lg-banner-sub">{c.visibility}{c.managed ? ' · 🛡' : ''}</span>
                        <span className="lg-banner-count"><i className={c.onlineCount ? 'pulse' : ''} style={{ background: c.onlineCount ? STATUS_COLOR.online : '#555' }} />{c.onlineCount}/{c.instanceCount}</span>
                      </button>
                      <div className="lg-soldiers">
                        {squad.map((i) => <Soldier key={i.id} i={i} onOpen={setSoldier} />)}
                        <button className="lg-recruit" onClick={() => publishInstance(c.id)} title="field a new soldier">＋</button>
                      </div>
                    </div>
                  );
                })}
              </div>
            </>
          )}
        </div>
      </div>
      <div className="ap-chatlog">
        <h4 className="ap-h4">💬 My chatlog</h4>
        <p className="ap-hint">Every session keeps its log — reopen one to read it back, or continue it if still active.</p>
        <SessionLog onOpen={setLogOpen} />
      </div>
      <KeysCard />
      {logOpen && (
        <div className="ap-modal" onClick={() => setLogOpen(null)}>
          <div className="ap-modal-box ap-modal-chat" onClick={(e) => e.stopPropagation()}>
            <ChatPanel instance={{ id: logOpen.instance_id, name: logOpen.inst_name || logOpen.class_name }} sessionId={logOpen.id} onClose={() => setLogOpen(null)} />
          </div>
        </div>
      )}
      {openCls && <ClassModal id={openCls} auth={auth} onClose={() => { setOpenCls(null); load(); }} />}
      {soldier && <SoldierDialog instance={soldier} onClose={() => { setSoldier(null); load(); }} onChanged={load} />}
      {newInst && (() => {
        const hb = `while true; do curl -s -XPOST https://amerc.ai/api/agents/instances/${newInst.id}/heartbeat -H 'content-type: application/json' -d '{"accesskey":"${newInst.accesskey}","status":"online","broker":"my-broker"}' >/dev/null; sleep 10; done`;
        return (
          <div className="ap-modal" onClick={() => setNewInst(null)}>
            <div className="ap-modal-box" onClick={(e) => e.stopPropagation()}>
              <div className="ap-modal-head"><h3>Instance #{newInst.id} — bring it online</h3><button onClick={() => setNewInst(null)}>✕</button></div>
              <p className="ap-hint">Registered, but <b style={{ color: '#ff9f5a' }}>offline</b> until a broker checks in. Two steps bring it to life:</p>
              <ol className="ap-steps">
                <li><b>Heartbeat</b> — keeps it marked online (check in every &lt;30s). Paste this anywhere with <code>curl</code>:
                  <div className="ap-codeblock"><code>{hb}</code><button className="px-action" onClick={() => copyToast(hb, 'Heartbeat loop copied')}>copy</button></div>
                </li>
                <li><b>Relay</b> — bridge your agent so live sessions reach it, over the broker WebSocket:
                  <code className="ap-token" onClick={() => copyToast(newInst.relayWs)}>{newInst.relayWs} ⧉</code>
                  <span className="ap-hint">The amerc webagent broker speaks this relay for you — point it at the accesskey below.</span>
                </li>
              </ol>
              <label className="ap-field">accesskey · your broker's secret<code className="ap-token" onClick={() => copyToast(newInst.accesskey)}>{newInst.accesskey} ⧉</code></label>
            </div>
          </div>
        );
      })()}
    </div>
  );
}
