// =================================================================
// pw-screens-brand-wallet.jsx — the brand's prepaid wallet (payments phase 2).
//
// Balance is read from the money record (never stored in the browser), per currency, split into
// available and held. A top-up hands the browser to the gateway the brand's billing country picks:
// Stripe (redirect) or PayU (signed form post). The wallet is credited only when the gateway's
// verified confirmation reaches the API — never by this screen, and never by the return redirect.
//
// The brand pays amount + DropCap's gateway fee + GST; the wallet is credited the amount. The form
// shows that breakdown (from the API's quote) before the brand pays. Every confirmed top-up gets a
// tax invoice, linked from the history as a printable page. While DropCap's GST details are not
// configured, invoices are withheld and a banner says why.
//
// Live mode talks to /advertiser/v1/wallet* with the advertiser token (advCall, defined in
// pw-screens-recommendations.jsx). Demo mode renders the `brandWallet` / `brandInvoices` fixtures in
// paywall-data.js and never pretends money arrived.
// =================================================================

const BW_CURRENCIES_STRIPE = ["USD", "EUR", "GBP", "AUD", "CAD", "SGD", "AED"];
const BW_STATUS_TONE = { succeeded: "good", failed: "warn", checkout_open: "info", created: "neutral" };
const BW_STATUS_LABEL = { succeeded: "Credited", failed: "Failed", checkout_open: "Awaiting payment", created: "Started" };
const BW_RETURN_MSG = {
  success: { tone: "info", text: "Payment submitted. Your balance updates as soon as the gateway confirms it — usually within a minute." },
  cancelled: { tone: "info", text: "Checkout cancelled. Nothing was charged." },
  failed: { tone: "warn", text: "The payment failed at the gateway. Nothing was credited." },
  pending: { tone: "info", text: "The gateway reports the payment as pending. It will be credited when it clears." },
  unverified: { tone: "warn", text: "We could not verify the gateway's response, so nothing was credited. If you were charged, contact support with the reference." },
  error: { tone: "warn", text: "The payment could not be confirmed. If you were charged, contact support with the reference." },
};
const BW_TREATMENT_LABEL = {
  intra_state: "CGST + SGST (same state as DropCap)",
  inter_state: "IGST (different state from DropCap)",
  export: "Export of services — zero-rated under LUT",
  domestic_unsplit: "GST (state split pending DropCap configuration)",
};

const bwMoney = (minor, currency) => {
  const n = Number(minor || 0) / 100;
  try { return new Intl.NumberFormat(undefined, { style: "currency", currency }).format(n); }
  catch (e) { return `${n.toFixed(2)} ${currency}`; }
};
const bwPct = (bps) => `${(Number(bps || 0) / 100).toFixed(Number(bps) % 100 ? 2 : 0)}%`;
const bwNewKey = () => (crypto.randomUUID ? crypto.randomUUID() : `k-${Date.now()}-${Math.random().toString(16).slice(2)}`);

// Demo-mode quote: the same arithmetic as the API (brandWalletService.topupBreakdown / 0054
// brand_topup_breakdown). Live mode always asks the API.
const bwRound = (minor, bps) => Math.floor((minor * bps + 5000) / 10000);
const bwBreakdown = (amount_minor, currency, fee_bps, gst_bps, tax_treatment) => {
  const fee_minor = bwRound(amount_minor, fee_bps);
  const taxable_minor = amount_minor + fee_minor;
  let cgst_minor = 0, sgst_minor = 0, igst_minor = 0, gst_minor = 0;
  if (tax_treatment === "intra_state") { cgst_minor = bwRound(taxable_minor, gst_bps / 2); sgst_minor = cgst_minor; gst_minor = 2 * cgst_minor; }
  else if (tax_treatment === "inter_state") { igst_minor = bwRound(taxable_minor, gst_bps); gst_minor = igst_minor; }
  else if (tax_treatment === "domestic_unsplit") { gst_minor = bwRound(taxable_minor, gst_bps); }
  return { currency, amount_minor, fee_bps, fee_minor, gst_bps, tax_treatment, taxable_minor, cgst_minor, sgst_minor, igst_minor, gst_minor, charge_minor: taxable_minor + gst_minor };
};

