// Bylt Capital CRM — offer links: the form that builds one, and the card on a
// deal that shows each link and what the client has done with it.

// The same arithmetic the server and the client's page use.
function offerTerms(amount, factor, term, freq) {
  const payback = Math.round(amount * factor * 100) / 100, weekly = freq === "Weekly";
  const payments = Math.max(1, weekly ? Math.ceil(term / 5) : term);      // equal payments: one a business day, or one a week
  return { amount, factor, term, freq: weekly ? "Weekly" : "Daily", payback, payments, payment: Math.round((payback / payments) * 100) / 100 };
}
// A package's term as typed (weeks or months), otherwise business days.
const termOf = (t) => (t.termCount && (t.termUnit === "weeks" || t.termUnit === "months") ? fmt.term(t.termCount, t.termUnit) : `${t.term} days`);
const termsLine = (t) => [fmt.money(t.amount), termOf(t), `factor ${t.factor}`, `${fmt.money(t.payment)} ${String(t.freq).toLowerCase()}`].join(" · ");
const OFFER_STATE = { open: { pill: "new", label: "Open" }, accepted: { pill: "funded", label: "Accepted" }, expired: { pill: "pending", label: "Expired" }, closed: { pill: "off", label: "Closed" } };
const docsOnFile = (deal) => { const d = (deal && deal.documents) || {}; return { dlFront: !!d.dlFront, dlBack: !!d.dlBack, check: !!d.check }; };
const plusDays = (n) => fmt.localDay(new Date(Date.now() + n * 864e5).toISOString());
const remembered = (key) => { try { return localStorage.getItem(key) || ""; } catch (e) { return ""; } };
const remember = (key, value) => { try { localStorage.setItem(key, value); } catch (e) { /* private window */ } };
let rowSeq = 0;
const rowKey = () => "r" + (++rowSeq);

