// =================================================================
// pw-screens-op-agents.jsx — Operator · Agent operators and the agent take (agent payments, 0069).
//
// An agent operator is an AI company whose agents buy per-article access from publishers. It holds
// API keys (the agent authenticates with one) and a prepaid wallet per currency, topped up by card or
// UPI through the API. There is no operator self-serve portal in this build: staff create operators
// and issue keys here.
//
// Any staff member reads. Creating an operator, editing its profile and issuing a key are ordinary
// writes; suspending or reactivating it, revoking a key and scheduling an agent take rate are
// destructive, so a manager does not see those controls and the API refuses them with a 403.
// A key's secret is returned once, by the call that issued it, and never again.
// =================================================================

const AO_STATUS_TONE = { active: "good", suspended: "warn" };
const AO_KEY_TONE = { active: "good", revoked: "neutral" };
const aoNotify = (msg, tone) => (window.__toast ? window.__toast(msg, tone) : null);
const aoIsManager = (session) => rcIsManager(session);
const aoAgentLabel = (catalog, token) => { const a = catalog.find((x) => x.token === token); return a ? `${a.label} (${a.operator})` : token; };

// Declared agents: chosen from the known-agent catalogue, or typed as a token.
const DeclaredAgentsPicker = ({ catalog, value, onChange }) => {
  const [pick, setPick] = React.useState("");
  const [token, setToken] = React.useState("");
  const add = (t) => { if (t && !value.includes(t) && value.length < 20) onChange([...value, t]); };
  const tokenOk = AP_TOKEN_RE.test(token.trim()) && !value.includes(token.trim());
  return (
    <div style={{ display: "grid", gap: 8 }}>
      <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
        {value.length === 0 ? <span style={{ fontSize: 12.5, color: "var(--muted)" }}>None yet. Purchases only apply a publisher's per-agent rule to agents declared here.</span>
          : value.map((t) => (
            <span key={t} className="hx-pill hx-pill-accent" style={{ display: "inline-flex", gap: 6, alignItems: "center" }}>
              {aoAgentLabel(catalog, t)}
              <button type="button" aria-label={`Remove ${t}`} onClick={() => onChange(value.filter((x) => x !== t))}
                style={{ border: 0, background: "transparent", cursor: "pointer", color: "inherit", padding: 0, lineHeight: 1 }}>×</button>
            </span>
          ))}
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: 6 }}>
        <Select value={pick} onChange={(v) => { add(v); setPick(""); }} placeholder="Add a known agent…"
          options={catalog.filter((a) => !value.includes(a.token)).map((a) => ({ label: `${a.label} · ${a.operator}`, value: a.token }))}/>
        <span/>
      </div>
      <div style={{ display: "grid", gridTemplateColumns: "1fr auto", gap: 6 }}>
        <Input value={token} placeholder="Or type a token, e.g. mybot" onChange={(e) => setToken(e.target.value.toLowerCase())}/>
        <Btn kind="secondary" size="sm" disabled={!tokenOk || value.length >= 20} onClick={() => { add(token.trim()); setToken(""); }}>Add</Btn>
      </div>
    </div>
  );
};