// GSTIN check for quick feedback while typing. The API validates again (and the database a third time).
const BW_GSTIN_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const bwGstinValid = (g) => {
  if (!/^[0-9]{2}[A-Z]{5}[0-9]{4}[A-Z][1-9A-Z]Z[0-9A-Z]$/.test(g)) return false;
  const st = Number(g.slice(0, 2));
  if (!((st >= 1 && st <= 38) || st === 97)) return false;
  let s = 0;
  for (let i = 0; i < 14; i++) { const p = BW_GSTIN_CHARS.indexOf(g[i]) * (i % 2 ? 2 : 1); s += Math.floor(p / 36) + (p % 36); }
  return BW_GSTIN_CHARS[(36 - (s % 36)) % 36] === g[14];
};

// PayU needs the browser to POST signed fields; a redirect would drop them and the hash with them.
const bwSubmitForm = (url, fields) => {
  const form = document.createElement("form");
  form.method = "POST";
  form.action = url;
  Object.entries(fields || {}).forEach(([k, v]) => {
    const input = document.createElement("input");
    input.type = "hidden"; input.name = k; input.value = v;
    form.appendChild(input);
  });
  document.body.appendChild(form);
  form.submit();
};

// ---- Printable tax invoice. Every value is escaped: names and addresses are brand-entered text. ----
const bwEsc = (s) => String(s == null ? "" : s).replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
const bwInvoiceHtml = (inv) => {
  const m = (minor) => bwEsc(bwMoney(minor, inv.currency));
  const a = inv.recipient.address || {};
  const addr = [a.line1, a.line2, a.city, a.region, a.postal_code, inv.recipient.country].filter(Boolean).map(bwEsc).join(", ");
  const taxRows = [];
  if (inv.tax_treatment === "intra_state") {
    taxRows.push(["CGST", inv.cgst_bps, inv.cgst_minor], ["SGST", inv.sgst_bps, inv.sgst_minor]);
  } else if (inv.tax_treatment === "inter_state") {
    taxRows.push(["IGST", inv.igst_bps, inv.igst_minor]);
  } else {
    taxRows.push(["IGST", 0, 0]);
  }
  return `<!doctype html><html><head><meta charset="utf-8"><title>Tax invoice ${bwEsc(inv.invoice_no)}</title>
<style>
  body{font:13px/1.45 -apple-system,Segoe UI,Roboto,Arial,sans-serif;color:#111;background:#fff;margin:0;padding:32px}
  .sheet{max-width:780px;margin:0 auto}
  h1{font-size:20px;margin:0 0 4px} .muted{color:#555}
  .grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin:18px 0}
  .box{border:1px solid #ccc;border-radius:6px;padding:12px}
  .box h3{margin:0 0 6px;font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:#555}
  table{width:100%;border-collapse:collapse;margin-top:8px} th,td{border:1px solid #ccc;padding:6px 8px;text-align:left}
  th{background:#f3f3f3;font-size:12px} td.r,th.r{text-align:right} tfoot td{font-weight:700}
  .note{margin-top:14px;padding:10px;border:1px dashed #888;border-radius:6px}
  .bar{display:flex;justify-content:space-between;align-items:flex-start}
  button{font:inherit;padding:6px 12px;cursor:pointer}
  @media print{button{display:none}body{padding:0}}
</style></head><body><div class="sheet">
<div class="bar"><div><h1>Tax Invoice</h1><div class="muted">${inv.tax_treatment === "export" ? "Export of services" : "Original for recipient"}</div></div>
<button onclick="window.print()">Print / Save as PDF</button></div>
<div class="grid">
  <div class="box"><h3>Supplier</h3><strong>${bwEsc(inv.supplier.name)}</strong><br>${bwEsc(inv.supplier.address)}<br>
    GSTIN: <strong>${bwEsc(inv.supplier.gstin)}</strong><br>State: ${bwEsc(inv.supplier.state_code)} - ${bwEsc(inv.supplier.state_name)}</div>
  <div class="box"><h3>Invoice</h3>Invoice no: <strong>${bwEsc(inv.invoice_no)}</strong><br>Invoice date: ${bwEsc(inv.invoice_date)}<br>
    Financial year: ${bwEsc(inv.financial_year)}<br>Place of supply: ${bwEsc(inv.place_of_supply)}<br>Payment reference: ${bwEsc(inv.topup_reference)}</div>
  <div class="box" style="grid-column:1/3"><h3>Recipient</h3><strong>${bwEsc(inv.recipient.name)}</strong><br>${addr}<br>
    GSTIN: ${inv.recipient.gstin ? `<strong>${bwEsc(inv.recipient.gstin)}</strong>` : "Not applicable (outside India)"}</div>
</div>
<div class="muted">${bwEsc(inv.description)}</div>
<table>
  <thead><tr><th>#</th><th>Description</th><th>SAC</th><th class="r">Taxable value</th></tr></thead>
  <tbody>
    <tr><td>1</td><td>Advertising services — prepaid wallet balance</td><td>${bwEsc(inv.sac)}</td><td class="r">${m(inv.amount_minor)}</td></tr>
    <tr><td>2</td><td>Payment gateway fee</td><td>${bwEsc(inv.sac)}</td><td class="r">${m(inv.fee_minor)}</td></tr>
  </tbody>
  <tfoot><tr><td colspan="3">Total taxable value</td><td class="r">${m(inv.taxable_minor)}</td></tr></tfoot>
</table>
<table>
  <thead><tr><th>Tax</th><th class="r">Rate</th><th class="r">Taxable value</th><th class="r">Amount</th></tr></thead>
  <tbody>${taxRows.map(([n, bps, amt]) => `<tr><td>${n}</td><td class="r">${bwEsc(bwPct(bps))}</td><td class="r">${m(inv.taxable_minor)}</td><td class="r">${m(amt)}</td></tr>`).join("")}</tbody>
  <tfoot><tr><td colspan="3">Invoice total</td><td class="r">${m(inv.total_minor)}</td></tr></tfoot>
</table>
${inv.note ? `<div class="note">${bwEsc(inv.note)}</div>` : ""}
<p class="muted" style="margin-top:18px">Amounts in ${bwEsc(inv.currency)}. This is a computer-generated invoice.</p>
</div></body></html>`;
};
const bwOpenInvoice = (inv) => {
  const url = URL.createObjectURL(new Blob([bwInvoiceHtml(inv)], { type: "text/html" }));
  // Not "noopener": with it window.open always returns null and the fallback would navigate away.
  const w = window.open(url, "_blank");
  if (!w) window.location.assign(url);
  setTimeout(() => URL.revokeObjectURL(url), 60_000);
};