// ── Create / edit an offer link ─────────────────────────────────────────────
function OfferSheet({ deal, offer, subs, user, office, onClose, onSaved }) {
  const editing = !!offer;
  const have = docsOnFile(deal);
  const missing = !(have.dlFront && have.check);
  const withTerms = (subs || []).filter(s => ["offer", "approved", "counter"].includes(s.status) && fmt.num(s.amount) > 0 && fmt.num(s.factor) >= 1 && fmt.termDays(s.term, s.termUnit) >= 5);
  const fromLenders = () => withTerms.slice(0, 6).map((s, i) => ({ key: rowKey(), label: `Option ${"ABCDEF"[i]}`, lender: s.lender || "", amount: String(s.amount), factor: String(s.factor), term: String(s.term), termUnit: s.termUnit === "weeks" ? "weeks" : "days", freq: s.freq === "Weekly" ? "Weekly" : "Daily" }));
  const blankPackage = (i) => ({ key: rowKey(), label: `Option ${"ABCDEF"[i] || ""}`.trim(), lender: "", amount: "", factor: "", term: "", termUnit: "days", freq: "Daily" });

  const [mode, setMode] = useState(offer ? offer.mode : withTerms.length ? "packages" : "adjust");
  const a = (offer && offer.adjust) || {};
  const [minAmount, setMin] = useState(a.minAmount ? String(a.minAmount) : "");
  const [maxAmount, setMax] = useState(a.maxAmount ? String(a.maxAmount) : fmt.num(deal.amount) ? String(deal.amount) : "");
  const [terms, setTerms] = useState(() => (a.terms && a.terms.length ? a.terms.map(t => ({ key: rowKey(), days: String(t.days), factor: String(t.factor) }))
    : [{ key: rowKey(), days: fmt.num(deal.term) ? String(fmt.termDays(deal.term, deal.termUnit)) : "", factor: fmt.num(deal.factor) ? String(deal.factor) : "" }]));
  const [daily, setDaily] = useState(a.freqs ? a.freqs.includes("Daily") : true);
  const [weekly, setWeekly] = useState(a.freqs ? a.freqs.includes("Weekly") : true);
  const [packages, setPackages] = useState(() => (offer && offer.packages ? offer.packages.map(p => ({ key: rowKey(), label: p.label || "", lender: p.lender || "", amount: String(p.amount), factor: String(p.factor), term: String(p.termCount || p.term), termUnit: p.termUnit || "days", freq: p.freq }))
    : withTerms.length ? fromLenders() : [blankPackage(0)]));
  const [requireDocs, setRequireDocs] = useState(offer ? !!offer.requireDocs : missing);
  const [expiresAt, setExpiresAt] = useState(offer ? offer.expiresAt || "" : plusDays(14));
  const [message, setMessage] = useState(offer ? offer.message || "" : "");
  const [askStatements, setAskStatements] = useState(offer ? !!offer.askStatements : false);
  // The product the client sees this offer as (in the portal). Defaults to the deal's first product.
  const [product, setProduct] = useState((offer && offer.product) || (Array.isArray(deal.products) && deal.products[0]) || "mca");
  // The rep's own number, from their profile. Left blank, the client is shown the office's
  // (looked up when the page opens, so a change to the office number reaches links already out).
  const officePhone = (office && office.phone) || "";
  const [phone, setPhone] = useState(offer ? offer.phone || "" : (user && user.phone) || remembered("ef.offerPhone"));
  const save = useSave(onSaved);

  const editRow = (list, setList, key, field, value) => setList(list.map(r => (r.key === key ? { ...r, [field]: value } : r)));
  const n = (v) => { const x = fmt.parseMoney(v); return x === "" || Number.isNaN(x) ? 0 : x; };

  // What the client will see for the top of the range, as the rep types.
  const preview = (() => {
    if (mode !== "adjust") return null;
    const top = n(maxAmount), t = terms.map(r => ({ days: n(r.days), factor: n(r.factor) })).filter(r => r.days >= 5 && r.factor >= 1);
    if (!top || !t.length) return null;
    return t.map(r => { const d = offerTerms(top, r.factor, r.days, "Daily"), w = offerTerms(top, r.factor, r.days, "Weekly");
      return `${r.days} days at ${r.factor}: payback ${fmt.money(d.payback)}${daily ? ` · ${fmt.money(d.payment)} daily` : ""}${weekly ? ` · ${fmt.money(w.payment)} weekly` : ""}`; });
  })();

  const submit = () => {
    const ownPhone = phone.trim() === officePhone ? "" : phone.trim();
    const body = { mode, product, requireDocs: missing ? requireDocs : false, askStatements, expiresAt, message: message.trim(), phone: ownPhone };
    if (mode === "adjust") {
      if (!n(maxAmount)) return save.setError("Enter the highest amount the client can take.");
      if (minAmount.trim() && n(minAmount) > n(maxAmount)) return save.setError("The lowest amount can't be more than the highest.");
      const list = terms.filter(r => r.days.trim() || r.factor.trim());
      if (!list.length) return save.setError("Add at least one term with its factor.");
      if (list.some(r => !n(r.days) || !n(r.factor))) return save.setError("Each term needs both days and a factor.");
      if (!daily && !weekly) return save.setError("Allow daily payments, weekly payments, or both.");
      body.adjust = { minAmount: minAmount.trim() ? n(minAmount) : n(maxAmount), maxAmount: n(maxAmount), terms: list.map(r => ({ days: n(r.days), factor: n(r.factor) })), freqs: [daily ? "Daily" : null, weekly ? "Weekly" : null].filter(Boolean) };
    } else {
      const list = packages.filter(p => p.amount.trim() || p.factor.trim() || p.term.trim());
      if (!list.length) return save.setError("Add at least one package.");
      if (list.some(p => !n(p.amount) || !n(p.factor) || !n(p.term))) return save.setError("Each package needs an amount, a factor and a term.");
      body.packages = list.map(p => ({ label: p.label.trim(), lender: p.lender.trim(), amount: n(p.amount), factor: n(p.factor), term: n(p.term), termUnit: p.termUnit || "days", freq: p.freq }));
    }
    if (ownPhone && !(user && user.phone)) remember("ef.offerPhone", ownPhone);
    save.run(() => (editing ? API.updateOffer(offer.slug, body) : API.createOffer(deal.id, body)));
  };

  return (
    <Sheet eyebrow={deal.biz} title={editing ? "Edit offer link" : "New offer link"} onClose={onClose} onSubmit={submit}
           submitLabel={editing ? "Save offer link" : "Create offer link"} busy={save.busy} error={save.error}>
      <div className="form-section">
        <span className="label">What the client can do</span>
        <Segmented value={mode} onChange={setMode} label="Kind of offer" options={[{ id: "adjust", label: "Adjust within a range" }, { id: "packages", label: "Pick from fixed offers" }]} />
        <div className="hint" style={{ marginTop: 8 }}>
          {mode === "adjust" ? "The client slides the amount between your lowest and highest, and picks a term and a payment schedule. Payback and payment follow."
            : "The client picks one of up to six complete offers, exactly as you enter them."}
        </div>
        <div className="form-grid" style={{ marginTop: 14 }}>
          <Field label="Product" hint="What the client sees this offer as in their portal."><Select value={product} onChange={setProduct} options={PRODUCT_OPTIONS} /></Field>
        </div>
      </div>

      {mode === "adjust" ? (
        <div className="form-section">
          <span className="label">Range</span>
          <div className="form-grid">
            <Field label="Lowest amount" hint="Leave blank for a fixed amount."><TextInput value={minAmount} onChange={setMin} inputMode="decimal" placeholder="$" /></Field>
            <Field label="Highest amount"><TextInput value={maxAmount} onChange={setMax} inputMode="decimal" placeholder="$" /></Field>
          </div>
          <span className="label" style={{ display: "block", margin: "16px 0 8px" }}>Term choices</span>
          {terms.map((r, i) => (
            <div className="offer-line" key={r.key}>
              <Field label={i ? `Term ${i + 1} (days)` : "Term (days)"}><TextInput value={r.days} onChange={x => editRow(terms, setTerms, r.key, "days", x)} inputMode="numeric" placeholder="120" /></Field>
              <Field label="Factor"><TextInput value={r.factor} onChange={x => editRow(terms, setTerms, r.key, "factor", x)} inputMode="decimal" placeholder="1.32" /></Field>
              {terms.length > 1 ? <button type="button" className="icon-btn" onClick={() => setTerms(terms.filter(x => x.key !== r.key))} aria-label={`Remove term ${i + 1}`}><Icon name="trash" size={15} /></button> : <span />}
            </div>
          ))}
          {terms.length < 6 ? <button type="button" className="btn btn-sm" onClick={() => setTerms(terms.concat([{ key: rowKey(), days: "", factor: "" }]))}><Icon name="plus" size={14} />Add a term</button> : null}
          <span className="label" style={{ display: "block", margin: "16px 0 8px" }}>Payment schedules the client can choose</span>
          <div className="check-row">
            <label><input type="checkbox" checked={daily} onChange={e => setDaily(e.target.checked)} /> Daily</label>
            <label><input type="checkbox" checked={weekly} onChange={e => setWeekly(e.target.checked)} /> Weekly</label>
          </div>
          {preview ? <div className="detail tnum"><b>At {fmt.money(n(maxAmount))}</b>{preview.map((line, i) => <div key={i}>{line}</div>)}</div> : null}
        </div>
      ) : (
        <div className="form-section">
          <div className="section-bar" style={{ marginBottom: 12 }}>
            <span className="label">Offers</span>
            {withTerms.length ? <button type="button" className="link-btn" onClick={() => setPackages(fromLenders())}>Fill from lender offers</button> : null}
          </div>
          {packages.map((p, i) => {
            const t = n(p.amount) && n(p.factor) && n(p.term) ? offerTerms(n(p.amount), n(p.factor), fmt.termDays(n(p.term), p.termUnit), p.freq) : null;
            return (
              <div className="offer-pkg" key={p.key}>
                <div className="offer-pkg-head">
                  <b>Offer {i + 1}</b>
                  {packages.length > 1 ? <button type="button" className="icon-btn" onClick={() => setPackages(packages.filter(x => x.key !== p.key))} aria-label={`Remove offer ${i + 1}`}><Icon name="trash" size={15} /></button> : null}
                </div>
                <div className="form-grid">
                  <Field label="Name the client sees"><TextInput value={p.label} onChange={x => editRow(packages, setPackages, p.key, "label", x)} placeholder={`Option ${"ABCDEF"[i]}`} /></Field>
                  <Field label="Lender" hint="For your records. The client never sees it."><TextInput value={p.lender} onChange={x => editRow(packages, setPackages, p.key, "lender", x)} /></Field>
                  <Field label="Amount"><TextInput value={p.amount} onChange={x => editRow(packages, setPackages, p.key, "amount", x)} inputMode="decimal" placeholder="$" /></Field>
                  <Field label="Factor"><TextInput value={p.factor} onChange={x => editRow(packages, setPackages, p.key, "factor", x)} inputMode="decimal" placeholder="1.32" /></Field>
                  <Field label="Term"><TermInput months value={p.term} unit={p.termUnit} onChange={x => editRow(packages, setPackages, p.key, "term", x)} onUnit={x => editRow(packages, setPackages, p.key, "termUnit", x)} /></Field>
                  <Field label="Payments"><Select value={p.freq} onChange={x => editRow(packages, setPackages, p.key, "freq", x)} options={[{ value: "Daily", label: "Daily" }, { value: "Weekly", label: "Weekly" }]} /></Field>
                </div>
                {t ? <div className="hint tnum" style={{ marginTop: 8 }}>Payback {fmt.money(t.payback)} · {fmt.money(t.payment)} {t.freq.toLowerCase()}</div> : null}
              </div>
            );
          })}
          {packages.length < 6 ? <button type="button" className="btn btn-sm" onClick={() => setPackages(packages.concat([blankPackage(packages.length)]))}><Icon name="plus" size={14} />Add an offer</button> : null}
        </div>
      )}

      <div className="form-section">
        <span className="label">Documents</span>
        {missing ? (
          <div className="switch-row" style={{ padding: 0, border: 0 }}>
            <div>
              <div className="t">Require driver license and voided check to submit</div>
              <div className="d">Not on file yet: {[have.dlFront ? "" : "driver license", have.check ? "" : "voided check"].filter(Boolean).join(" and ")}. {requireDocs ? "The client must upload them before they can submit." : "The client can accept without them, or choose to upload them."}</div>
            </div>
            <Switch checked={requireDocs} onChange={setRequireDocs} label="Require driver license and voided check to submit" />
          </div>
        ) : <div className="dim" style={{ fontSize: 13.5 }}>Driver license and voided check are already on file, so the client only has to accept.</div>}
        <div className="switch-row" style={{ padding: "14px 0 0", border: 0 }}>
          <div>
            <div className="t">Ask for bank statements</div>
            <div className="d">Adds a place on the client's page to send statement PDFs. Never needed to accept.{office && office.statements ? " Each one is read as it arrives and the breakdown appears on the deal." : ""}</div>
          </div>
          <Switch checked={askStatements} onChange={setAskStatements} label="Ask for bank statements" />
        </div>
      </div>

      <div className="form-section">
        <div className="form-grid">
          <Field label="Good until"><TextInput value={expiresAt} onChange={setExpiresAt} type="date" /></Field>
          <Field label="Your phone number" hint={user && user.phone && phone.trim() === user.phone ? "Shown on the offer for questions. From your profile." : officePhone ? `Shown on the offer for questions. Leave blank to show the office number, ${officePhone}.` : "Shown on the offer for questions."}><TextInput value={phone} onChange={setPhone} inputMode="tel" autoComplete="tel" placeholder={officePhone} /></Field>
          <Field label="Note to the client" full hint="Optional. Shown at the top of the offer.">
            <textarea className="textarea" value={message} onChange={e => setMessage(e.target.value)} maxLength={600} />
          </Field>
        </div>
      </div>
    </Sheet>
  );
}