const OpAgentOperatorsScreen = ({ data }) => {
  const list = useListQuery("adminListAgentOperators", { sort: "created_at", dir: "desc", filters: { status: "" }, watch: data.agentOperators });
  React.useEffect(() => { if (list.error) aoNotify("Could not load agent operators.", "bad"); }, [list.error]);
  const catalog = apCatalog(data);
  return (
    <Screen>
      <PageHead icon={IconCpu} title="Agent operators"
        subtitle="AI companies whose agents pay publishers per article. Each has API keys and a prepaid wallet per currency."
        actions={<Btn kind="primary" size="sm" onClick={() => window.__nav("agent-operator-create")}>New agent operator</Btn>}/>
      <Banner tone="info" icon={<IconInfo size={15}/>}>
        Operators top up their wallets through the API (card or UPI; India on PayU, elsewhere on Stripe). Suspending an operator
        or revoking a key is superadmin only.
      </Banner>
      <Card title="Operators" padded={false}>
        <div style={{ padding: "12px 16px", borderBottom: "1px solid var(--line-2)" }}>
          <ListToolbar list={list} placeholder="Search name or contact email"
            filters={[{ key: "status", label: "States", options: statusOptions(["active", "suspended"]) }]}/>
        </div>
        <DataTable rows={list.rows} keyField="agent_id" server={list.server} empty="No agent operators yet."
          onRowClick={(r) => window.__nav("op-agent-operator", r.agent_id)}
          columns={[
            { key: "name", label: "Operator", sortable: true, render: (r) => <span><strong style={{ color: "var(--ink)" }}>{r.name}</strong><div style={{ fontSize: 12, color: "var(--muted)" }}>{r.contact_email}</div></span> },
            { key: "billing_country", label: "Billing", render: (r) => <span style={{ fontFamily: "var(--mono)", fontSize: 12.5 }}>{r.billing_country}</span> },
            { key: "declared_agents", label: "Declared agents", render: (r) => <span style={{ fontSize: 12.5 }}>{(r.declared_agents || []).map((t) => aoAgentLabel(catalog, t)).join(", ") || "—"}</span> },
            { key: "status", label: "State", render: (r) => <Pill tone={AO_STATUS_TONE[r.status] || "neutral"}>{r.status}</Pill> },
            { key: "created_at", label: "Created", sortable: true, render: (r) => <span style={{ fontFamily: "var(--mono)", fontSize: 12.5 }}>{fmt.date(r.created_at)}</span> },
          ]}/>
      </Card>
    </Screen>
  );
};

// The billing fields as the contract takes them: an address is all-or-nothing, blank optionals omitted.
const aoBillingBody = (f) => {
  const t = (v) => String(v || "").trim();
  const addr = { line1: t(f.line1), city: t(f.city), region: t(f.region), postal_code: t(f.postal_code) };
  if (t(f.line2)) addr.line2 = t(f.line2);
  const anyAddr = [f.line1, f.line2, f.city, f.region, f.postal_code].some((v) => t(v));
  const addrOk = !anyAddr || (addr.line1 && addr.city && addr.region && addr.postal_code);
  return { address: anyAddr ? addr : null, addrOk };
};