// ---- Billing profile form ----
const BrandBillingForm = ({ wallet, busy, onSave, onCancel }) => {
  const p = wallet.billing_profile || {};
  const a = p.billing_address || {};
  const [f, setF] = React.useState({
    billing_country: p.billing_country || "", billing_name: p.billing_name || "", gstin: p.gstin || "",
    line1: a.line1 || "", line2: a.line2 || "", city: a.city || "", region: a.region || "", postal_code: a.postal_code || "",
  });
  const [err, setErr] = React.useState("");
  const set = (k) => (e) => setF(s => ({ ...s, [k]: e.target.value }));
  const country = f.billing_country.trim().toUpperCase();
  const gstin = f.gstin.trim().toUpperCase();
  const submit = () => {
    if (!/^[A-Z]{2}$/.test(country)) return setErr("Enter a two-letter country code, e.g. IN or US.");
    if (!f.billing_name.trim() || !f.line1.trim() || !f.city.trim() || !f.region.trim() || !f.postal_code.trim()) return setErr("Name and full address are required.");
    if (country === "IN" && !gstin) return setErr("A GSTIN is required for a brand billed in India.");
    if (gstin && !bwGstinValid(gstin)) return setErr("That GSTIN is not valid — check it character by character.");
    setErr("");
    onSave({
      billing_country: country, billing_name: f.billing_name.trim(), gstin: gstin || null,
      billing_address: { line1: f.line1.trim(), ...(f.line2.trim() ? { line2: f.line2.trim() } : {}), city: f.city.trim(), region: f.region.trim(), postal_code: f.postal_code.trim() },
    });
  };
  return <Card title="Billing details" subtitle="Printed on your tax invoices. Required before your first top-up.">
    <div style={{ display: "grid", gap: 12 }}>
      {err && <Banner tone="warn" icon={<IconInfo size={15}/>}>{err}</Banner>}
      <FieldGroup cols={2}>
        <div><label className="pw-lbl">Billing country</label>
          <Input value={f.billing_country} placeholder="IN, US, GB…" disabled={!!p.billing_country} onChange={set("billing_country")}/></div>
        <div><label className="pw-lbl">GSTIN {country === "IN" ? "(required)" : "(optional)"}</label>
          <Input value={f.gstin} placeholder="27AAPFU0939F1ZV" onChange={set("gstin")}/></div>
      </FieldGroup>
      <div><label className="pw-lbl">Legal name</label><Input value={f.billing_name} onChange={set("billing_name")}/></div>
      <FieldGroup cols={2}>
        <div><label className="pw-lbl">Address line 1</label><Input value={f.line1} onChange={set("line1")}/></div>
        <div><label className="pw-lbl">Address line 2</label><Input value={f.line2} onChange={set("line2")}/></div>
      </FieldGroup>
      <FieldGroup cols={3}>
        <div><label className="pw-lbl">City</label><Input value={f.city} onChange={set("city")}/></div>
        <div><label className="pw-lbl">State / region</label><Input value={f.region} onChange={set("region")}/></div>
        <div><label className="pw-lbl">Postal code</label><Input value={f.postal_code} onChange={set("postal_code")}/></div>
      </FieldGroup>
      <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
        <Btn kind="primary" onClick={submit} disabled={busy}>{busy ? "Saving…" : "Save billing details"}</Btn>
        {onCancel && <Btn onClick={onCancel} disabled={busy}>Cancel</Btn>}
        <span style={{ fontSize: 12, color: "var(--muted)" }}>The billing country sets your gateway and can only be set once.</span>
      </div>
    </div>
  </Card>;
};

