// Bylt Capital CRM — bank statements: the breakdown worked out from each
// statement PDF on a deal, and the roll-up across them.

const ST_PENDING = ["queued", "working"];
const dayRange = (a, b) => (a && b ? `${fmt.day(a)} to ${fmt.day(b)}, ${String(b).slice(0, 4)}` : "");
// What reading cost, in dollars: to the cent, or "under 1¢".
const readCost = (usd) => (!usd ? "" : usd < 0.01 ? "under 1¢" : "$" + (Math.round(usd * 100) / 100).toFixed(2));
const money0 = (n) => (n < 0 ? "−" : "") + "$" + Math.round(Math.abs(fmt.num(n))).toLocaleString("en-US");
// A statement file on a deal or lead: filed as one (role), or stored under statement/ (older files).
const isStatementFile = (f) => !!f && (f.role === "statement" || /^(deals|leads)\/[^/]+\/statement\//.test(String(f.objectKey || "")));

function statementStatus(s) {
  if (!s.status) return { pill: "neutral", label: "Not read" };
  if (s.status === "queued") return { pill: "pending", label: "Waiting to be read" };
  if (s.status === "working") return { pill: "new", label: "Reading…" };
  if (s.status === "done") return { pill: "funded", label: "Ties out" };
  if (s.status === "unreadable") return { pill: "declined", label: "Can't be read" };
  if (s.status === "failed") return { pill: "declined", label: "Reading failed" };
  const check = (s.summary && s.summary.check) || {}, h = s.header || {};
  return { pill: "review", label: check.status === "mismatch" ? "Doesn't tie out" : check.status === "totals" ? "Totals don't match" : check.status === "unverified" ? "Couldn't be checked" : !(h.periodStart && h.periodEnd) ? "Period not found" : "Partly read" };
}

// Loads a deal's statements, and keeps looking while any are still being read.
function useStatements(dealId, docCount) {
  const [data, setData] = useState(null);
  const showing = useRef(dealId); showing.current = dealId;
  const load = useCallback(async () => {
    try { const d = await API.statements(dealId); if (showing.current === dealId) setData(d); return d; }
    catch (e) { return null; }
  }, [dealId]);
  const loadedFor = useRef(null);
  useEffect(() => { if (loadedFor.current !== dealId) { loadedFor.current = dealId; setData(null); } load(); }, [dealId, docCount]);
  // A statement rule added or removed (rules.jsx): the figures are worked out again, so they are loaded again.
  useEffect(() => { window.addEventListener("ef:statements-changed", load); return () => window.removeEventListener("ef:statements-changed", load); }, [load]);
  const pending = !!(data && data.statements.some(s => ST_PENDING.includes(s.status)));
  useEffect(() => {
    if (!pending) return;
    const t = setInterval(load, 4000);
    return () => clearInterval(t);
  }, [pending, load]);
  return { data, reload: load, pending };
}

// ── Deposits and positions by month, from this lead's or deal's own statements ──
// The same chart as the client file (clients.jsx DepositsChart), worked out here from the statements already
// loaded: the month a statement counts in is the one its middle day falls in (as on the server), the same
// statement uploaded twice counts once, and a statement covering much more or less than a month is brought to a
// month's worth. Positions are the repeating payments the statement reader found; the timeline marks one that
// starts or stops within the months covered.
// A payee as the timeline shows it: the bank's codes and numbers taken off ("MELIO TX_8693320" → "Melio"), at most 20 letters.
function shortPayee(payee) {
  const words = String(payee || "").split(/\s+/).filter(w => w && !/\d/.test(w) && !/^(ach|debit|pmt|pymt|ppd|ccd|web|des|id|co|entry|orig|name|indn|llc|inc)$/i.test(w.replace(/[^a-z]/gi, "")));
  const s = (words.length ? words : String(payee || "").split(/\s+/)).join(" ").replace(/\b([A-Z])([A-Z]+)\b/g, (m, a, b) => a + b.toLowerCase());
  return s.length > 20 ? s.slice(0, 19).trim() + "…" : s;
}
function statementMonths(list) {
  const day = (s) => Date.parse(s + "T00:00:00Z");
  const midMonth = (h) => (h && h.periodStart && h.periodEnd ? new Date((day(h.periodStart) + day(h.periodEnd)) / 2).toISOString().slice(0, 7) : null);
  const good = (list || []).filter(s => s && s.summary && (s.status === "done" || s.status === "review") && midMonth(s.header))
    .sort((a, b) => (a.status === "done" ? 0 : 1) - (b.status === "done" ? 0 : 1));
  const seen = new Set(), byMonth = new Map(), pos = new Map();
  let from = null, to = null;
  for (const s of good) {
    const h = s.header;
    const key = h.accountLast4 ? `${h.accountLast4}|${h.periodStart}|${h.periodEnd}` : null;
    if (key && seen.has(key)) continue;
    if (key) seen.add(key);
    if (!from || h.periodStart < from) from = h.periodStart;
    if (!to || h.periodEnd > to) to = h.periodEnd;
    const d = s.summary.periodDays, k = d && (d < 25 || d > 35) ? 30.4 / d : 1;
    const m = midMonth(h);
    const cur = byMonth.get(m) || { month: m, deposits: 0, keys: new Set(), source: "statement" };
    cur.deposits += (s.summary.deposits.revenue || 0) * k;
    for (const p of s.summary.positions || []) {
      if (!p.platform) cur.keys.add(`${p.key}|${p.amount}|${p.cadence}`);   // platform financing isn't counted in the bars
      const name = p.lender || shortPayee(p.payee), id = `${p.lender || String(p.payee || "").slice(0, 22)}|${p.amount}`;
      const g = pos.get(id) || { funder: name, id, first: p.firstDate, last: p.lastDate, platform: !!p.platform };
      if (p.firstDate && p.firstDate < g.first) g.first = p.firstDate;
      if (p.lastDate && p.lastDate > g.last) g.last = p.lastDate;
      pos.set(id, g);
    }
    byMonth.set(m, cur);
  }
  const months = [...byMonth.values()].sort((a, b) => a.month.localeCompare(b.month))
    .map(m => ({ month: m.month, deposits: Math.round(m.deposits * 100) / 100, positions: m.keys.size, source: "statement" }));
  const events = [];
  const near = (a, b, days) => Math.abs(day(a) - day(b)) <= days * 864e5;
  for (const g of pos.values()) {
    if (g.first && from && !near(g.first, from, 7)) events.push({ date: g.first, kind: "start", funder: g.funder, id: g.id, platform: g.platform });
    if (g.last && to && !near(g.last, to, 10)) events.push({ date: g.last, kind: "end", funder: g.funder, id: g.id, platform: g.platform });
  }
  return { months, events };
}
function StatementChart({ statements }) {
  const [range, setRange] = useState(6);
  const data = statements && statements.data;
  const { months, events: payEvents } = useMemo(() => statementMonths(data ? data.statements : []), [data]);
  if (!data || !months.length || !window.DepositsChart) return null;
  const s = data.summary || {};
  // A funding on the statements is marked on the day it landed; the "payments start" mark of the payment it started is then left off.
  const started = new Set((s.positions || []).concat(s.platformPositions || []).filter(p => p.funding).map(p => `${p.lender || String(p.payee || "").slice(0, 22)}|${p.amount}`));
  const events = payEvents.filter(e => !(e.kind === "start" && started.has(e.id)))
    .concat((s.fundings || []).map(f => { const n = f.lender || fundingName(f); return { date: f.date, kind: "funded", platform: !!f.platform, funder: `Funded ${fmt.moneyShort(f.amount)} · ${n.length > 20 ? n.slice(0, 19).trim() + "…" : n}` }; }));
  const avg = months.length ? Math.round(months.reduce((t, m) => t + m.deposits, 0) / months.length) : 0;
  const monthly = s.positionsMonthly || 0;
  // The share is the same figure as the remit label beside the business name (positions over average monthly sales).
  const totals = { avgDeposits: avg, monthly, paymentShare: remitShare(s), positions: (s.positions || []).length };
  return <window.DepositsChart months={months} events={events} totals={totals} range={range} setRange={setRange} canWrite={false} fromStatements />;
}

// ── Monthly remit: what the current positions take out of a month's sales ──
// Positions' monthly payments over average monthly sales (the same two figures the chart shows). Null when the
// statements give no sales to measure against.
function remitShare(summary) {
  if (!summary || !(summary.avgMonthlyRevenue > 0)) return null;
  return Math.round(((summary.positionsMonthly || 0) / summary.avgMonthlyRevenue) * 100);
}
// The same with platform financing (PayPal, Square, DoorDash…) counted too: null when there is none, or the same figure.
function remitShareAll(summary) {
  if (!summary || !(summary.avgMonthlyRevenue > 0) || !(summary.platformMonthly > 0)) return null;
  return Math.round(((summary.allMonthly || 0) / summary.avgMonthlyRevenue) * 100);
}
// The band a remit falls in: 20% and under green, to 40% orange, to 50% red, over 50% red and Over-Leveraged.
function remitBand(share) { return share > 50 ? "over" : share > 40 ? "red" : share > 20 ? "orange" : "green"; }
// A label for beside the business name: a sliding scale (green, orange, red) with a marker at the remit, and the figure.
// `withPlatforms`: the figure with platform financing counted too, shown beside it when there is any.
function RemitBadge({ share, basis, compact, withPlatforms }) {
  if (share === null || share === undefined || !isFinite(share)) return null;
  const band = remitBand(share);
  const alt = withPlatforms !== null && withPlatforms !== undefined && isFinite(withPlatforms) && withPlatforms !== share ? withPlatforms : null;
  const altText = alt === null ? "" : ` With platform financing (PayPal, Square, DoorDash…) counted too: ${alt}%.`;
  // In lists: the colour and the figure only.
  if (compact) return <span className="remit-wrap"><span className={`remit sm remit-${band}`} title={`Current positions take ${share}% of ${basis || "average monthly sales"}.${altText}`}><b className="tnum">{share}%</b>{band === "over" ? <em>Over-Leveraged</em> : null}</span>{alt !== null ? <span className={`remit-alt remit-${remitBand(alt)}`} title={altText.trim()}>{alt}% w/ platforms</span> : null}</span>;
  const at = Math.max(0, Math.min(100, (share / 60) * 100));
  return (
    <span className="remit-wrap">
      <span className={`remit remit-${band}`} title={`Current positions take ${share}% of ${basis || "average monthly sales"}. 20% and under is green, 21–40% orange, 41–50% red, over 50% over-leveraged.${altText}`}>
        <span className="remit-scale" aria-hidden="true"><i style={{ left: at + "%" }} /></span>
        <b className="tnum">{share}%</b><span>remit</span>{band === "over" ? <em>Over-Leveraged</em> : null}
      </span>
      {alt !== null ? <span className={`remit-alt remit-${remitBand(alt)}`} title={altText.trim()}>{alt}% with platforms</span> : null}
    </span>
  );
}

// The repeating lender payments on the latest statements, listed like the client file's Current positions:
// the biggest monthly cost first.
function StatementPositions({ summary }) {
  const list = (summary && summary.positions) || [];
  const rules = useStatementRules();
  const notPosition = (p) => openRuleSheet({ description: p.payee, direction: "debit", amount: p.amount, date: p.lastDate, kind: "not-position" });
  return (
    <div className="card card-flush">
      <div className="card-head"><span className="label">Current positions ({list.length})</span><span className="dim" style={{ fontSize: 12 }}>from the latest statements</span></div>
      {list.length ? list.map((p, i) => (
        <div className="row" key={i}>
          <div className="grow">
            <div className="title">{p.lender || p.payee}{p.ref ? <span className="dim" style={{ fontWeight: 400 }}>deal {p.ref}</span> : null}{p.funding ? <span className="pill plain s-funded">Funded {fmt.day(p.funding.date)}</span> : null}</div>
            <div className="line tnum">{fmt.money(p.amount)} {p.cadence === "weekly" ? "weekly" : "daily"} · {money0(p.monthly)} monthly{p.lender ? <span className="muted"> · {p.payee}</span> : null}{p.was ? <span className="muted"> · was {fmt.money(p.was)}</span> : null}</div>
            {p.funding && p.paidSince ? <div className="line tnum fund-line">Funded {fmt.money(p.funding.amount)} on {fmt.day(p.funding.date)} → {p.paidSince.count} {p.paidSince.count === 1 ? "payment" : "payments"} since {fmt.day(p.paidSince.since)}, {fmt.money(p.paidSince.total)} paid on these statements</div> : null}
            {rules && rules.canEdit ? <div className="when"><button type="button" className="link-btn" onClick={() => notPosition(p)}>Not a lender payment</button></div> : null}
          </div>
          <div className="right"><div className="tnum" style={{ fontWeight: 600, color: "var(--st-declined)" }}>{money0(p.monthly)}</div><div className="when tnum">{p.count}× · last {fmt.day(p.lastDate)}</div></div>
        </div>
      )) : <div className="row dim" style={{ fontSize: 13 }}>No repeating lender payments on the statements.</div>}
      {(summary && summary.platformPositions || []).length ? (
        <>
          <div className="row sub-head"><span className="label">Platform financing ({summary.platformPositions.length})</span><span className="dim" style={{ fontSize: 12 }}>not counted as positions · {money0(summary.platformMonthly)} a month</span></div>
          {summary.platformPositions.map((p, i) => (
            <div className="row platform-row" key={"pf" + i}>
              <div className="grow">
                <div className="title">{p.lender || shortPayee(p.payee)}<span className="pill plain s-neutral">Platform</span>{p.funding ? <span className="pill plain s-funded">Funded {fmt.day(p.funding.date)}</span> : null}</div>
                <div className="line tnum">{fmt.money(p.amount)} {p.cadence === "weekly" ? "weekly" : "daily"} · {money0(p.monthly)} monthly<span className="muted"> · {p.payee}</span></div>
              </div>
              <div className="right"><div className="tnum" style={{ fontWeight: 600 }}>{money0(p.monthly)}</div><div className="when tnum">{p.count}× · last {fmt.day(p.lastDate)}</div></div>
            </div>
          ))}
        </>
      ) : null}
    </div>
  );
}

// Labels from the statements: IN REVERSE (a reverse consolidator is paying the merchant every week) and
// DEBT SETTLEMENT (payments to a debt settlement program: not fundable). `reverse`: the consolidators' names.
// HIGH RISK: gambling or casinos over $500 in a month, or Cash App-type transfers over 10% of a month's deposits.
function BankLabels({ reverse, debtSettlement, highRisk }) {
  const names = Array.isArray(reverse) ? reverse.filter(Boolean) : [];
  if (!names.length && !debtSettlement && !highRisk) return null;
  return (
    <span className="bank-labels">
      {highRisk ? <span className="bank-label risk" title="Gambling or casinos over $500 in a month, or Cash App, Venmo, Zelle and PayPal transfers over 10% of a month's deposits">High risk</span> : null}
      {debtSettlement ? <span className="bank-label ds" title="Payments to a debt settlement program on the statements: not fundable">Debt settlement</span> : null}
      {names.length ? <span className="bank-label rev" title={`Reverse consolidation: ${names.join(", ")} deposits every week`}>In reverse</span> : null}
    </span>
  );
}
// The same, from a rollup of the statements (summary.reverse / summary.debtSettlement).
const bankLabelsOf = (sum) => ({ reverse: ((sum && sum.reverse) || []).map(r => r.name), debtSettlement: !!(sum && (sum.debtSettlement || []).length) });

// ── Before you call: the talking point and the red flags (src/call-notes.js) ──
const RED_LABELS = { cashApp: "Cash App · Venmo · Zelle · PayPal", liquor: "Liquor and bars", casino: "Casinos", gambling: "Gambling and betting" };
const RED_SHORT = { cashApp: "P2P transfer", liquor: "Liquor", casino: "Casino", gambling: "Gambling" };
const acctOf = (x) => [x.bankName, x.accountLast4 ? "····" + x.accountLast4 : ""].filter(Boolean).join(" ");
const dayYr = (d) => (d ? `${fmt.day(d)} ’${String(d).slice(2, 4)}` : "");
function CallNotesCard({ notes, open }) {
  const [all, setAll] = useState(!!open);
  if (!notes || (!notes.accounts || !notes.accounts.length)) return null;
  const tp = notes.talkingPoint, sum = notes.redFlagSummary || {}, flags = notes.redFlags || [];
  const cats = ["gambling", "casino", "cashApp", "liquor"];
  return (
    <div className="card card-flush call-notes">
      <div className="card-head"><span className="label">Before you call</span>{sum.highRisk ? <span className="bank-label risk">High risk</span> : <span className="dim" style={{ fontSize: 12 }}>{notes.accounts.map(acctOf).join(" · ")}</span>}</div>
      <div className="call-tp">
        <div className="call-k">Talking point</div>
        {tp ? (
          <>
            <div className="call-say">“I see <b>{fmt.money(tp.amount)}</b> at <b>{tp.merchant}</b> on {fmt.day(tp.date)}…”</div>
            <div className="when" title={tp.rawDescription}>{dayYr(tp.date)} · {acctOf(tp)} · {tp.rawDescription}</div>
            {(notes.talkingPointBackups || []).length ? <div className="when">Or: {notes.talkingPointBackups.map(b => `${b.merchant} ${fmt.money(b.amount)} on ${fmt.day(b.date)}`).join(" · ")}</div> : null}
          </>
        ) : <div className="muted" style={{ fontSize: 13 }}>No easy purchase (Amazon, Best Buy, Target, a supermarket…) on the statements to open with.</div>}
      </div>
      <div className="call-rf">
        <div className="call-k">Red flags{flags.length ? ` (${flags.length})` : ""}</div>
        {sum.highRisk ? <div className="call-risk">{(sum.reasons || []).join(" · ")}</div> : null}
        {flags.length ? (
          <div className="call-cats">
            {cats.map(c => { const x = sum[c] || { count: 0, total: 0 }; return (
              <div key={c} className={`call-cat${x.count ? " on" : ""}`}>
                <div className="t">{RED_LABELS[c]}</div>
                <div className="v tnum">{x.count ? `${x.count} · ${money0(x.total)}` : "None"}</div>
                {x.largest ? <div className="s tnum">Largest {fmt.money(x.largest.amount)} · {x.largest.merchant} · {fmt.day(x.largest.date)}</div> : null}
              </div>
            ); })}
          </div>
        ) : <div className="muted" style={{ fontSize: 13 }}>None found on the statements.</div>}
        {flags.length ? <button type="button" className="link-btn" onClick={() => setAll(!all)} aria-expanded={all}>{all ? "Hide the transactions" : `Show all ${flags.length}`}</button> : null}
        {all ? (
          <div className="txn-wrap" style={{ marginTop: 8 }}>
            <table className="txn tnum">
              <thead><tr><th>Date</th><th>Kind</th><th>Merchant</th><th className="num">In</th><th className="num">Out</th><th>Account</th><th>Bank line</th></tr></thead>
              <tbody>{flags.map((f, i) => <tr key={i}><td>{dayYr(f.date)}</td><td>{RED_SHORT[f.category] || f.category}</td><td>{f.merchant}</td><td className="num">{f.direction === "credit" ? fmt.money(f.amount) : ""}</td><td className="num">{f.direction === "credit" ? "" : fmt.money(f.amount)}</td><td>{acctOf(f)}</td><td className="desc">{f.rawDescription}</td></tr>)}</tbody>
            </table>
          </div>
        ) : null}
      </div>
    </div>
  );
}

// Layout B: a compact panel in the right-hand column of a lead's or deal's file (on every tab), and the same as a
// strip under the action buttons where that column is folded away (narrower screens and phones). "See all" opens
// the full card (every transaction) in a sheet.
const RED_SHORT_PANEL = { gambling: "Gambling", casino: "Casinos", cashApp: "Cash App · Venmo · Zelle", liquor: "Liquor and bars" };
function CallNotesBody({ notes, onAll }) {
  const tp = notes.talkingPoint, sum = notes.redFlagSummary || {}, n = notes.redFlagsTotal || (notes.redFlags || []).length;
  return (
    <div className="cn-body">
      <div>
        <div className="cn-k">Open with</div>
        {tp ? <><div className="cn-say tnum">{fmt.money(tp.amount)} at {tp.merchant}, {fmt.day(tp.date)}</div>
          <div className="when" title={tp.rawDescription}>{acctOf(tp)} · {tp.rawDescription}</div>
          {(notes.talkingPointBackups || []).length ? <div className="when">Or: {notes.talkingPointBackups.map(b => `${b.merchant} ${fmt.money(b.amount)}`).join(" · ")}</div> : null}</>
          : <div className="muted" style={{ fontSize: 12.5 }}>No easy purchase on the statements to open with.</div>}
      </div>
      <div>
        <div className="cn-k">Red flags{n ? ` (${n})` : ""}</div>
        {sum.highRisk ? <div className="cn-why">{(sum.reasons || []).join(" · ")}</div> : null}
        {n ? ["gambling", "casino", "cashApp", "liquor"].map(c => { const x = sum[c] || { count: 0 }; return (
          <div key={c} className={`cn-row${x.count ? " on" : ""}`}><span>{RED_SHORT_PANEL[c]}</span><b className="tnum">{x.count ? `${x.count} · ${money0(x.total)}` : "None"}</b></div>
        ); }) : <div className="muted" style={{ fontSize: 12.5 }}>None found on the statements.</div>}
        {n ? <button type="button" className="link-btn" onClick={onAll}>See all {n} {n === 1 ? "transaction" : "transactions"}</button> : null}
      </div>
    </div>
  );
}
function CallNotesSheet({ notes, onClose }) {
  return <Portal><Sheet eyebrow="Before you call" title="Talking point and red flags" onClose={onClose}><div className="form-section"><CallNotesCard notes={notes} open /></div></Sheet></Portal>;
}
function CallNotesPanel({ notes }) {
  const [sheet, setSheet] = useState(false);
  if (!notes || !(notes.accounts || []).length) return null;
  const risk = notes.redFlagSummary && notes.redFlagSummary.highRisk;
  return (
    <div>
      <span className="label">Before you call</span>
      <div className={`cn-panel${risk ? " risk" : ""}`}>
        {risk ? <div className="cn-head"><span className="bank-label risk">High risk</span></div> : null}
        <CallNotesBody notes={notes} onAll={() => setSheet(true)} />
      </div>
      {sheet ? <CallNotesSheet notes={notes} onClose={() => setSheet(false)} /> : null}
    </div>
  );
}
function CallNotesStrip({ notes }) {
  const [open, setOpen] = useState(false);
  const [sheet, setSheet] = useState(false);
  if (!notes || !(notes.accounts || []).length) return null;
  const tp = notes.talkingPoint, sum = notes.redFlagSummary || {}, n = notes.redFlagsTotal || (notes.redFlags || []).length;
  return (
    <div className={`cn-strip${sum.highRisk ? " risk" : ""}`}>
      <button type="button" className="cn-strip-head" onClick={() => setOpen(!open)} aria-expanded={open}>
        <span className="cn-k" style={{ margin: 0 }}>Before you call</span>
        <span className="cn-strip-sum tnum">{tp ? `${fmt.money(tp.amount)} at ${tp.merchant}` : "No opener"} · {n ? `${n} red ${n === 1 ? "flag" : "flags"}` : "no red flags"}</span>
        {sum.highRisk ? <span className="bank-label risk">High risk</span> : null}
        <Icon name="chev" size={13} />
      </button>
      {open ? <CallNotesBody notes={notes} onAll={() => setSheet(true)} /> : null}
      {sheet ? <CallNotesSheet notes={notes} onClose={() => setSheet(false)} /> : null}
    </div>
  );
}

// The reverse consolidation and debt settlement found on the statements, above the positions.
function StatementWarnings({ summary }) {
  const rev = (summary && summary.reverse) || [], ds = (summary && summary.debtSettlement) || [];
  if (!rev.length && !ds.length) return null;
  return (
    <div className="card card-flush">
      {ds.length ? (
        <>
          <div className="card-head"><span className="label">Debt settlement</span><span className="bank-label ds">Not fundable</span></div>
          {ds.map((d, i) => (
            <div className="row" key={"ds" + i}>
              <div className="grow"><div className="title">{d.payee}</div><div className="line tnum">{d.count} {d.count === 1 ? "payment" : "payments"}, last {fmt.money(d.amount)} on {fmt.day(d.last)}</div></div>
              <div className="right"><div className="tnum" style={{ fontWeight: 600 }}>{fmt.money(d.total)}</div><div className="when">on these statements</div></div>
            </div>
          ))}
        </>
      ) : null}
      {rev.length ? (
        <>
          <div className="card-head"><span className="label">Reverse consolidation</span><span className="bank-label rev">In reverse</span></div>
          {rev.map((r, i) => (
            <div className="row" key={"rv" + i}>
              <div className="grow">
                <div className="title">{r.name}</div>
                <div className="line tnum">Deposits about {fmt.money(r.amount)} {r.cadence} · {r.count} from {fmt.day(r.first)} to {fmt.day(r.last)}</div>
                <div className="line tnum">{r.payment ? <>Paid back {fmt.money(r.payment.amount)} {r.payment.cadence === "weekly" ? "weekly" : "daily"} ({money0(r.payment.monthly)} a month){r.payment.payee ? <span className="muted"> · {r.payment.payee}</span> : null}</> : <span className="muted">The payment back to them isn't named on the statements; mark it with Fix on its row (Lender payment, {r.name})</span>}</div>
              </div>
              <div className="right"><div className="tnum" style={{ fontWeight: 600, color: "var(--st-funded)" }}>{fmt.money(r.total)}</div><div className="when">received on these statements</div></div>
            </div>
          ))}
        </>
      ) : null}
    </div>
  );
}

// The lender's name for a funding not matched to the office's list: the deposit's own wording, trimmed.
const fundingName = (f) => String(f.description || "").replace(/\b(incoming wire|wire in|wire transfer|fedwire|book transfer|proceeds|ach|credit|deposit|dep|ppd|ccd|orig co name:?|co entry descr:?|des:?|id:?)\b/gi, " ").replace(/[#:]?\s*\d{4,}.*$/, "").replace(/\s+/g, " ").trim().slice(0, 28) || "a lender";

// Money in from lenders: each funding on the statements, newest first, with the payments it started when they show up.
// How sure it is that a deposit was a funding: payments to the same lender began after it (Confirmed), it came from
// a lender on the list, a known funder or one named in a rule (Lender match), or only its wording says so (Possible).
const fundingCertainty = (f) => (f.payments ? { label: "Confirmed", cls: "s-funded", tip: "Repeating payments to the same lender began after it" }
  : f.basis === "payments" ? { label: "Payments follow", cls: "s-funded", tip: "No lender is named, but new repeating payments began within three weeks after it" }
  : f.basis === "wording" ? { label: "Possible", cls: "s-review", tip: "Only the bank's wording says this is a funding, and no payments to the sender follow it yet" }
  : { label: "Lender match", cls: "s-submitted", tip: f.basis === "rule" ? "A statement rule names the lender" : "The sender is a lender on the list or a known funder; no payments to it follow yet" });
function StatementFundings({ summary }) {
  const list = (summary && summary.fundings) || [];
  const rules = useStatementRules();
  if (!list.length) return null;
  return (
    <div className="card card-flush">
      <div className="card-head"><span className="label">Recent fundings ({list.length})</span><span className="dim" style={{ fontSize: 12 }}>deposits from lenders on the statements</span></div>
      {list.map((f, i) => (
        <div className="row" key={i}>
          <div className="grow">
            <div className="title">{f.lender || fundingName(f)}{(() => { const c = fundingCertainty(f); return <span className={`pill plain ${c.cls}`} title={c.tip}>{c.label}</span>; })()}{f.platform ? <span className="pill plain s-neutral" title="Platform financing: not counted as a position">Platform</span> : null}{f.ref ? <span className="dim" style={{ fontWeight: 400 }}>deal {f.ref}</span> : null}</div>
            <div className="line">{f.payments
              ? <span className="tnum">Payments {fmt.money(f.payments.amount)} {f.payments.cadence === "weekly" ? "weekly" : "daily"} from {fmt.day(f.payments.since)} · {f.payments.count} so far, {fmt.money(f.payments.total)} paid on these statements</span>
              : <span className="muted">No repeating payments to this lender found after it yet</span>}</div>
            <div className="when" title={f.description}>{f.description}{rules && rules.canEdit ? <> · <button type="button" className="link-btn" onClick={() => openRuleSheet({ description: f.description, direction: "credit", amount: f.amount, date: f.date, kind: "sales" })}>Not a funding</button></> : null}</div>
          </div>
          <div className="right"><div className="tnum" style={{ fontWeight: 600, color: "var(--st-funded)" }}>{fmt.money(f.amount)}</div><div className="when tnum">{fmt.day(f.date)} ’{String(f.date).slice(2, 4)}</div></div>
        </div>
      ))}
    </div>
  );
}

// What the client file shows from its advances, worked out here from a lead's or deal's own statements:
// the deposits-and-positions chart, the current positions and the banking figures.
function StatementOverview({ statements, onOpen }) {
  const data = statements && statements.data;
  if (!data || !data.summary) return null;
  return <><StatementChart statements={statements} /><StatementWarnings summary={data.summary} /><StatementPositions summary={data.summary} /><StatementFundings summary={data.summary} /><StatementSummary summary={data.summary} onOpen={onOpen} /></>;
}

// The figures across every statement on the deal.
function StatementSummary({ summary, onOpen }) {
  if (!summary) return null;
  const s = summary;
  const stat = (label, value, sub, bad) => <div className="stat"><div className="label">{label}</div><div className={`v tnum${bad ? " bad" : ""}`}>{value}</div>{sub ? <div className="s">{sub}</div> : null}</div>;
  return (
    <div className="card">
      <div className="card-head">
        <span className="label">Bank statements · {s.statements} {s.statements === 1 ? "statement" : "statements"}{s.from && s.to ? ` · ${dayRange(s.from, s.to)}` : ""}{s.accounts > 1 ? ` · ${s.accounts} accounts` : ""}</span>
        {onOpen ? <button className="link-btn" onClick={onOpen}>View statements</button> : null}
      </div>
      <div className="stats">
        {stat("Avg monthly sales", s.avgMonthlyRevenue != null ? money0(s.avgMonthlyRevenue) : "—", s.avgDepositCount != null ? `${Math.round(s.avgDepositCount)} deposits a month` : null)}
        {stat("Avg daily balance", s.avgDailyBalance != null ? money0(s.avgDailyBalance) : "—", s.lowestBalance != null ? `lowest ${money0(s.lowestBalance)}` : null)}
        {stat("Days below zero", String(s.negativeDays || 0), null, s.negativeDays > 0)}
        {stat("NSF and returns", String(s.nsfCount || 0), null, s.nsfCount > 0)}
        {stat("Existing positions", String((s.positions || []).length), ((s.positions || []).length ? `${money0(s.positionsMonthly)} a month${remitShare(s) !== null ? ` · ${remitShare(s)}% of avg sales` : ""}` : "None found") + ((s.platformPositions || []).length ? ` · plus ${s.platformPositions.length} platform (${money0(s.platformMonthly)} a month)` : ""))}
      </div>
      {s.needReview ? <div className="card-body dim" style={{ fontSize: 12.5, borderTop: "1px solid var(--rule-soft)" }}>{s.needReview === 1 ? "One statement doesn't" : `${s.needReview} statements don't`} tie out to the bank's own balances; {s.needReview === 1 ? "it is" : "they are"} marked below.</div> : null}
    </div>
  );
}

function StatementRows({ dealId, objectKey, part }) {
  const [rows, setRows] = useState(null);
  const [error, setError] = useState("");
  const rules = useStatementRules();
  const [ver, setVer] = useState(0);
  useEffect(() => { const on = () => setVer(v => v + 1); window.addEventListener("ef:statements-changed", on); return () => window.removeEventListener("ef:statements-changed", on); }, []);
  useEffect(() => { API.statementRows(dealId, objectKey, part).then(setRows, e => setError(e.message)); }, [dealId, objectKey, part, ver]);
  const canFix = !!(rules && rules.canEdit);
  if (error) return <div className="banner" role="alert">{error}</div>;
  if (!rows) return <div className="muted" style={{ fontSize: 13 }}>Loading rows…</div>;
  if (!rows.length) return <div className="muted" style={{ fontSize: 13 }}>No rows were saved for this statement.</div>;
  return (
    <div className="txn-wrap scroll" tabIndex={0} role="region" aria-label="Transactions read from this statement">
      <table className="txn tnum">
        <thead><tr><th>Date</th><th>Description</th><th className="num">In</th><th className="num">Out</th><th className="num">Balance</th><th>Filed as</th>{canFix ? <th><span className="sr-only">Fix</span></th> : null}</tr></thead>
        <tbody>
          {rows.map((t, i) => (
            <tr key={i}>
              <td>{fmt.day(t.date)}</td><td className="desc">{t.description}</td>
              <td className="num">{t.direction === "credit" ? fmt.money(t.amount) : ""}</td><td className="num">{t.direction === "debit" ? fmt.money(t.amount) : ""}</td>
              <td className="num">{t.balance == null ? "" : fmt.money(t.balance)}</td>
              <td>{String(t.category || "").replace(/_/g, " ")}{t.rule ? <span className="muted" title={t.rule === "no-lender-evidence" ? "Only the reader called this a funding, with no lender behind it: counted as sales" : t.rule === "payments-follow" ? "No lender named, but new repeating payments began within three weeks after it: counted as a funding" : String(t.rule).startsWith("built-in") ? `Built-in rule (${String(t.rule).slice(10)})` : "An office statement rule"}> · rule</span> : null}{t.noPosition ? <span className="muted"> · not a position</span> : null}</td>
              {canFix ? <td><button type="button" className="link-btn" onClick={() => openRuleSheet({ description: t.description, direction: t.direction, amount: t.amount, date: t.date })}>Fix</button></td> : null}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

// `fileActions`: Open, Read again and Delete act on the whole file, so a file read as several statements
// shows them once, on the first of its statements in the list.
function StatementRow({ s, dealId, canWrite, ready, onRead, onDelete, fileActions = true }) {
  const [open, setOpen] = useState(false);
  const [rows, setRows] = useState(false);
  const [busy, setBusy] = useState(false);
  const st = statementStatus(s);
  const h = s.header || {}, m = s.summary;
  const pending = ST_PENDING.includes(s.status);
  const read = async () => { setBusy(true); await onRead(s.objectKey); setBusy(false); };
  return (
    <div className="st-row">
    <div className="row st-head">
      <span className="doc-icon">PDF</span>
      <div className="grow">
        <div className="title">{h.bankName ? `${h.bankName}${h.accountLast4 ? " ····" + h.accountLast4 : ""}` : s.name}<Pill status={st.pill} label={st.label} /></div>
        <div className="when" style={{ marginTop: 2 }}>{[dayRange(h.periodStart, h.periodEnd), h.bankName ? s.name : "", s.partOf ? `statement ${s.part + 1} of ${s.partOf} in this file${Array.isArray(s.filePages) && s.filePages.length ? ` (${s.filePages.length === 1 ? "page" : "pages"} ${s.filePages[0]}${s.filePages.length > 1 ? "–" + s.filePages[s.filePages.length - 1] : ""})` : ""}` : "", s.pages && fileActions ? `${s.pages} ${s.pages === 1 ? "page" : "pages"}` : "", s.uploadedBy ? `from ${s.uploadedBy}` : "", fileActions && s.spent ? `reading cost ${readCost(s.spent)}` : ""].filter(Boolean).join(" · ")}</div>
        {s.reason ? <div className="line">{s.reason}</div> : null}
        {m ? (
          <>
            <div className="terms tnum">
              <span>Sales deposits <b>{money0(m.deposits.revenue)}</b> ({m.deposits.revenueCount})</span>
              {m.balances ? <span>Avg balance <b>{money0(m.balances.average)}</b></span> : null}
              {m.balances ? <span>Lowest <b>{money0(m.balances.lowest)}</b></span> : null}
              <span>NSF <b>{m.nsf.count}</b></span>
              <span>Positions <b>{m.positions.filter(p => !p.platform).length}</b>{m.positions.some(p => p.platform) ? <span className="muted"> + {m.positions.filter(p => p.platform).length} platform</span> : null}</span>
            </div>
            {m.flags.length ? <div className="chips">{m.flags.map((f, i) => <span key={i} className={`chip flag-${f.level}`}>{f.text}</span>)}</div> : null}
            <div className="when"><button className="link-btn" onClick={() => setOpen(!open)} aria-expanded={open}>{open ? "Hide breakdown" : "Breakdown"}</button></div>
          </>
        ) : null}
      </div>
      <div className="row-actions">
        {!fileActions ? null : <a className="btn btn-sm" href={API.documentUrl(s.objectKey)} target="_blank" rel="noopener">Open</a>}
        {canWrite && ready && !pending && fileActions ? <button className="btn btn-sm" onClick={read} disabled={busy}>{busy ? "Starting…" : s.status ? "Read again" : "Read"}</button> : null}
        {canWrite && onDelete && fileActions ? <button className="icon-btn" onClick={() => onDelete({ objectKey: s.objectKey, name: s.name })} aria-label={`Delete ${s.name}`}><Icon name="trash" size={15} /></button> : null}
      </div>
    </div>
            {m && open ? (
              <div className="detail st-detail">
                <dl className="st-kv tnum">
                  <div><dt>All money in</dt><dd>{fmt.money(m.deposits.total)} ({m.deposits.count})</dd></div>
                  <div><dt>Sales deposits</dt><dd>{fmt.money(m.deposits.revenue)} on {m.deposits.days} {m.deposits.days === 1 ? "day" : "days"}, averaging {fmt.money(m.deposits.average)}</dd></div>
                  {m.deposits.cardSettlements ? <div><dt>Of which card payouts</dt><dd>{fmt.money(m.deposits.cardSettlements)}</dd></div> : null}
                  {m.deposits.transfersIn ? <div><dt>Transfers in</dt><dd>{fmt.money(m.deposits.transfersIn)}</dd></div> : null}
                  {m.deposits.loanProceeds ? <div><dt>Loan or advance proceeds</dt><dd>{fmt.money(m.deposits.loanProceeds)}</dd></div> : null}
                  {m.deposits.reversals ? <div><dt>Reversals and refunds</dt><dd>{fmt.money(m.deposits.reversals)}</dd></div> : null}
                  <div><dt>All money out</dt><dd>{fmt.money(m.withdrawals.total)} ({m.withdrawals.count})</dd></div>
                  {h.openingBalance != null && h.closingBalance != null ? <div><dt>Opening to closing</dt><dd>{fmt.money(h.openingBalance)} to {fmt.money(h.closingBalance)}</dd></div> : null}
                  {m.balances ? <div><dt>Lowest balance</dt><dd>{fmt.money(m.balances.lowest)} on {fmt.day(m.balances.lowestDate)}{m.balances.negativeDays ? `, below zero on ${m.balances.negativeDays} ${m.balances.negativeDays === 1 ? "day" : "days"}` : ""}</dd></div> : null}
                  <div><dt>Check against the bank</dt><dd>{m.check.status === "ok" ? "Opening balance plus the rows read equals the closing balance" : m.check.status === "mismatch" ? `Off by ${fmt.money(Math.abs(m.check.difference))}` : m.check.status === "totals" ? "The balances agree, but the rows don't add up to the bank's own printed totals" : "No balances printed to check against"}</dd></div>
                </dl>
                {m.positions.length ? (
                  <>
                    <b>Existing positions</b>
                    <div className="txn-wrap" style={{ maxHeight: "none", marginTop: 6 }}>
                      <table className="txn tnum">
                        <thead><tr><th>Paid to</th><th className="num">Payment</th><th>How often</th><th className="num">Seen</th><th className="num">About a month</th></tr></thead>
                        <tbody>{m.positions.map((p, i) => <tr key={i}><td className="desc">{p.platform ? <span className="pill plain s-neutral" style={{ marginRight: 6 }}>Platform</span> : null}{p.lender ? <><b>{p.lender}</b><span className="muted"> · {p.payee}{p.ref ? ` · deal ${p.ref}` : ""}</span></> : p.payee}{p.was ? <span className="muted"> · was {fmt.money(p.was)}</span> : null}</td><td className="num">{fmt.money(p.amount)}</td><td>{fmt.title(p.cadence)}</td><td className="num">{p.count}×, last {fmt.day(p.lastDate)}</td><td className="num">{money0(p.monthly)}</td></tr>)}</tbody>
                      </table>
                    </div>
                  </>
                ) : null}
                {m.nsf.items.length ? (
                  <div style={{ marginTop: 12 }}>
                    <b>NSF, overdraft and returned items</b>
                    {m.nsf.items.map((t, i) => <div key={i} className="tnum">{fmt.day(t.date)} · {fmt.money(t.amount)} · {t.description}</div>)}
                  </div>
                ) : null}
                {m.deposits.largest.length ? (
                  <div style={{ marginTop: 12 }}>
                    <b>Largest sales deposits</b>
                    {m.deposits.largest.map((t, i) => <div key={i} className="tnum">{fmt.day(t.date)} · {fmt.money(t.amount)} · {t.description}</div>)}
                  </div>
                ) : null}
                <div style={{ marginTop: 12 }}><button className="link-btn" onClick={() => setRows(!rows)} aria-expanded={rows}>{rows ? "Hide the rows" : `Show all ${m.transactionCount} rows`}</button></div>
                {rows ? <StatementRows dealId={dealId} objectKey={s.objectKey} part={s.part} /> : null}
              </div>
            ) : null}
    </div>
  );
}

// The Documents tab's statements section.
function StatementsCard({ deal, statements, canWrite, toast, onDelete }) {
  const data = statements.data;
  const files = (Array.isArray(deal.docs) ? deal.docs : []).filter(isStatementFile);
  if (!data || (!files.length && !data.statements.length)) return null;
  const known = new Set(data.statements.map(s => s.objectKey));
  // Files with no reading yet (uploaded before reading was set up).
  const unread = files.filter(f => !known.has(f.objectKey)).map(f => ({ objectKey: f.objectKey, name: f.name, uploadedBy: f.uploadedBy }));
  const onRead = async (key) => {
    try { await API.readStatement(deal.id, key); await statements.reload(); toast("Reading the statement"); }
    catch (err) { toast(err.message, true); }
  };
  return (
    <>
      <StatementSummary summary={data.summary} />
      <div className="section-bar"><span className="label">Bank statements{data.spent ? <span className="dim" style={{ textTransform: "none", letterSpacing: 0, fontWeight: 400, marginLeft: 8 }}>reading cost {readCost(data.spent)}</span> : null}</span><span style={{ display: "inline-flex", gap: 12, alignItems: "center" }}>{statements.pending ? <span className="muted" style={{ fontSize: 12.5 }}>Reading… this takes a minute or two a statement</span> : null}<button type="button" className="link-btn" onClick={openRulesSheet}>Statement rules</button></span></div>
      {!data.ready ? <div className="banner info">Statement reading isn't set up on the server yet, so statements are stored but not read.</div> : null}
      <div className="card card-flush">
        {(() => { const shown = new Set(); return data.statements.concat(unread).map(s => { const first = !shown.has(s.objectKey); shown.add(s.objectKey); return <StatementRow key={s.objectKey + "#" + (s.part || 0)} s={s} dealId={deal.id} canWrite={canWrite} ready={data.ready} onRead={onRead} onDelete={onDelete} fileActions={first} />; }); })()}
      </div>
    </>
  );
}

Object.assign(window, { CallNotesCard, CallNotesPanel, CallNotesStrip, BankLabels, bankLabelsOf, StatementWarnings, useStatements, StatementSummary, StatementsCard, StatementChart, StatementPositions, StatementFundings, StatementOverview, RemitBadge, remitShare, remitShareAll, remitBand, readCost, statementMonths, isStatementFile });