const AgentOperatorCreateScreen = ({ data }) => {
  const [f, setF] = React.useState({ name: "", contact_email: "", billing_country: "", billing_name: "", line1: "", line2: "", city: "", region: "", postal_code: "", gstin: "", declared_agents: [] });
  const [busy, setBusy] = React.useState(false);
  const set = (p) => setF((x) => ({ ...x, ...p }));
  const { address, addrOk } = aoBillingBody(f);
  const emailOk = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(f.contact_email.trim());
  const gstinOk = !f.gstin.trim() || /^[0-9]{2}[A-Z0-9]{13}$/.test(f.gstin.trim().toUpperCase());
  const canSave = f.name.trim() && emailOk && /^[A-Z]{2}$/.test(f.billing_country) && addrOk && gstinOk && !busy;

  const save = async () => {
    setBusy(true);
    const body = {
      name: f.name.trim(), contact_email: f.contact_email.trim(), billing_country: f.billing_country,
      ...(f.billing_name.trim() ? { billing_name: f.billing_name.trim() } : {}),
      ...(address ? { billing_address: address } : {}),
      ...(f.gstin.trim() ? { gstin: f.gstin.trim().toUpperCase() } : {}),
      declared_agents: f.declared_agents,
    };
    const now = new Date().toISOString();
    const demoId = (crypto.randomUUID ? crypto.randomUUID() : `ao-${Date.now()}`);
    const row = { agent_id: demoId, name: body.name, contact_email: body.contact_email, billing_country: body.billing_country, status: "active",
      declared_agents: body.declared_agents, created_at: now, billing_name: body.billing_name || null, billing_address: body.billing_address || null,
      gstin: body.gstin || null, created_by: "you", updated_at: now, balances: [], keys: [] };
    try {
      const res = await window.__api.write("adminCreateAgentOperator",
        (d) => ({ ...d, agentOperators: [row, ...(d.agentOperators || [])] }),
        { body, ok: "Agent operator created", err: "Couldn’t create the agent operator" });
      pwListsStale();
      window.__nav("op-agent-operator", (res && res.operator && res.operator.agent_id) || demoId);
    } catch (e) { /* write() has already said why */ } finally { setBusy(false); }
  };

  return (
    <FormFrame page={{ icon: IconCpu, back: "Agent operators" }} open onClose={pwBackTo("op-agent-operators")} title="New agent operator" width={620}
      footer={<><Btn kind="secondary" onClick={pwBackTo("op-agent-operators")}>Cancel</Btn><Btn kind="primary" disabled={!canSave} onClick={save}>{busy ? "Creating…" : "Create operator"}</Btn></>}>
      <FormField label="Name"><Input value={f.name} maxLength={120} onChange={(e) => set({ name: e.target.value })} placeholder="Northstar Research"/></FormField>
      <FormField label="Contact email" hint="Where top-up receipts and invoices go. Kept in the database only.">
        <Input type="email" value={f.contact_email} maxLength={254} onChange={(e) => set({ contact_email: e.target.value })}/>
      </FormField>
      <FormField label="Billing country" hint="Decides the top-up rail: India on PayU (INR), everywhere else on Stripe (USD).">
        <Select value={f.billing_country} onChange={(v) => set({ billing_country: v })} placeholder="Choose a country" options={window.pwCountryOptions()}/>
      </FormField>
      <FormField label="Billing name (optional)"><Input value={f.billing_name} maxLength={200} onChange={(e) => set({ billing_name: e.target.value })}/></FormField>
      <FormField label="Billing address (optional)" hint="All of line 1, city, region and postal code, or none of them.">
        <div style={{ display: "grid", gap: 6 }}>
          <Input value={f.line1} placeholder="Line 1" onChange={(e) => set({ line1: e.target.value })}/>
          <Input value={f.line2} placeholder="Line 2 (optional)" onChange={(e) => set({ line2: e.target.value })}/>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: 6 }}>
            <Input value={f.city} placeholder="City" onChange={(e) => set({ city: e.target.value })}/>
            <Input value={f.region} placeholder="State / region" onChange={(e) => set({ region: e.target.value })}/>
            <Input value={f.postal_code} placeholder="Postal code" onChange={(e) => set({ postal_code: e.target.value })}/>
          </div>
        </div>
      </FormField>
      {!addrOk && <div style={{ fontSize: 12.5, color: "var(--bad)", marginBottom: 8 }}>Fill in line 1, city, region and postal code, or clear the address.</div>}
      <FormField label="GSTIN (optional)" hint="Indian operators with a GST registration; checked by the API.">
        <Input value={f.gstin} maxLength={15} onChange={(e) => set({ gstin: e.target.value })} placeholder="29ABCDE1234F1ZW"/>
      </FormField>
      <FormField label="Declared agents" hint="The agents this operator runs. A publisher's per-agent rule applies to a purchase when it names one of these.">
        <DeclaredAgentsPicker catalog={apCatalog(data)} value={f.declared_agents} onChange={(v) => set({ declared_agents: v })}/>
      </FormField>
    </FormFrame>
  );
};

// The issued secret, once. Never stored in the fixture store or anywhere else.
const AgentSecretBox = ({ issued, onDismiss }) => {
  const [copied, setCopied] = React.useState(false);
  if (!issued) return null;
  const copy = async () => { try { await navigator.clipboard.writeText(issued.secret); setCopied(true); } catch (e) { aoNotify("Copy failed — select the text instead.", "warn"); } };
  return (
    <Banner tone="warn" icon={<IconKey size={15}/>}>
      <div style={{ display: "grid", gap: 8 }}>
        <strong>Copy this secret now. It is shown once and cannot be retrieved again — DropCap keeps only a hash.</strong>
        {issued.demo && <span>Demo mode: this is a made-up secret; no key exists.</span>}
        <div>Key: <code>{issued.key.key_id}</code></div>
        {issued.secret ? (
          <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
            <code style={{ wordBreak: "break-all", padding: "6px 8px", background: "var(--surface, #fff)", border: "1px solid var(--line)", borderRadius: 6, flex: "1 1 280px" }}>{issued.secret}</code>
            <Btn size="sm" kind="secondary" icon={<IconCopy size={14}/>} onClick={copy}>{copied ? "Copied" : "Copy"}</Btn>
          </div>
        ) : <span>The secret was already shown by the first response to this request and is not returned again.</span>}
        <span>The agent sends it as <code>Authorization: Bearer &lt;secret&gt;</code>.</span>
        <div><Btn size="sm" onClick={onDismiss}>I have copied it</Btn></div>
      </div>
    </Banner>
  );
};