const BwBreakdown = ({ q }) => {
  if (!q) return null;
  const row = (label, minor, strong) => <div style={{ display: "flex", justifyContent: "space-between", padding: "4px 0", fontWeight: strong ? 700 : 400, borderTop: strong ? "1px solid var(--border)" : "none" }}>
    <span>{label}</span><span style={{ fontVariantNumeric: "tabular-nums" }}>{bwMoney(minor, q.currency)}</span></div>;
  return <div style={{ border: "1px solid var(--border)", borderRadius: 8, padding: "10px 14px", maxWidth: 440 }}>
    {row("Credited to your wallet", q.amount_minor)}
    {row(`Gateway fee (${bwPct(q.fee_bps)})`, q.fee_minor)}
    {q.tax_treatment === "intra_state" && <>{row(`CGST (${bwPct(q.gst_bps / 2)})`, q.cgst_minor)}{row(`SGST (${bwPct(q.gst_bps / 2)})`, q.sgst_minor)}</>}
    {q.tax_treatment === "inter_state" && row(`IGST (${bwPct(q.gst_bps)})`, q.igst_minor)}
    {q.tax_treatment === "domestic_unsplit" && row(`GST (${bwPct(q.gst_bps)})`, q.gst_minor)}
    {q.tax_treatment === "export" && row("GST (0% — export of services)", 0)}
    {row("Total to pay", q.charge_minor, true)}
    <div style={{ fontSize: 11.5, color: "var(--muted)", marginTop: 4 }}>{BW_TREATMENT_LABEL[q.tax_treatment]}. GST is on the amount plus the fee.</div>
  </div>;
};