// ── Sending a link to the client ────────────────────────────────────────────
// Two ways, side by side: from the office (its Twilio number or its email
// account, logged on the deal), or from the rep's own phone or mail app with
// the message already written.
function SendOfferSheet({ o, deal, office, onClose, onSent }) {
  const first = String(deal.contact || "").split(" ")[0];
  const [channel, setChannel] = useState(deal.phone || !deal.email ? "sms" : "email");
  const [toPhone, setToPhone] = useState(deal.phone || "");
  const [toEmail, setToEmail] = useState(deal.email || "");
  const [subject, setSubject] = useState("Your funding offer from Bylt Capital");
  const [message, setMessage] = useState(`${first ? "Hi " + first + ", h" : "H"}ere is your funding offer from Bylt Capital: ${o.url}`);
  const [image, setImage] = useState(true);
  const save = useSave(onSent);
  const sms = channel === "sms";
  const fromOffice = sms ? !!(office && office.sms) : !!(office && office.email);
  const body = message.includes(o.url) ? message : `${message.trim()}\n${o.url}`;
  const ownLink = sms
    ? `sms:${toPhone.replace(/[^\d+]/g, "")}?&body=${encodeURIComponent(body)}`
    : `mailto:${toEmail.replace(/[^\w.+@-]/g, "")}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
  const submit = () => {
    if (sms && toPhone.replace(/\D/g, "").length < 10) return save.setError("Enter the client's mobile number.");
    if (!sms && !/^\S+@\S+\.\S+$/.test(toEmail.trim())) return save.setError("Enter the client's email address.");
    save.run(() => API.sendOffer(o.slug, sms ? { channel: "sms", to: toPhone.trim(), message: body, image } : { channel: "email", to: toEmail.trim(), subject: subject.trim(), message: body }));
  };
  return (
    <Sheet eyebrow={deal.biz} title="Send offer link" onClose={onClose} onSubmit={fromOffice ? submit : null}
           submitLabel={sms ? "Text it from the office" : "Email it from the office"} busyLabel="Sending…" busy={save.busy} error={save.error}>
      <div className="form-section">
        <Segmented value={channel} onChange={(x) => { setChannel(x); save.setError(""); }} label="How to send it" options={[{ id: "sms", label: "Text message" }, { id: "email", label: "Email" }]} />
      </div>
      <div className="form-section">
        <div className="form-grid">
          {sms ? <Field label="Client's mobile" full><TextInput value={toPhone} onChange={setToPhone} type="tel" inputMode="tel" /></Field>
            : <Field label="Client's email" full><TextInput value={toEmail} onChange={setToEmail} type="email" /></Field>}
          {sms ? null : <Field label="Subject" full><TextInput value={subject} onChange={setSubject} maxLength={150} /></Field>}
          <Field label="Message" full hint="The link is always included.">
            <textarea className="textarea" style={{ minHeight: 120 }} value={message} onChange={e => setMessage(e.target.value)} maxLength={1200} />
          </Field>
        </div>
        {sms && fromOffice ? (
          <label className="check" style={{ marginTop: 12 }}>
            <input type="checkbox" checked={image} onChange={e => setImage(e.target.checked)} />
            Attach the picture, so it shows even if the client's phone doesn't load link previews
          </label>
        ) : null}
      </div>
      <div className="form-section">
        <span className="label">{fromOffice ? "Or send it yourself" : "Send it yourself"}</span>
        <p className="hint" style={{ margin: "6px 0 10px" }}>
          {fromOffice ? (sms ? "Opens your own messages app with this text written, so it comes from your number." : "Opens your own mail app with this message written.")
            : sms ? "Texting from the office number isn't set up yet. This opens your own messages app with the text written, so it comes from your number."
            : "Email from the office address isn't set up yet. This opens your own mail app with the message written."}
        </p>
        <a className={`btn${fromOffice ? "" : " btn-primary"}`} href={ownLink} onClick={() => setTimeout(onClose, 300)}>
          <Icon name={sms ? "phone" : "mail"} size={15} />{sms ? "Open in my messages" : "Open in my mail"}
        </a>
      </div>
    </Sheet>
  );
}

// ── The card on a deal ──────────────────────────────────────────────────────
function OfferLinkRow({ o, deal, canWrite, isAdmin, office, onEdit, onChanged, onApply, toast, confirm }) {
  const st = OFFER_STATE[o.state] || OFFER_STATE.open;
  const [busy, setBusy] = useState(false);
  const [sending, setSending] = useState(false);
  const what = o.mode === "adjust"
    ? (o.adjust.minAmount < o.adjust.maxAmount ? `${fmt.money(o.adjust.minAmount)} to ${fmt.money(o.adjust.maxAmount)}` : fmt.money(o.adjust.maxAmount))
      + (o.bulk && o.adjust.terms.every(t => t.days % 5 === 0) ? ` · ${o.adjust.terms.map(t => t.days / 5).join(" / ")} weeks · factor ${o.adjust.terms.map(t => Number(t.factor).toFixed(2)).join(" / ")}` : ` · ${o.adjust.terms.map(t => t.days).join(" / ")} days`) + ` · ${o.adjust.freqs.join(" or ").toLowerCase()}`
    : `${o.packages.length} fixed ${o.packages.length === 1 ? "offer" : "offers"}: ${o.packages.map(p => fmt.money(p.amount)).join(", ")}`;
  const lower = (s) => s.replace(/^T/, "t").replace(/^Y/, "y");
  const copy = async () => {
    try { await navigator.clipboard.writeText(o.url); toast("Link copied"); }
    catch (e) { toast("Couldn't copy automatically. Select the link below and copy it.", true); }
  };
  const setStatus = async (status) => {
    setBusy(true);
    try { await API.updateOffer(o.slug, { status }); await onChanged(); toast(status === "closed" ? "Offer link closed" : "Offer link reopened"); }
    catch (err) { toast(err.message, true); }
    setBusy(false);
  };
  const remove = () => confirm({
    title: "Delete this offer link?", body: o.accepted ? "The client's acceptance and its confirmation number will be removed with it." : "The link will stop working for the client.", confirmLabel: "Delete link",
    onConfirm: async () => { await API.deleteOffer(o.slug); await onChanged(); toast("Offer link deleted"); },
  });
  const ups = o.uploads || {};
  const sent = [ups.dlFront ? "driver license" : "", ups.dlBack ? "license back" : "", ups.check ? "voided check" : "", ups.statements ? `${ups.statements} bank ${ups.statements === 1 ? "statement" : "statements"}` : ""].filter(Boolean);
  const sends = Array.isArray(o.sent) ? o.sent : [];
  const lastSend = sends[sends.length - 1];

  return (
    <div className="row offer-row">
      <span className={`hicon ${o.state === "accepted" ? "t-good" : o.state === "open" ? "" : "t-quiet"}`}><Icon name={o.state === "accepted" ? "check" : "link"} size={15} /></span>
      <div className="grow">
        <div className="title">{o.mode === "adjust" ? "Adjustable offer" : "Fixed offers"}<Pill status={st.pill} label={st.label} />{o.requireDocs ? <span className="chip">Documents required</span> : null}{o.askStatements ? <span className="chip">Asks for statements</span> : null}</div>
        <div className="line tnum">{what}</div>
        <div className="when">
          Created by {o.createdBy || "staff"} {lower(fmt.when(o.createdAt))}{o.expiresAt ? ` · good until ${fmt.day(o.expiresAt)}` : ""}
          {o.views ? null : " · not opened yet"}
        </div>
        {o.views && o.lastViewedAt ? (
          <div className="seen-line">
            <SeenChip at={o.lastViewedAt} via={o.lastVia || "text"} views={o.views} long />
            <span className="when">{lower(fmt.when(o.lastViewedAt))} · opened {o.views} {o.views === 1 ? "time" : "times"}{o.emailViews ? ` (${o.emailViews} from the email, ${o.views - o.emailViews} from the text)` : ""}</span>
          </div>
        ) : null}
        {o.accepted ? (
          <div className="offer-accepted">
            <b>Accepted by {o.accepted.name}</b> {lower(fmt.when(o.accepted.at))}
            <div className="tnum">{o.selection ? termsLine(o.selection) : ""}{o.selection ? ` · payback ${fmt.money(o.selection.payback)}` : ""}</div>
            <div className="when">
              Confirmation {o.confirmationNo}{o.accepted.phone ? <> · <a href={`tel:${String(o.accepted.phone).replace(/[^\d+]/g, "")}`}>{o.accepted.phone}</a></> : null}{o.accepted.email ? <> · <a href={`mailto:${encodeURIComponent(o.accepted.email).replace(/%40/g, "@")}`}>{o.accepted.email}</a></> : null}
            </div>
            {canWrite && o.selection && deal.status !== "funded" && deal._kind !== "lead" ? <button className="btn btn-sm" style={{ marginTop: 8 }} onClick={() => onApply(o)}>Use these terms on the deal</button> : null}
          </div>
        ) : o.looking ? <div className="when tnum">Client is looking at {termsLine(o.looking)} ({lower(fmt.when(o.looking.at))})</div> : null}
        {lastSend ? <div className="when">{lastSend.channel === "sms" ? "Texted" : "Emailed"} to {lastSend.to} by {lastSend.by} {lower(fmt.when(lastSend.at))}{sends.length > 1 ? ` · sent ${sends.length} times` : ""}</div> : null}
        {sent.length ? <div className="when">Client uploaded: {sent.join(", ")}</div> : null}
        <div className="offer-url token">{o.url}</div>
        <div className="offer-acts">
          <button className="btn btn-sm btn-primary" onClick={copy}><Icon name="copy" size={14} />Copy link</button>
          {canWrite && o.state === "open" ? <button className="btn btn-sm" onClick={() => setSending(true)}><Icon name="mail" size={14} />Send</button> : null}
          <a className="btn btn-sm" href={`${o.url}?preview=1`} target="_blank" rel="noopener"><Icon name="eye" size={14} />Preview</a>
          {canWrite && !o.accepted ? <button className="btn btn-sm" onClick={() => onEdit(o)} disabled={busy}><Icon name="edit" size={14} />Edit</button> : null}
          {canWrite && o.state !== "closed" && !o.accepted ? <button className="btn btn-sm" onClick={() => setStatus("closed")} disabled={busy}>Close</button> : null}
          {canWrite && o.state === "closed" ? <button className="btn btn-sm" onClick={() => setStatus("open")} disabled={busy}>Reopen</button> : null}
          {canWrite && (!o.accepted || isAdmin) ? <button className="icon-btn" onClick={remove} aria-label="Delete offer link"><Icon name="trash" size={15} /></button> : null}
        </div>
      </div>
      {sending ? <Portal><SendOfferSheet o={o} deal={deal} office={office} onClose={() => setSending(false)} onSent={async () => { setSending(false); await onChanged(); toast("Offer link sent"); }} /></Portal> : null}
    </div>
  );
}

function OfferLinksCard({ deal, offers, canWrite, isAdmin, office, onNew, onEdit, onChanged, onApply, toast, confirm }) {
  if (!offers.length && !canWrite) return null;
  return (
    <div className="card card-flush" id="offer-links">
      <div className="card-head">
        <span className="label">Offer links</span>
        {canWrite ? <button className="btn btn-sm" onClick={onNew}><Icon name="plus" size={14} />New offer link</button> : null}
      </div>
      {offers.length ? offers.map(o => <OfferLinkRow key={o.slug} o={o} deal={deal} canWrite={canWrite} isAdmin={isAdmin} office={office} onEdit={onEdit} onChanged={onChanged} onApply={onApply} toast={toast} confirm={confirm} />)
        : <div className="card-body dim" style={{ fontSize: 13.5 }}>Send the client a link to their offer. They can adjust it or pick a package, accept it, and upload their driver license, voided check and bank statements from their phone.</div>}
    </div>
  );
}

Object.assign(window, { OfferSheet, OfferLinksCard, offerTerms });