const aoRandom = (n, alphabet) => Array.from({ length: n }, () => alphabet[Math.floor(Math.random() * alphabet.length)]).join("");

const OpAgentOperatorDetailScreen = ({ data, agentId }) => {
  const { session } = useSession();
  const manager = aoIsManager(session);
  const r = useApRead("adminGetAgentOperator", { params: { agent_id: agentId } },
    () => { const op = (data.agentOperators || []).find((o) => o.agent_id === agentId); return op ? { operator: op } : null; },
    { enabled: Boolean(agentId) });
  React.useEffect(() => { if (r.error) aoNotify("Could not load this agent operator.", "bad"); }, [r.error]);
  const op = r.value && r.value.operator;
  const catalog = apCatalog(data);
  const [issued, setIssued] = React.useState(null);
  const [modal, setModal] = React.useState(null); // { kind: "status" | "revoke" | "edit" | "issue", key? }
  const [busy, setBusy] = React.useState(false);

  const patchStore = (fn) => (d) => ({ ...d, agentOperators: (d.agentOperators || []).map((o) => (o.agent_id === agentId ? fn(o) : o)) });
  const done = () => { setModal(null); pwListsStale(); if (r.live) r.reload(); };
  const run = async (fn) => { setBusy(true); try { await fn(); done(); } catch (e) { /* write() has already said why */ } finally { setBusy(false); } };

  if (!op) {
    return (
      <Screen>
        <PageHead icon={IconCpu} title="Agent operator" actions={<Btn kind="secondary" icon={<IconArrowL size={14}/>} onClick={pwBackTo("op-agent-operators")}>Back to Agent operators</Btn>}/>
        <Card><div style={{ padding: 24, textAlign: "center", color: "var(--muted)" }}>{r.loading ? "Loading…" : "This agent operator was not found."}</div></Card>
      </Screen>
    );
  }

  const suspended = op.status === "suspended";
  // The target is fixed when the dialog opens, so the optimistic update cannot flip it mid-save.
  const setStatus = (wasSuspended, reason) => run(() => window.__api.write("adminUpdateAgentOperator",
    patchStore((o) => ({ ...o, status: wasSuspended ? "active" : "suspended", updated_at: new Date().toISOString() })),
    { params: { agent_id: agentId }, body: { status: wasSuspended ? "active" : "suspended", reason },
      ok: wasSuspended ? "Operator reactivated" : "Operator suspended", err: "Couldn’t change the operator's status" }));
  const revoke = (key, reason) => run(() => window.__api.write("adminRevokeAgentApiKey",
    patchStore((o) => ({ ...o, keys: o.keys.map((k) => (k.key_id === key.key_id ? { ...k, status: "revoked", revoked_at: new Date().toISOString() } : k)) })),
    { params: { agent_id: agentId, key_id: key.key_id }, body: { reason }, ok: `Key ${key.key_id} revoked`, err: "Couldn’t revoke the key" }));
  const edit = (body) => run(() => window.__api.write("adminUpdateAgentOperator",
    patchStore((o) => ({ ...o, ...body, updated_at: new Date().toISOString() })),
    { params: { agent_id: agentId }, body, ok: "Operator updated", err: "Couldn’t update the operator" }));
  const issue = (expiresAt) => run(async () => {
    const lower = "abcdefghijklmnopqrstuvwxyz0123456789";
    const b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
    const keyId = `ak_${aoRandom(8, lower)}`;
    const now = new Date().toISOString();
    const demoKey = { key_id: keyId, status: "active", expires_at: expiresAt || null, last_used_at: null, revoked_at: null, created_by: "you", created_at: now };
    const res = await window.__api.write("adminIssueAgentApiKey",
      patchStore((o) => ({ ...o, keys: [demoKey, ...(o.keys || [])] })),
      { params: { agent_id: agentId }, body: expiresAt ? { expires_at: expiresAt } : {}, ok: "Key issued", err: "Couldn’t issue a key" });
    if (res && res.mock) setIssued({ key: demoKey, secret: `${keyId}.${aoRandom(43, b64)}`, demo: true });
    else if (res && res.key) setIssued({ key: res.key, secret: res.secret });
  });

  return (
    <Screen>
      <PageHead icon={IconCpu} title={op.name}
        subtitle={<span>{op.contact_email} · billing {op.billing_country} · <Pill tone={AO_STATUS_TONE[op.status] || "neutral"}>{op.status}</Pill></span>}
        actions={<div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
          <Btn kind="secondary" icon={<IconArrowL size={14}/>} onClick={pwBackTo("op-agent-operators")}>Back to Agent operators</Btn>
          <Btn kind="secondary" size="sm" icon={<IconPencil size={14}/>} onClick={() => setModal({ kind: "edit" })}>Edit</Btn>
          {!manager && <Btn kind={suspended ? "primary" : "danger"} size="sm" onClick={() => setModal({ kind: "status", wasSuspended: suspended })}>{suspended ? "Reactivate" : "Suspend"}</Btn>}
        </div>}/>
      {suspended && <Banner tone="warn" icon={<IconInfo size={15}/>}>Suspended: its agents cannot buy anything, and every key is refused with <code>agent_suspended</code>. The wallet is kept.</Banner>}
      <AgentSecretBox issued={issued} onDismiss={() => setIssued(null)}/>

      <KpiGrid cols={Math.max(2, (op.balances || []).length + 1)}>
        {(op.balances || []).length === 0
          ? <StatCard label="Wallet" value="—" sub="No wallet yet: created by the first top-up"/>
          : op.balances.map((b) => <StatCard key={b.currency} label={`Wallet · ${b.currency}`} value={apMoney(b.balance_minor, b.currency)} sub="Prepaid, spent per article"/>)}
        <StatCard label="Active keys" value={fmt.number((op.keys || []).filter((k) => k.status === "active").length)} sub={`${(op.keys || []).length} issued in total`}/>
      </KpiGrid>

      <div style={{ display: "grid", gridTemplateColumns: "minmax(0,1fr) minmax(0,1fr)", gap: "var(--gap)" }} className="pw-ao-grid">
        <Card title="Profile">
          {[
            ["Contact email", op.contact_email],
            ["Billing country", op.billing_country],
            ["Billing name", op.billing_name || "—"],
            ["Billing address", op.billing_address ? [op.billing_address.line1, op.billing_address.line2, op.billing_address.city, op.billing_address.region, op.billing_address.postal_code].filter(Boolean).join(", ") : "—"],
            ["GSTIN", op.gstin || "—"],
            ["Created", `${fmt.dateTime(op.created_at)}${op.created_by ? ` by ${op.created_by}` : ""}`],
            ["Updated", fmt.dateTime(op.updated_at)],
          ].map(([k, v]) => (
            <div key={k} style={{ display: "flex", justifyContent: "space-between", gap: 12, fontSize: 13, padding: "6px 0", borderBottom: "1px solid var(--line)" }}>
              <span style={{ color: "var(--muted)" }}>{k}</span><span style={{ textAlign: "right" }}>{v}</span>
            </div>
          ))}
        </Card>
        <Card title="Declared agents" subtitle="A publisher's per-agent rule applies to this operator's purchases when it names one of these.">
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
            {(op.declared_agents || []).length === 0 ? <span style={{ fontSize: 13, color: "var(--muted)" }}>None declared.</span>
              : op.declared_agents.map((t) => <Pill key={t} tone="accent">{aoAgentLabel(catalog, t)}</Pill>)}
          </div>
        </Card>
      </div>

      <Card title="API keys" subtitle="The agent authenticates with a key's secret. Issue a new key before revoking the old one to rotate without downtime." padded={false}
        action={<Btn kind="primary" size="sm" icon={<IconKey size={14}/>} disabled={busy} onClick={() => setModal({ kind: "issue" })}>Issue a key</Btn>}>
        <DataTable rows={op.keys || []} keyField="key_id" rowsPerPage={0} empty="No keys issued yet."
          columns={[
            { key: "key_id", label: "Key", render: (k) => <span style={{ fontFamily: "var(--mono)", fontSize: 12.5 }}>{k.key_id}</span> },
            { key: "status", label: "State", render: (k) => <Pill tone={AO_KEY_TONE[k.status] || "neutral"}>{k.status}</Pill> },
            { key: "created_at", label: "Issued", render: (k) => <span style={{ fontSize: 12.5 }}>{fmt.date(k.created_at)}{k.created_by ? ` · ${k.created_by}` : ""}</span> },
            { key: "last_used_at", label: "Last used", render: (k) => <span style={{ fontSize: 12.5 }}>{k.last_used_at ? fmt.relativeTime(k.last_used_at) : "never"}</span> },
            { key: "expires_at", label: "Expires", render: (k) => <span style={{ fontSize: 12.5 }}>{k.expires_at ? fmt.date(k.expires_at) : "never"}</span> },
            { key: "revoked_at", label: "Revoked", render: (k) => <span style={{ fontSize: 12.5 }}>{k.revoked_at ? fmt.date(k.revoked_at) : "—"}</span> },
            { key: "actions", label: "", sortable: false, render: (k) => (k.status === "active" && !manager)
              ? <Btn kind="danger" size="sm" onClick={(e) => { e.stopPropagation(); setModal({ kind: "revoke", key: k }); }}>Revoke</Btn> : null },
          ]}/>
      </Card>

      {modal && modal.kind === "status" && (
        <ReasonModal title={modal.wasSuspended ? `Reactivate ${op.name}` : `Suspend ${op.name}`} busy={busy} confirmLabel={modal.wasSuspended ? "Reactivate" : "Suspend"}
          intro={modal.wasSuspended ? "Its agents can buy again with any active key." : "Its agents stop being able to buy at once; every key is refused. The wallet and keys are kept."}
          onClose={() => setModal(null)} onConfirm={(reason) => setStatus(modal.wasSuspended, reason)}/>
      )}
      {modal && modal.kind === "revoke" && (
        <ReasonModal title={`Revoke ${modal.key.key_id}`} busy={busy} confirmLabel="Revoke key"
          intro="Requests with this key are refused from now on. This cannot be undone; issue a new key if the agent still needs one."
          onClose={() => setModal(null)} onConfirm={(reason) => revoke(modal.key, reason)}/>
      )}
      {modal && modal.kind === "issue" && <IssueKeyModal busy={busy} onClose={() => setModal(null)} onConfirm={issue}/>}
      {modal && modal.kind === "edit" && <EditOperatorModal op={op} catalog={catalog} busy={busy} onClose={() => setModal(null)} onConfirm={edit}/>}
      <style>{`@media (max-width: 900px) { .pw-ao-grid { grid-template-columns: 1fr !important; } }`}</style>
    </Screen>
  );
};