const BrandWalletScreen = ({ data }) => {
  const live = advLive();
  const token = loadAdvToken();
  const [wallet, setWallet] = React.useState(live ? null : (data.brandWallet || null));
  const [status, setStatus] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [amount, setAmount] = React.useState("");
  const [currency, setCurrency] = React.useState("");
  const [quote, setQuote] = React.useState(null);
  const [editingBilling, setEditingBilling] = React.useState(false);
  // One Idempotency-Key per form submission: kept across retries and double clicks of the same
  // amount/currency, replaced when either changes or after a checkout is handed off.
  const submitKey = React.useRef(null);

  const returned = React.useMemo(() => {
    try { return new URLSearchParams(window.location.search).get("topup"); } catch (e) { return null; }
  }, []);

  const load = React.useCallback(async () => {
    if (!live) return;
    if (!token) { setStatus("Sign in on Login & Profile to see your wallet."); return; }
    try {
      setWallet(await advCall("/advertiser/v1/wallet", { method: "GET", token }));
      setStatus("");
    } catch (e) { setStatus(e.message || "Could not load the wallet."); }
  }, [live, token]);
  React.useEffect(() => { load(); }, [load]);

  const cur = currency || wallet?.default_currency || "USD";
  const currencyOptions = wallet?.gateway === "payu" ? ["INR"] : BW_CURRENCIES_STRIPE;
  const profileOk = !!wallet?.billing_profile?.complete;

  // The breakdown before paying: from the API in live mode, local arithmetic in demo mode.
  React.useEffect(() => {
    const n = Number(amount);
    if (!wallet || !profileOk || !Number.isFinite(n) || n <= 0) { setQuote(null); return; }
    if (!live) {
      setQuote(bwBreakdown(Math.round(n * 100), cur, wallet.fee_bps, wallet.gst_bps, wallet.tax_treatment || "export"));
      return;
    }
    let cancelled = false;
    const t = setTimeout(async () => {
      try {
        const out = await advCall(`/advertiser/v1/wallet/quote?amount=${encodeURIComponent(n)}&currency=${encodeURIComponent(cur)}`, { method: "GET", token });
        if (!cancelled) setQuote(out.breakdown);
      } catch (e) { if (!cancelled) setQuote(null); }
    }, 300);
    return () => { cancelled = true; clearTimeout(t); };
  }, [amount, cur, wallet, profileOk, live, token]);

  const changeAmount = (v) => { submitKey.current = null; setAmount(v); };
  const changeCurrency = (v) => { submitKey.current = null; setCurrency(v); };

  const saveBilling = async (body) => {
    if (!live) {
      setWallet(w => {
        const c = body.billing_country;
        const state = "29"; // demo: DropCap registered in Karnataka
        const tax_treatment = c !== "IN" ? "export" : (body.gstin || "").slice(0, 2) === state ? "intra_state" : "inter_state";
        return { ...w, billing_country: c, gateway: c === "IN" ? "payu" : "stripe", default_currency: c === "IN" ? "INR" : "USD", tax_treatment,
          billing_profile: { ...body, gstin: body.gstin || null, complete: true, missing: [] } };
      });
      setCurrency(""); setEditingBilling(false);
      setStatus("Demo mode: billing details saved locally.");
      return;
    }
    setBusy(true);
    try {
      setWallet(await advCall("/advertiser/v1/wallet/billing-profile", { method: "PUT", token, body }));
      setCurrency(""); setEditingBilling(false);
      setStatus("Billing details saved.");
    } catch (e) { setStatus(e.message || "Could not save the billing details."); }
    finally { setBusy(false); }
  };

  const topUp = async () => {
    const n = Number(amount);
    const min = wallet?.topup_min_amount ?? 10;
    if (!Number.isFinite(n) || n <= 0) { setStatus("Enter an amount."); return; }
    if (n < min) { setStatus(`The minimum top-up is ${min.toFixed(2)} ${cur}.`); return; }
    if (!submitKey.current) submitKey.current = bwNewKey();
    if (!live) {
      const q = bwBreakdown(Math.round(n * 100), cur, wallet.fee_bps, wallet.gst_bps, wallet.tax_treatment || "export");
      const row = {
        topup_id: `demo-${Date.now()}`, reference: `btdemo${String(Date.now()).slice(-17)}`, gateway: wallet.gateway,
        currency: cur, amount_minor: q.amount_minor, fee_minor: q.fee_minor, gst_minor: q.gst_minor, charge_minor: q.charge_minor,
        tax_treatment: q.tax_treatment, status: "checkout_open", failure_reason: null, invoice_status: "pending",
        invoice_withheld_reason: null, invoice_id: null, invoice_no: null, created_at: new Date().toISOString(), confirmed_at: null,
      };
      setWallet(w => ({ ...w, topups: [row, ...(w.topups || [])] }));
      submitKey.current = null;
      setAmount("");
      setStatus(`Demo mode: a ${wallet.gateway === "payu" ? "PayU" : "Stripe"} checkout for ${bwMoney(q.charge_minor, cur)} would open now. Nothing is credited without a real gateway confirmation.`);
      return;
    }
    setBusy(true);
    setStatus("Starting checkout…");
    try {
      const out = await advCall("/advertiser/v1/wallet/topups", { token, body: { amount: n, currency: cur }, idempotencyKey: submitKey.current });
      if (out.checkout?.kind === "redirect") { submitKey.current = null; window.location.assign(out.checkout.url); return; }
      if (out.checkout?.kind === "form_post") { submitKey.current = null; bwSubmitForm(out.checkout.url, out.checkout.fields); return; }
      setStatus("Payments are simulated on this environment: no checkout was opened and nothing will be credited.");
      load();
    } catch (e) {
      // Same key on retry: a second click reaches the same top-up, never a second checkout.
      setStatus((e.message || "Could not start the top-up.") + " You can retry safely.");
    } finally { setBusy(false); }
  };

  const openInvoice = async (t) => {
    if (!t.invoice_id) return;
    if (!live) {
      const inv = (data.brandInvoices || {})[t.invoice_id];
      if (inv) bwOpenInvoice(inv); else setStatus("Demo mode: no sample invoice for this row.");
      return;
    }
    try {
      const out = await advCall(`/advertiser/v1/wallet/invoices/${encodeURIComponent(t.invoice_id)}`, { method: "GET", token });
      bwOpenInvoice(out.invoice);
    } catch (e) { setStatus(e.message || "Could not load the invoice."); }
  };

  const balances = wallet?.balances || [];
  const topups = wallet?.topups || [];
  const ret = returned && BW_RETURN_MSG[returned];
  const inv = wallet?.invoicing;

  return <Screen>
    <PageHead icon={IconCard} title="Wallet" subtitle="Prepaid balance your campaigns spend from. Top up by card or UPI; funds appear once the gateway confirms payment."/>
    {!live && <Banner tone="info" icon={<IconInfo size={15}/>}>Demo mode — sample balance and history. No gateway is called and nothing is credited.</Banner>}
    {inv && !inv.enabled && <Banner tone="warn" icon={<IconInfo size={15}/>}>{inv.reason || "Tax invoices are not being issued yet."}</Banner>}
    {ret && <Banner tone={ret.tone} icon={<IconInfo size={15}/>}>{ret.text}</Banner>}
    {status && <Banner tone="info" icon={<IconInfo size={15}/>}>{status}</Banner>}

    {wallet && <>
      {balances.length === 0 ? (
        <Card><EmptyState icon={<IconCard size={26}/>} title="No balance yet" description="Your first confirmed top-up appears here."/></Card>
      ) : (
        <KpiGrid cols={Math.min(4, balances.length * 2)}>
          {balances.flatMap(b => [
            <StatCard key={`${b.currency}-a`} label={`Available · ${b.currency}`} value={bwMoney(b.available_minor, b.currency)}/>,
            <StatCard key={`${b.currency}-h`} label={`Held · ${b.currency}`} value={bwMoney(b.held_minor, b.currency)} sub="Not yet spendable"/>,
          ])}
        </KpiGrid>
      )}

      {(!profileOk || editingBilling) && <BrandBillingForm wallet={wallet} busy={busy} onSave={saveBilling} onCancel={profileOk ? () => setEditingBilling(false) : null}/>}

      {profileOk && <Card title="Top up" subtitle={wallet.gateway === "payu" ? "Paid through PayU in INR (India billing country)." : "Paid through Stripe."}>
        <div style={{ display: "grid", gap: 14 }}>
          <FieldGroup cols={2}>
            <div><label className="pw-lbl">Amount to credit</label><Input type="number" min={wallet.topup_min_amount} step="0.01" value={amount} placeholder={`Min ${wallet.topup_min_amount}`} onChange={e => changeAmount(e.target.value)}/></div>
            <div><label className="pw-lbl">Currency</label><Select value={cur} onChange={changeCurrency} options={currencyOptions.map(c => ({ label: c, value: c }))}/></div>
          </FieldGroup>
          <BwBreakdown q={quote}/>
          <div style={{ display: "flex", gap: 10, alignItems: "center", flexWrap: "wrap" }}>
            <Btn kind="primary" onClick={topUp} disabled={busy || !quote}>{busy ? "Working…" : quote ? `Pay ${bwMoney(quote.charge_minor, quote.currency)}` : "Continue to payment"}</Btn>
            <span style={{ fontSize: 12, color: "var(--muted)" }}>
              Invoiced to {wallet.billing_profile.billing_name}{wallet.billing_profile.gstin ? ` · GSTIN ${wallet.billing_profile.gstin}` : ""} · {wallet.billing_country}
            </span>
            <Btn onClick={() => setEditingBilling(true)} disabled={busy || editingBilling}>Edit billing details</Btn>
          </div>
        </div>
      </Card>}

      <Card title="Top-up history" padded={false}>
        <Table rows={topups.map(t => ({ ...t, id: t.topup_id }))} columns={[
          { key: "created_at", label: "Started", render: t => new Date(t.created_at).toLocaleString() },
          { key: "amount_minor", label: "Credited", align: "right", render: t => bwMoney(t.amount_minor, t.currency) },
          { key: "charge_minor", label: "Charged", align: "right", render: t => bwMoney(t.charge_minor ?? t.amount_minor, t.currency) },
          { key: "gateway", label: "Gateway", render: t => t.gateway === "payu" ? "PayU" : "Stripe" },
          { key: "status", label: "Status", render: t => <Pill tone={BW_STATUS_TONE[t.status] || "neutral"}>{BW_STATUS_LABEL[t.status] || t.status}</Pill> },
          { key: "invoice_no", label: "Invoice", render: t =>
              t.invoice_id ? <a href="#" onClick={e => { e.preventDefault(); openInvoice(t); }}>{t.invoice_no}</a>
              : t.invoice_status === "withheld" ? <span title={t.invoice_withheld_reason || ""}><Pill tone="warn">Withheld</Pill></span>
              : <span style={{ color: "var(--muted)" }}>—</span> },
          { key: "reference", label: "Reference", mono: true },
        ]}/>
      </Card>
    </>}
  </Screen>;
};