const ReasonModal = ({ title, intro, confirmLabel, busy, onClose, onConfirm }) => {
  const [reason, setReason] = React.useState("");
  return (
    <Modal open onClose={onClose} title={title} width={480}
      footer={<><Btn kind="secondary" onClick={onClose}>Cancel</Btn><Btn kind="primary" disabled={!reason.trim() || busy} onClick={() => onConfirm(reason.trim())}>{busy ? "Saving…" : confirmLabel}</Btn></>}>
      <div style={{ fontSize: 13, color: "var(--ink-2)", marginBottom: 12, lineHeight: 1.5 }}>{intro}</div>
      <FormField label="Reason" hint="Required. Recorded in the audit trail.">
        <Input value={reason} maxLength={500} onChange={(e) => setReason(e.target.value)}/>
      </FormField>
    </Modal>
  );
};

const IssueKeyModal = ({ busy, onClose, onConfirm }) => {
  const [expires, setExpires] = React.useState("");
  const at = expires ? new Date(expires) : null;
  const ok = !at || at.getTime() > Date.now();
  return (
    <Modal open onClose={onClose} title="Issue an API key" width={460}
      footer={<><Btn kind="secondary" onClick={onClose}>Cancel</Btn><Btn kind="primary" disabled={!ok || busy} onClick={() => onConfirm(at ? at.toISOString() : null)}>{busy ? "Issuing…" : "Issue key"}</Btn></>}>
      <div style={{ fontSize: 13, color: "var(--ink-2)", marginBottom: 12, lineHeight: 1.5 }}>
        The secret is shown once, on the next screen. Pass it to the operator securely; DropCap cannot show it again.
      </div>
      <FormField label="Expires (optional)" hint="Blank: never. A key can always be revoked.">
        <Input type="datetime-local" value={expires} onChange={(e) => setExpires(e.target.value)}/>
      </FormField>
      {!ok && <div style={{ fontSize: 12.5, color: "var(--bad)" }}>The expiry must be in the future.</div>}
    </Modal>
  );
};

const EditOperatorModal = ({ op, catalog, busy, onClose, onConfirm }) => {
  const [f, setF] = React.useState({ name: op.name, contact_email: op.contact_email, declared_agents: op.declared_agents || [] });
  const ok = f.name.trim() && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(f.contact_email.trim());
  const body = () => {
    const b = {};
    if (f.name.trim() !== op.name) b.name = f.name.trim();
    if (f.contact_email.trim() !== op.contact_email) b.contact_email = f.contact_email.trim();
    if (JSON.stringify(f.declared_agents) !== JSON.stringify(op.declared_agents || [])) b.declared_agents = f.declared_agents;
    return b;
  };
  const changed = Object.keys(body()).length > 0;
  return (
    <Modal open onClose={onClose} title={`Edit ${op.name}`} width={540}
      footer={<><Btn kind="secondary" onClick={onClose}>Cancel</Btn><Btn kind="primary" disabled={!ok || !changed || busy} onClick={() => onConfirm(body())}>{busy ? "Saving…" : "Save"}</Btn></>}>
      <FormField label="Name"><Input value={f.name} maxLength={120} onChange={(e) => setF({ ...f, name: e.target.value })}/></FormField>
      <FormField label="Contact email"><Input type="email" value={f.contact_email} maxLength={254} onChange={(e) => setF({ ...f, contact_email: e.target.value })}/></FormField>
      <FormField label="Declared agents"><DeclaredAgentsPicker catalog={catalog} value={f.declared_agents} onChange={(v) => setF({ ...f, declared_agents: v })}/></FormField>
    </Modal>
  );
};

// ---- Agent take: a tab on Commission rates ----
const AgentTakeRatesCard = ({ data }) => {
  const r = useApRead("adminListAgentTakeRates", {}, () => ({ rates: data.agentTakeRates || [] }));
  React.useEffect(() => { if (r.error) aoNotify("Could not load agent take rates.", "bad"); }, [r.error]);
  const rates = (r.value && r.value.rates) || [];
  const def = rates.find((x) => x.publisher_id === null && x.status === "in_force");
  return (
    <Card title="Agent take" subtitle={`DropCap's share of what AI agents pay publishers per article${def ? ` — ${rcPct(def.take_bps)} by default now` : ""}. A new rate takes over from its date; a purchase keeps the rate it was split at.`} padded={false}>
      <DataTable rows={rates} keyField="rate_id" defaultSort={{ key: "effective_from", dir: "desc" }} searchKeys={["publisher_name", "note"]}
        searchPlaceholder="Search publisher or note" empty={r.loading ? "Loading…" : "No agent take rates."}
        columns={[
          { key: "publisher_name", label: "Applies to", accessor: (x) => x.publisher_name || "", render: (x) => x.publisher_id === null
            ? <strong style={{ color: "var(--ink)" }}>Every publisher (default)</strong> : (x.publisher_name || x.publisher_id) },
          { key: "take_bps", label: "DropCap keeps", align: "right", accessor: (x) => x.take_bps, render: (x) => <strong>{rcPct(x.take_bps)}</strong> },
          { key: "publisher_keeps", label: "Publisher keeps", align: "right", accessor: (x) => 10000 - x.take_bps, render: (x) => rcPct(10000 - x.take_bps) },
          { key: "effective_from", label: "From", render: (x) => <span style={{ fontFamily: "var(--mono)", fontSize: 12.5 }}>{fmt.date(x.effective_from)}</span> },
          { key: "status", label: "State", render: (x) => <Pill tone={RC_STATE_TONE[x.status] || "neutral"}>{String(x.status || "").replace("_", " ")}</Pill> },
          { key: "note", label: "Note", render: (x) => <span style={{ fontSize: 12.5, color: "var(--muted)" }}>{x.note || ""}{x.created_by ? ` · ${x.created_by}` : ""}</span> },
        ]}/>
    </Card>
  );
};

const AgentTakeCreateScreen = ({ data }) => {
  useRefList(["listPublishers"]);
  const pubs = data.publishers || [];
  const [f, setF] = React.useState({ publisher_id: "", take: "", effective_from: rcNextMonthLocal(), note: "" });
  const [busy, setBusy] = React.useState(false);
  const set = (p) => setF((x) => ({ ...x, ...p }));
  const pct = f.take === "" ? NaN : Number(f.take);
  const rateOk = Number.isFinite(pct) && pct >= 0 && pct <= 100;
  const from = f.effective_from ? new Date(f.effective_from) : null;
  const dateOk = Boolean(from && from.getTime() > Date.now());
  const canSave = rateOk && dateOk && f.note.trim() && !busy;
  const save = async () => {
    setBusy(true);
    const body = { publisher_id: f.publisher_id || null, take_bps: Math.round(pct * 100), effective_from: from.toISOString(), note: f.note.trim() };
    const pub = pubs.find((p) => (p.publisher_id || p.id) === f.publisher_id);
    const row = { rate_id: (crypto.randomUUID ? crypto.randomUUID() : `at-${Date.now()}`), ...body, publisher_name: pub ? pub.name : null,
      status: "scheduled", created_by: "you", created_at: new Date().toISOString() };
    try {
      await window.__api.write("adminCreateAgentTakeRate", (d) => ({ ...d, agentTakeRates: [row, ...(d.agentTakeRates || [])] }),
        { body, ok: "Agent take scheduled", err: "Couldn’t schedule that rate" });
      window.__nav("op-rates");
    } catch (e) { /* write() has already said why */ } finally { setBusy(false); }
  };
  return (
    <FormFrame page={{ icon: IconCoin, back: "Commission rates" }} kind="modal" open onClose={pwBackTo("op-rates")} title="Schedule an agent take rate" width={520}
      footer={<><Btn kind="secondary" onClick={pwBackTo("op-rates")}>Cancel</Btn><Btn kind="primary" disabled={!canSave} onClick={save}>{busy ? "Saving…" : "Schedule rate"}</Btn></>}>
      <FormField label="Applies to" hint="Blank schedules the platform default; choosing a publisher gives them their own rate.">
        <Select value={f.publisher_id} onChange={(v) => set({ publisher_id: v })}
          options={[{ label: "Every publisher (default)", value: "" }, ...pubs.map((p) => ({ label: p.name, value: p.publisher_id || p.id }))]}/>
      </FormField>
      <FormField label="DropCap keeps (%)" hint={rateOk ? `The publisher keeps ${(100 - pct).toFixed(2).replace(/\.00$/, "")}% of each agent purchase.` : "Between 0 and 100."}>
        <Input type="number" step="0.01" min="0" max="100" value={f.take} placeholder="25" onChange={(e) => set({ take: e.target.value })}/>
      </FormField>
      <FormField label="Effective from" hint="Must be in the future; defaults to the start of next month (UTC).">
        <Input type="datetime-local" value={f.effective_from} onChange={(e) => set({ effective_from: e.target.value })}/>
      </FormField>
      <FormField label="Note" hint="Why — recorded with the rate and in the audit trail.">
        <Input value={f.note} maxLength={500} onChange={(e) => set({ note: e.target.value })}/>
      </FormField>
      {f.effective_from && !dateOk && <div style={{ fontSize: 12.5, color: "var(--bad)" }}>Effective date must be in the future.</div>}
    </FormFrame>
  );
};

Object.assign(window, { OpAgentOperatorsScreen, AgentOperatorCreateScreen, OpAgentOperatorDetailScreen, AgentTakeRatesCard, AgentTakeCreateScreen });
