/* global React */
// components-fdic-npl.jsx — Four NPL-investor sourcing dashboards for the bank page.
// Built to uploads/bank_intel_four_dashboard_fixes.md.
// Tab order: Investor Summary · NPL / Loans · Held For Sale · Sold Nonaccruals ·
//            Charge-Offs · OREO + Texas · Capital / Liquidity Context.
// Public FDIC /financials drives loan, NPL, charge-off, OREO, capital and liquidity
// views. RC-N memo items 5/7/8 (HFS late/nonaccrual, sold nonaccruals) are not in the
// public API — they come from the FFIEC CDR feed adapter (live proxy or modeled sample).
(function () {
  const { useState, useMemo, useEffect } = React;

  // ── value helpers (FDIC reports $ in thousands) ──
  function dol(valK, dec = 1) {
    if (valK == null || valK === "" || isNaN(Number(valK))) return "—";
    const n = Number(valK) * 1000, a = Math.abs(n);
    if (a >= 1e12) return "$" + (n / 1e12).toFixed(dec) + "T";
    if (a >= 1e9) return "$" + (n / 1e9).toFixed(dec) + "B";
    if (a >= 1e6) return "$" + (n / 1e6).toFixed(dec) + "M";
    if (a >= 1e3) return "$" + (n / 1e3).toFixed(0) + "K";
    return "$" + n.toFixed(0);
  }
  function pct(x, dec = 1) {
    if (x == null || !isFinite(x)) return "N/A";
    return x.toFixed(dec) + "%";
  }
  const num = (v) => { const n = Number(v); return isNaN(n) ? 0 : n; };

  function qLabel(repdte) {
    const s = String(repdte);
    const y = s.slice(0, 4), m = s.slice(4, 6);
    const q = { "03": "Q1", "06": "Q2", "09": "Q3", "12": "Q4" }[m] || "";
    return q + " " + y;
  }
  const qIndex = (repdte) => { const s = String(repdte); return Number(s.slice(0, 4)) * 4 + (Number(s.slice(4, 6)) / 3 - 1); };

  function sellerColor(label) {
    if (label === "Hot") return "var(--vrb-navy)";
    if (label === "Priority") return "var(--vrb-blue)";
    if (label === "Watch") return "var(--vrb-blue)";
    return "var(--vrb-sky)";
  }

  // ── target real estate categories the public feed supports with delinquency ──
  const TARGET_CATS = [
    { id: "construction", label: "Construction & Land Dev", bal: "LNRECONS", p3: "P3RECONS", p9: "P9RECONS", nt: "NTRECONS" },
    { id: "multifamily",  label: "Multifamily (5+ units)",  bal: "LNREMULT", p3: "P3REMULT", p9: "P9REMULT", nt: "NTREMULT" },
    { id: "cre",          label: "Commercial RE (Nonfarm Nonres)", bal: "LNRENRES", p3: "P3RENRES", p9: "P9RENRES", nt: "NTRENRES" },
  ];

  // ── derive everything the dashboards need from the financials history ──
  function useNPL(fin, cert) {
    return useMemo(() => {
      if (!fin || !fin.length) return null;
      const rows = [...fin].sort((a, b) => qIndex(b.REPDTE) - qIndex(a.REPDTE)); // newest first
      const latest = rows[0], prev = rows[1];

      const ytdLookup = {};
      rows.forEach(r => { ytdLookup[String(r.REPDTE)] = r; });
      function qtrFlow(row, field) {
        const s = String(row.REPDTE), m = s.slice(4, 6);
        const cur = num(row[field]);
        if (m === "03") return cur; // Q1 = YTD
        const py = s.slice(0, 4), pm = { "06": "03", "09": "06", "12": "09" }[m];
        const pr = rows.find(rr => { const ss = String(rr.REPDTE); return ss.slice(0, 4) === py && ss.slice(4, 6) === pm; });
        return cur - (pr ? num(pr[field]) : 0);
      }

      const totalLoans = num(latest.LNLS) || num(latest.LNLSNET);
      const npl90 = num(latest.P9ASSET);
      const nonaccrual = num(latest.LNLSNTV);
      const totalNPL = npl90 + nonaccrual;
      const nplRatio = totalLoans > 0 ? (totalNPL / totalLoans) * 100 : null;
      const reo = num(latest.FREPO);
      const prevNPL = prev ? num(prev.P9ASSET) + num(prev.LNLSNTV) : null;

      const cats = TARGET_CATS.map(c => {
        const bal = num(latest[c.bal]);
        const p3 = num(latest[c.p3]);
        const p9 = num(latest[c.p9]);
        const nplAmt = p9;
        const ratio = bal > 0 ? (nplAmt / bal) * 100 : null;
        const prevP9 = prev ? num(prev[c.p9]) : 0;
        const trend = rows.slice(0, 4).reverse().map(r => {
          const b = num(r[c.bal]); return b > 0 ? (num(r[c.p9]) / b) * 100 : 0;
        });
        const ntQtr = qtrFlow(latest, c.nt);
        const avgBal = prev ? (bal + num(prev[c.bal])) / 2 : bal;
        const ncoRatio = avgBal > 0 ? (ntQtr * 4 / avgBal) * 100 : null;
        const prevNt = prev ? qtrFlow(prev, c.nt) : 0;
        const ntTrend = rows.slice(0, 4).reverse().map(r => qtrFlow(r, c.nt));
        return { ...c, bal, p3, p9, nplAmt, ratio, qoq: nplAmt - prevP9, trend,
          ntQtr, ncoRatio, qoqNt: ntQtr - prevNt, ntTrend };
      });

      const targetNPL = cats.reduce((s, c) => s + c.nplAmt, 0);
      const targetBal = cats.reduce((s, c) => s + c.bal, 0);
      const targetNtQtr = cats.reduce((s, c) => s + c.ntQtr, 0);
      const targetAvgBal = prev ? (targetBal + TARGET_CATS.reduce((s, c) => s + num(prev[c.bal]), 0)) / 2 : targetBal;
      const targetNcoRatio = targetAvgBal > 0 ? (targetNtQtr * 4 / targetAvgBal) * 100 : null;

      const ncoQtr = qtrFlow(latest, "NTLNLS");
      const avgLoans = prev ? (totalLoans + (num(prev.LNLS) || num(prev.LNLSNET))) / 2 : totalLoans;
      const ncoRatioTotal = avgLoans > 0 ? (ncoQtr * 4 / avgLoans) * 100 : null;
      // Guard the same way as the Sold-Nonaccrual fix: nonaccrual can be a
      // near-zero base for a bank that's mostly cleaned up its book, which
      // sends CO/Nonaccrual to absurd values (seen: 1283.8%). Suppress the
      // ratio below a $2M floor instead of showing a meaningless number.
      const coToNonaccrual = nonaccrual >= 2000 ? (ncoQtr / nonaccrual) * 100 : null;

      const resBal = num(latest.LNRE) - num(latest.LNRECONS) - num(latest.LNREMULT) - num(latest.LNRENRES);
      const creOwner = num(latest.LNRENROW), creNonOwner = num(latest.LNRENRN);

      const series = rows.slice().reverse().map(r => {
        const tl = num(r.LNLS) || num(r.LNLSNET);
        const np = num(r.P9ASSET) + num(r.LNLSNTV);
        return { label: qLabel(r.REPDTE), nplRatio: tl > 0 ? (np / tl) * 100 : 0, nco: qtrFlow(r, "NTLNLS") };
      });

      // shared investor signals (same engine as list + header)
      const sig = window.BankSignals ? window.BankSignals.compute(latest, prev, cert) : null;

      return {
        latest, prev, quarter: qLabel(latest.REPDTE), cert,
        totalLoans, npl90, nonaccrual, totalNPL, nplRatio, prevNPL, reo,
        cats: cats.sort((a, b) => b.nplAmt - a.nplAmt || (b.ratio || 0) - (a.ratio || 0)),
        targetNPL, targetBal, targetNcoRatio, targetNtQtr,
        ncoQtr, ncoRatioTotal, coToNonaccrual,
        resBal, creOwner, creNonOwner, series, sig,
      };
    }, [fin, cert]);
  }

  // ── small UI atoms ──
  function Signal({ level }) {
    const map = { High: "high", Moderate: "med", Medium: "med", Watch: "low", Low: "low", "N/A": "na", None: "na" };
    return <span className={"npl-signal npl-signal-" + (map[level] || "na")}>{level}</span>;
  }
  function Caveat({ children, tone = "info" }) {
    return <div className={"npl-caveat npl-caveat-" + tone}>{children}</div>;
  }
  function SourceNote({ children }) {
    return <div className="npl-source-note">{children}</div>;
  }
  function Spark({ data, color = "var(--vrb-blue)" }) {
    if (!data || data.length < 2) return <span className="npl-spark-empty">—</span>;
    const max = Math.max(...data, 0.0001), min = Math.min(...data, 0);
    const rng = max - min || 1, W = 70, H = 22;
    const pts = data.map((v, i) => `${(i / (data.length - 1)) * W},${H - ((v - min) / rng) * H}`).join(" ");
    return (
      <svg className="npl-spark" width={W} height={H} viewBox={`0 0 ${W} ${H}`}>
        <polyline points={pts} fill="none" stroke={color} strokeWidth="1.6" strokeLinejoin="round" strokeLinecap="round" />
      </svg>
    );
  }
  function Kpi({ label, value, sub, accent }) {
    return (
      <div className="npl-kpi" style={accent ? { "--kpi-accent": accent } : null}>
        <div className="npl-kpi-val" style={accent ? { color: accent } : null}>{value}</div>
        <div className="npl-kpi-lbl">{label}</div>
        {sub && <div className="npl-kpi-sub">{sub}</div>}
      </div>
    );
  }
  function Unavailable({ title, need }) {
    return (
      <div className="npl-unavailable">
        <div className="npl-unavailable-icon" aria-hidden="true">
          <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="9"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
        </div>
        <div>
          <div className="npl-unavailable-title">{title}</div>
          <p className="npl-unavailable-body">{need}</p>
        </div>
      </div>
    );
  }

  // ── FFIEC CDR feed hook: current-period RC-N memo 5/7/8 (live proxy or sample) ──
  function useRCN(cert, repdte, latest) {
    const [state, setState] = useState({ loading: true, data: null });
    useEffect(() => {
      let live = true;
      if (!window.FFIECFeed || cert == null) { setState({ loading: false, data: null }); return; }
      setState({ loading: true, data: null });
      window.FFIECFeed.fetchRCN(cert, repdte, latest).then(d => {
        if (live) setState({ loading: false, data: d });
      });
      return () => { live = false; };
    }, [cert, repdte]);
    return state;
  }

  // Semiannual RC-N history (newest first) for the HFS / Sold trend tables.
  // One batched request per bank when an FFIEC CDR proxy is connected; modeled
  // sample otherwise. `loading` is true only until the first batch resolves.
  function useRCNHistory(cert, latest, n = 4) {
    const [state, setState] = useState({ loading: true, rows: [] });
    const repdte = latest && latest.REPDTE;
    useEffect(() => {
      let live = true;
      if (!window.FFIECFeed || !window.FFIECFeed.fetchRCNHistory || cert == null || !repdte) {
        setState({ loading: false, rows: [] });
        return;
      }
      setState({ loading: true, rows: [] });
      window.FFIECFeed.fetchRCNHistory(cert, repdte, n, latest).then(rows => {
        if (live) setState({ loading: false, rows: rows || [] });
      });
      return () => { live = false; };
    }, [cert, repdte, n]);
    return state;
  }

  function SourceBadge({ source }) {
    if (source === "live") return <span className="npl-source-badge npl-source-badge--live">Live · FFIEC CDR</span>;
    if (source === "stale") return <span className="npl-source-badge npl-source-badge--stale">Live · cached (retrying)</span>;
    if (source === "error") return <span className="npl-source-badge npl-source-badge--error">Feed error</span>;
    return <span className="npl-source-badge npl-source-badge--sample">Modeled sample</span>;
  }
  const rcnAsOfLabel = (asOf) => { const s = String(asOf || ""); return s.length < 6 ? "" : qLabel(s); };

  // ════════════════════════ Investor Summary ════════════════════════
  function SummaryTab({ d, cert }) {
    const { data: rcn } = useRCN(cert, d.latest.REPDTE, d.latest);
    const sig = d.sig || {};
    const rcnOk = rcn && rcn.source !== "error";
    const severeHFS = rcnOk ? rcn.memo5.pd90 + rcn.memo5.nonaccrual : (sig.severeHFS || 0);
    const sold = rcnOk ? rcn.memo8Sold : (sig.soldNonaccrual || 0);
    const nplColor = d.nplRatio >= 3 ? "var(--status-negative)" : d.nplRatio >= 1.5 ? "var(--status-caution)" : "var(--status-positive)";
    const ncoColor = d.ncoRatioTotal >= 1 ? "var(--status-negative)" : d.ncoRatioTotal >= 0.4 ? "var(--status-caution)" : "var(--status-positive)";

    const interp = [
      sig.severeHFS > 0 && { label: "Held-for-sale distress", note: "Severe HFS balance present — near-term sale pipeline signal." },
      sig.soldNonaccrual > 0 && { label: "Proven seller behavior", note: "Bank has disposed of nonaccrual assets in the latest 6-month window." },
      sig.isNplOutlier && { label: "NPL outlier", note: "NPL / loans is above the " + (window.BankSignals ? window.BankSignals.T.nplOutlier.toFixed(1) : "3.0") + "% sourcing threshold." },
      sig.isCoSpike && { label: "Charge-off pressure", note: "Real-estate net charge-off ratio is elevated or rising." },
      sig.isOreoHeavy && { label: "OREO accumulation", note: "OREO is material vs. assets or tangible equity — REO opportunity." },
      sig.isTexasDanger && { label: "Texas danger", note: "Texas ratio is above 100% — broad balance-sheet stress." },
      sig.isCapitalPressure && { label: "Capital pressure", note: "Capital ratios are near regulatory minimums." },
    ].filter(Boolean);

    return (
      <div className="fdic-section">
        <div className="fdic-section-title">Investor Summary <span className="npl-asof">as of {d.quarter}</span></div>

        {/* Seller Signal Score — horizontal meter. Score + label sit on their
            own row above the bar, badges sit below it, so nothing ever
            squeezes into leftover flex space and wraps unpredictably. */}
        {sig.sellerScore != null && (
          <div className="npl-meter" style={{ "--score-color": sellerColor(sig.sellerLabel) }}>
            <div className="npl-meter-row">
              <span className="npl-meter-score">{sig.sellerScore}<span className="npl-meter-of">/100</span></span>
              <span className="npl-meter-label" style={{ color: sellerColor(sig.sellerLabel) }}>{sig.sellerLabel} seller signal · {sig.sellerClass}</span>
            </div>
            <div className="npl-meter-bar"><div className="npl-meter-fill" style={{ width: sig.sellerScore + "%" }} /></div>
            <div className="npl-meter-badges">
              {sig.badges && sig.badges.length ? sig.badges.map(b => <span key={b} className="npl-score-badge">{b}</span>) : <span className="npl-score-none">No primary signals above threshold</span>}
            </div>
            <div className="npl-meter-weights">
              Severe HFS 25+15 · Sold 20+10 · NPL 15 · Target RE 10 · RE CO 10 · OREO 10 · Texas 5 · Capital 5
            </div>
          </div>
        )}

        <div className="npl-subhead">Target Real Estate Exposure</div>
        <div className="npl-kpi-grid npl-kpi-grid-4">
          <Kpi label="Target RE Loans" value={dol(d.targetBal)} sub="constr · multifamily · CRE" />
          <Kpi label="Target RE Problem Loans" value={dol(d.targetNPL)} accent={nplColor} sub="90+ PD only · target categories" />
          <Kpi label="OREO" value={dol(d.reo)} accent={sig.isOreoHeavy ? "var(--status-caution)" : null} sub="real estate owned" />
          <Kpi label="Texas Ratio" value={sig.texas != null ? pct(sig.texas) : "N/A"} accent={sig.isTexasDanger ? "var(--status-negative)" : null} sub="bank stress context" />
        </div>

        <div className="npl-subhead">Distress &amp; Seller Signals</div>
        <div className="npl-kpi-grid npl-kpi-grid-4">
          <Kpi label="NPL / Loans" value={pct(d.nplRatio)} accent={nplColor} sub={dol(d.totalNPL) + " total NPL"} />
          <Kpi label="RE Net CO Ratio" value={pct(sig.reNcoRatio, 2)} accent={ncoColor} sub="annualized" />
          <Kpi label="Severe HFS" value={dol(severeHFS)} accent={severeHFS > 0 ? "var(--vrb-blue)" : null} sub={<>sale pipeline <Signal level={sig.hfsSignal || "None"} /></>} />
          <Kpi label="Nonaccruals Sold" value={dol(sold)} accent={sold > 0 ? "var(--vrb-green)" : null} sub="latest 6-mo window" />
        </div>

        <div className="npl-trend-card">
          <div className="npl-trend-head">
            <span className="npl-trend-title">NPL / Loans — 8-quarter trend</span>
            <span className="npl-trend-now" style={{ color: nplColor }}>{pct(d.nplRatio)}</span>
          </div>
          <NPLTrendChart series={d.series} />
        </div>

        {interp.length > 0 && (
          <div className="npl-interp">
            <div className="npl-interp-title">Signal Interpretation</div>
            <ul className="npl-interp-list">
              {interp.map((it, i) => (
                <li key={i}><span className="npl-interp-label">{it.label}</span>{it.note}</li>
              ))}
            </ul>
          </div>
        )}

        <SourceNote>Bank-level sourcing view from public Call Report data plus FFIEC RC-N (HFS / sold, modeled sample until a CDR proxy is connected). Identifies seller pressure, not loan-level pricing.</SourceNote>
      </div>
    );
  }

  function NPLTrendChart({ series }) {
    if (!series || series.length < 2) return null;
    const W = 760, H = 150, padL = 40, padB = 22, padT = 12;
    const iW = W - padL - 8, iH = H - padB - padT;
    const max = Math.max(...series.map(s => s.nplRatio), 1) * 1.15;
    const x = (i) => padL + (i / (series.length - 1)) * iW;
    const y = (v) => padT + iH - (v / max) * iH;
    const line = series.map((s, i) => `${x(i)},${y(s.nplRatio)}`).join(" ");
    const area = `${padL},${padT + iH} ${line} ${x(series.length - 1)},${padT + iH}`;
    return (
      <svg className="npl-trend-svg" viewBox={`0 0 ${W} ${H}`} preserveAspectRatio="none" role="img" aria-label="NPL to loans ratio trend">
        {[0, max / 2, max].map((v, i) => (
          <g key={i}>
            <line x1={padL} x2={W - 8} y1={y(v)} y2={y(v)} stroke="var(--vrb-ink-200)" strokeWidth="1" strokeDasharray={i === 0 ? "" : "2 4"} />
            <text x={padL - 6} y={y(v) + 3} textAnchor="end" className="npl-axis">{v.toFixed(1)}%</text>
          </g>
        ))}
        <polyline points={area} fill="rgba(46,117,182,0.10)" stroke="none" />
        <polyline points={line} fill="none" stroke="var(--vrb-blue)" strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" />
        {series.map((s, i) => (
          <g key={i}>
            <circle cx={x(i)} cy={y(s.nplRatio)} r="3" fill="var(--vrb-blue)" />
            <text x={x(i)} y={H - 6} textAnchor="middle" className="npl-axis">{s.label}</text>
          </g>
        ))}
      </svg>
    );
  }

  // ════════════════════════ Dashboard 1 — NPL / Loans ════════════════════════
  function NPLModule({ d }) {
    const rows = d.cats.filter(c => c.bal > 0 || c.nplAmt > 0);
    return (
      <div className="fdic-section">
        <div className="fdic-section-title">NPL / Loans <span className="npl-asof">as of {d.quarter}</span></div>
        <div className="npl-kpi-grid npl-kpi-grid-4">
          <Kpi label="Total NPL" value={dol(d.totalNPL)} sub="90+ days + nonaccrual" />
          <Kpi label="NPL / Loans" value={pct(d.nplRatio)} accent={d.nplRatio >= 3 ? "var(--status-negative)" : d.nplRatio >= 1.5 ? "var(--status-caution)" : "var(--status-positive)"} sub="of total loans & leases" />
          <Kpi label="Target RE Problem Loans" value={dol(d.targetNPL)} sub="90+ PD only · constr/MF/CRE" />
          <Kpi label="Nonaccrual" value={dol(d.nonaccrual)} sub={"OREO held: " + dol(d.reo)} />
        </div>

        <div className="npl-table-wrap">
          <table className="npl-table npl-table--stacked">
            <thead>
              <tr>
                <th>Target Real Estate Category</th>
                <th className="num">Balance</th>
                <th className="num">% Loans</th>
                <th className="num">30–89 PD</th>
                <th className="num">90+ PD</th>
                <th className="num">Problem Amt</th>
                <th className="num">Problem %</th>
                <th className="num">QoQ Δ</th>
                <th className="num">4Q Trend</th>
              </tr>
            </thead>
            <tbody>
              {rows.map(c => (
                <tr key={c.id}>
                  <td className="npl-cat" data-label="Category">{c.label}</td>
                  <td className="num" data-label="Balance">{dol(c.bal)}</td>
                  <td className="num" data-label="% Loans">{d.totalLoans > 0 ? pct((c.bal / d.totalLoans) * 100) : "—"}</td>
                  <td className="num" data-label="30–89 PD">{dol(c.p3)}</td>
                  <td className="num" data-label="90+ PD">{dol(c.p9)}</td>
                  <td className="num npl-strong" data-label="Problem Amt">{dol(c.nplAmt)}</td>
                  <td className="num" data-label="Problem %"><span className={"npl-ratio-pill " + (c.ratio >= 5 ? "bad" : c.ratio >= 2 ? "warn" : "ok")}>{pct(c.ratio)}</span></td>
                  <td className={"num " + (c.qoq > 0 ? "npl-up" : c.qoq < 0 ? "npl-down" : "")} data-label="QoQ Δ">{c.qoq === 0 ? "—" : (c.qoq > 0 ? "+" : "") + dol(c.qoq)}</td>
                  <td className="num" data-label="4Q Trend"><Spark data={c.trend} color={c.ratio >= 5 ? "var(--status-negative)" : "var(--vrb-blue)"} /></td>
                </tr>
              ))}
              <tr>
                <td className="npl-cat" data-label="Category">1–4 Family &amp; Other RE <span className="npl-tag">balance only</span></td>
                <td className="num" data-label="Balance">{dol(d.resBal)}</td>
                <td className="num" data-label="% Loans">{d.totalLoans > 0 ? pct((d.resBal / d.totalLoans) * 100) : "—"}</td>
              </tr>
            </tbody>
          </table>
        </div>
        <SourceNote>Per-category 1–4 family delinquency is not itemized in the public feed — balance only above. Category NPL = 90+ days past due and still accruing (public RC-N item lines, construction / multifamily / commercial RE). Per-category nonaccrual and 1–4 family lien splits are not in the public API; total NPL (top cards) includes total nonaccrual.</SourceNote>
      </div>
    );
  }

  // ════════════════════════ Dashboard 2 — Held For Sale ════════════════════════
  function HFSModule({ d, cert }) {
    const { loading, data } = useRCN(cert, d.latest.REPDTE, d.latest);
    const { rows: hist } = useRCNHistory(cert, d.latest, 4);
    const m5 = data && data.memo5 ? data.memo5 : { pd90: 0, nonaccrual: 0 };
    const severe = data ? m5.pd90 + m5.nonaccrual : 0;
    const sevToLoans = d.totalLoans > 0 ? severe / d.totalLoans : 0;
    const level = !data ? "N/A" : severe <= 0 ? "None" : sevToLoans >= 0.01 ? "High" : sevToLoans >= 0.003 ? "Moderate" : "Watch";
    return (
      <div className="fdic-section">
        <div className="fdic-section-title">
          Held For Sale — Late &amp; Nonaccrual
          {data && <span className="npl-asof">as of {rcnAsOfLabel(data.asOf)}</span>}
          {data && <SourceBadge source={data.source} />}
        </div>
        {loading ? (
          <div className="fdic-skeleton fdic-skeleton-card" style={{ height: 120 }} />
        ) : !data ? (
          <Unavailable title="Requires RC-N Memorandum Item 5"
            need="Held-for-sale loans 90+ days past due or in nonaccrual status are reported on RC-N memo item 5 (semiannual). Connect an FFIEC CDR proxy to populate this module." />
        ) : data.source === "error" ? (
          <Unavailable title="FFIEC feed unreachable"
            need={"Proxy error: " + data.error + ". Verify FFIEC_CONFIG.proxyUrl and the CDR security token."} />
        ) : (
          <>
            <div className="npl-kpi-grid npl-kpi-grid-4">
              <Kpi label="HFS 90+ Days PD" value={dol(data.memo5.pd90)} sub="memo 5, col B" />
              <Kpi label="HFS Nonaccrual" value={dol(data.memo5.nonaccrual)} sub="memo 5, col C" />
              <Kpi label="Severe HFS" value={dol(severe)} accent={severe > 0 ? "var(--vrb-blue)" : "var(--status-positive)"} sub="90+ PD + nonaccrual" />
              <Kpi label="Sale Pipeline Signal" value={<Signal level={level} />} sub={pct(sevToLoans * 100, 2) + " of loans"} />
            </div>

            <div className="npl-table-wrap">
              <table className="npl-table npl-table--stacked">
                <thead>
                  <tr><th>Six-Month Period</th><th className="num">HFS 90+</th><th className="num">HFS Nonaccrual</th><th className="num">Severe HFS</th><th className="num">Severe HFS / Loans</th><th>Signal</th></tr>
                </thead>
                <tbody>
                  {hist.map((h, i) => {
                    const m5 = h && h.memo5 ? h.memo5 : { pd90: 0, nonaccrual: 0 };
                    const sev = m5.pd90 + m5.nonaccrual;
                    const r = d.totalLoans > 0 ? sev / d.totalLoans : 0;
                    const lv = sev <= 0 ? "None" : r >= 0.01 ? "High" : r >= 0.003 ? "Moderate" : "Watch";
                    return (
                      <tr key={i}>
                        <td className="npl-cat" data-label="Period">{rcnAsOfLabel(h.asOf)}{i === 0 && <span className="npl-tag">latest</span>}</td>
                        <td className="num" data-label="HFS 90+">{dol(m5.pd90)}</td>
                        <td className="num" data-label="HFS Nonaccrual">{dol(m5.nonaccrual)}</td>
                        <td className="num npl-strong" data-label="Severe HFS">{dol(sev)}</td>
                        <td className="num" data-label="Severe HFS / Loans">{pct(r * 100, 2)}</td>
                        <td data-label="Signal"><Signal level={lv} /></td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            </div>
          </>
        )}
        <SourceNote><strong>Aggregate</strong> RC-N memo 5 — not split by residential / CRE. Severe HFS = 90+ PD + nonaccrual HFS. 30–89 day column is confidential and not publicly distributed. Filed semiannually (Jun &amp; Dec).</SourceNote>
      </div>
    );
  }

  // ════════════════════════ Dashboard 3 — Sold Nonaccruals ════════════════════════
  function SoldModule({ d, cert }) {
    const { loading, data } = useRCN(cert, d.latest.REPDTE, d.latest);
    const { rows: hist } = useRCNHistory(cert, d.latest, 5);
    // Ending nonaccrual is context only — it is NOT used as the pace ratio's
    // denominator anymore. A bank that has cleared most of its nonaccrual book
    // can have an ending balance near zero, which made "sold / nonaccrual"
    // blow up to numbers like 1,387,097%. Total loans is a large, stable base
    // that stays meaningful at any nonaccrual level, so the headline ratio and
    // the history column both use Sold / Total Loans instead.
    const endingNonaccrual = d.nonaccrual;
    const smallBase = endingNonaccrual > 0 && endingNonaccrual < 2000; // < $2M
    const soldToLoans = (sold) => (d.totalLoans > 0 ? sold / d.totalLoans : null);
    const paceLevel = (r) => (r == null ? "N/A" : r <= 0 ? "None" : r >= 0.01 ? "High" : r >= 0.003 ? "Moderate" : "Watch");
    return (
      <div className="fdic-section">
        <div className="fdic-section-title">
          Sold Nonaccruals
          {data && <span className="npl-asof">six months to {rcnAsOfLabel(data.asOf)}</span>}
          {data && <SourceBadge source={data.source} />}
        </div>
        {loading ? (
          <div className="fdic-skeleton fdic-skeleton-card" style={{ height: 120 }} />
        ) : !data ? (
          <Unavailable title="Requires RC-N Memorandum Items 7 & 8"
            need="Nonaccrual assets sold and additions to nonaccrual over the previous six months are reported semiannually on RC-N memo items 7 and 8. Connect an FFIEC CDR proxy to populate this module." />
        ) : data.source === "error" ? (
          <Unavailable title="FFIEC feed unreachable"
            need={"Proxy error: " + data.error + ". Verify FFIEC_CONFIG.proxyUrl and the CDR security token."} />
        ) : (
          <>
            <div className="npl-kpi-grid npl-kpi-grid-4">
              <Kpi label="Nonaccrual Assets Sold" value={dol(data.memo8Sold)} accent={data.memo8Sold > 0 ? "var(--vrb-green)" : null} sub="outstanding balance at sale · prev 6 mo" />
              <Kpi label="Additions to Nonaccrual" value={dol(data.memo7Additions)} sub="memo 7 · prev 6 mo" />
              <Kpi label="Additions Less Sold" value={dol(data.memo7Additions - data.memo8Sold)} accent={data.memo7Additions - data.memo8Sold > 0 ? "var(--status-caution)" : "var(--status-positive)"} sub="net nonaccrual change" />
              <Kpi label="Sold / Total Loans" value={pct((soldToLoans(data.memo8Sold) || 0) * 100, 2)} accent={data.memo8Sold > 0 ? "var(--vrb-blue)" : null} sub={<>disposition pace <Signal level={paceLevel(soldToLoans(data.memo8Sold))} /></>} />
            </div>

            <div className="npl-table-wrap">
              <table className="npl-table npl-table--stacked">
                <thead>
                  <tr><th>Six-Month Period</th><th className="num">Nonaccrual Sold</th><th className="num">Additions</th><th className="num">Additions Less Sold</th><th className="num">Sold / Loans</th></tr>
                </thead>
                <tbody>
                  {hist.map((h, i) => (
                    <tr key={i}>
                      <td className="npl-cat" data-label="Period">{rcnAsOfLabel(h.asOf)}{i === 0 && <span className="npl-tag">latest</span>}</td>
                      <td className="num npl-strong" data-label="Nonaccrual Sold">{dol(h.memo8Sold)}</td>
                      <td className="num" data-label="Additions">{dol(h.memo7Additions)}</td>
                      <td className={"num " + (h.memo7Additions - h.memo8Sold > 0 ? "npl-up" : "npl-down")} data-label="Additions Less Sold">{dol(h.memo7Additions - h.memo8Sold)}</td>
                      <td className="num" data-label="Sold / Loans">{pct((soldToLoans(h.memo8Sold) || 0) * 100, 2)}</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
          </>
        )}
        <SourceNote><strong>Aggregate</strong> outstanding balance of nonaccrual assets sold — not sale price, not asset-class specific, and not sale proceeds. A non-zero value is a proven-seller signal. Filed semiannually; Q1/Q3 show the last reported window.{smallBase && <> Ending nonaccrual is a small base (<strong>{dol(endingNonaccrual)}</strong>), so disposition pace above is measured against total loans instead.</>}</SourceNote>
      </div>
    );
  }

  // ════════════════════════ Dashboard 4 — Charge-Offs ════════════════════════
  function ChargeoffModule({ d }) {
    const rows = d.cats.filter(c => c.bal > 0);
    const nonTargetNco = d.ncoQtr - d.targetNtQtr;
    return (
      <div className="fdic-section">
        <div className="fdic-section-title">Charge-Offs <span className="npl-asof">{d.quarter}</span></div>
        <div className="npl-kpi-grid npl-kpi-grid-4">
          <Kpi label="Target RE Net CO" value={dol(d.targetNtQtr)} sub="quarter · target categories" />
          <Kpi label="Target RE Net CO Ratio" value={pct(d.targetNcoRatio, 2)} accent={d.targetNcoRatio >= 0.5 ? "var(--status-caution)" : "var(--status-positive)"} sub="annualized" />
          <Kpi label="Bankwide Net CO" value={dol(d.ncoQtr)} sub="all loan types" />
          <Kpi label="CO / Nonaccrual" value={d.coToNonaccrual != null ? pct(d.coToNonaccrual) : "N/A"} sub={d.coToNonaccrual != null ? "cleanup-pace proxy" : "nonaccrual base too small to size"} />
        </div>
        <Caveat>Gross charge-off and recovery split is unavailable in the current public feed — figures shown are <strong>net</strong> charge-offs.</Caveat>

        <div className="npl-subhead">Table A — Target Real Estate Charge-Offs</div>
        <div className="npl-table-wrap">
          <table className="npl-table npl-table--stacked">
            <thead>
              <tr><th>Target RE Category</th><th className="num">Balance</th><th className="num">Net CO (Qtr)</th><th className="num">Net CO Ratio Ann.</th><th className="num">QoQ Δ</th><th className="num">4Q Trend</th></tr>
            </thead>
            <tbody>
              {rows.map(c => (
                <tr key={c.id}>
                  <td className="npl-cat" data-label="Category">{c.label}</td>
                  <td className="num" data-label="Balance">{dol(c.bal)}</td>
                  <td className="num npl-strong" data-label="Net CO (Qtr)">{dol(c.ntQtr)}</td>
                  <td className="num" data-label="Net CO Ratio Ann.">{pct(c.ncoRatio, 2)}</td>
                  <td className={"num " + (c.qoqNt > 0 ? "npl-up" : c.qoqNt < 0 ? "npl-down" : "")} data-label="QoQ Δ">{c.qoqNt === 0 ? "—" : (c.qoqNt > 0 ? "+" : "") + dol(c.qoqNt)}</td>
                  <td className="num" data-label="4Q Trend"><Spark data={c.ntTrend} color="var(--status-caution)" /></td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>

        <div className="npl-subhead">Table B — Bankwide Context</div>
        <div className="npl-table-wrap">
          <table className="npl-table npl-table--stacked">
            <thead>
              <tr><th>Bankwide Context</th><th className="num">Net CO (Qtr)</th><th className="num">Net CO Ratio Ann.</th><th>Why shown</th></tr>
            </thead>
            <tbody>
              <tr>
                <td className="npl-cat" data-label="Context">Non-Target Loans</td>
                <td className="num" data-label="Net CO (Qtr)">{dol(nonTargetNco)}</td>
                <td className="num" data-label="Net CO Ratio Ann.">—</td>
                <td className="npl-ctx-why" data-label="Why shown">C&amp;I, consumer, other — excluded from target views</td>
              </tr>
              <tr>
                <td className="npl-cat npl-strong" data-label="Context">Total Bank</td>
                <td className="num npl-strong" data-label="Net CO (Qtr)">{dol(d.ncoQtr)}</td>
                <td className="num" data-label="Net CO Ratio Ann.">{pct(d.ncoRatioTotal, 2)}</td>
                <td className="npl-ctx-why" data-label="Why shown">Overall bank stress context</td>
              </tr>
            </tbody>
          </table>
        </div>
        <SourceNote>RI-B charge-offs are reported YTD; quarterly figures are computed by differencing (Q1 = YTD). Ratios annualized over average category loans. Gross CO / recovery splits beyond construction, multifamily and commercial RE are not in the public feed.</SourceNote>
      </div>
    );
  }

  // ════════════════════════ Supporting — OREO + Texas ════════════════════════
  function OreoTexasModule({ d }) {
    const l = d.latest;
    const npl = d.totalNPL, oreo = d.reo;
    const equity = num(l.EQ), reserves = num(l.LNLSRES);
    const numerator = npl + oreo;
    const denom = equity + reserves;
    const texas = denom > 0 ? (numerator / denom) * 100 : null;
    const oreoShare = numerator > 0 ? oreo / numerator : 0;
    const driver = oreoShare >= 0.5 ? "OREO-driven" : oreoShare >= 0.25 ? "Mixed (loans + OREO)" : "Loan-driven";
    const band = texas == null ? null : texas >= 100 ? { l: "Danger", c: "var(--status-negative)" } : texas >= 75 ? { l: "Stressed", c: "var(--status-caution)" } : texas >= 50 ? { l: "Watch", c: "var(--status-caution)" } : { l: "Healthy", c: "var(--status-positive)" };
    const segs = numerator > 0 ? [
      { label: "Nonperforming Loans", v: npl, color: "var(--vrb-blue)" },
      { label: "OREO", v: oreo, color: "var(--status-caution)" },
    ] : [];
    return (
      <div className="fdic-section">
        <div className="fdic-section-title">OREO + Texas Ratio <span className="npl-asof">{d.quarter}</span></div>
        <div className="npl-kpi-grid npl-kpi-grid-4">
          <Kpi label="Texas Ratio" value={pct(texas)} accent={band ? band.c : null} sub={band ? band.l : ""} />
          <Kpi label="OREO" value={dol(oreo)} accent={oreoShare >= 0.5 ? "var(--status-caution)" : null} sub="real estate owned" />
          <Kpi label="Nonperforming Loans" value={dol(npl)} sub="90+ PD + nonaccrual" />
          <Kpi label="Stress Driver" value={driver} sub={pct(oreoShare * 100) + " of numerator is OREO"} />
        </div>
        <div className="npl-kpi-grid npl-kpi-grid-2" style={{ marginTop: 12 }}>
          <Kpi label="OREO / Assets" value={num(l.ASSET) > 0 ? pct((oreo / num(l.ASSET)) * 100, 2) : "N/A"} accent={num(l.ASSET) > 0 && oreo / num(l.ASSET) >= 0.01 ? "var(--status-caution)" : null} sub="REO burden vs. balance sheet" />
          <Kpi label="OREO / Equity" value={equity > 0 ? pct((oreo / equity) * 100, 1) : "N/A"} accent={equity > 0 && oreo / equity >= 0.10 ? "var(--status-caution)" : null} sub="REO burden vs. tangible-equity proxy" />
        </div>

        <div className="npl-decomp">
          <div className="npl-decomp-title">Texas Ratio Composition</div>
          <div className="npl-decomp-row">
            <span className="npl-decomp-lbl">Numerator · {dol(numerator)}</span>
            <div className="npl-decomp-bar">
              {segs.map(s => <div key={s.label} className="npl-decomp-seg" style={{ width: (numerator > 0 ? (s.v / numerator) * 100 : 0) + "%", background: s.color }} title={s.label + " " + dol(s.v)} />)}
            </div>
          </div>
          <div className="npl-decomp-row">
            <span className="npl-decomp-lbl">Denominator · {dol(denom)}</span>
            <div className="npl-decomp-bar">
              <div className="npl-decomp-seg" style={{ width: (denom > 0 ? (equity / denom) * 100 : 0) + "%", background: "var(--vrb-navy)" }} title={"Tangible equity " + dol(equity)} />
              <div className="npl-decomp-seg" style={{ width: (denom > 0 ? (reserves / denom) * 100 : 0) + "%", background: "var(--vrb-green)" }} title={"Loan loss reserves " + dol(reserves)} />
            </div>
          </div>
          <div className="npl-decomp-legend">
            <span><i style={{ background: "var(--vrb-blue)" }} />NPL</span>
            <span><i style={{ background: "var(--status-caution)" }} />OREO</span>
            <span><i style={{ background: "var(--vrb-navy)" }} />Equity</span>
            <span><i style={{ background: "var(--vrb-green)" }} />Reserves</span>
          </div>
        </div>
        <SourceNote>Texas Ratio = (nonperforming loans + OREO) ÷ (tangible equity + loan loss reserves). Decomposition shows whether stress is loan-driven or OREO-driven. Equity here uses total equity as a tangible-equity proxy; intangibles are not in the list-level feed.</SourceNote>
      </div>
    );
  }

  // ════════════════════════ Supporting — Capital / Liquidity ════════════════════════
  function CapitalLiquidityModule({ d }) {
    const l = d.latest;
    const raw = (v) => (v == null || v === "" || isNaN(Number(v)) || Number(v) === 0) ? null : Number(v);
    const t1lev = raw(l.RBCT1CER), t1rbc = raw(l.RBC1RWAJ), trbc = raw(l.RBCRWAJ);
    const dep = num(l.DEP), brok = num(l.BRO), sec = num(l.SC);
    const brokPct = dep > 0 ? (brok / dep) * 100 : null;
    const secToAssets = num(l.ASSET) > 0 ? (sec / num(l.ASSET)) * 100 : null;
    const prevDep = d.prev ? num(d.prev.DEP) : 0;
    const depQoq = prevDep > 0 ? ((dep - prevDep) / prevDep) * 100 : null;
    const capRow = (label, v, min, sub) => {
      const color = v == null ? null : v >= min * 1.5 ? "var(--status-positive)" : v >= min ? "var(--status-caution)" : "var(--status-negative)";
      return { label, v, min, sub, color };
    };
    const caps = [
      capRow("Tier 1 Leverage", t1lev, 4, "core capital / avg assets"),
      capRow("Tier 1 Risk-Based", t1rbc, 6, "core capital / RWA"),
      capRow("Total Risk-Based", trbc, 8, "total capital / RWA"),
    ];
    return (
      <div className="fdic-section">
        <div className="fdic-section-title">Capital / Liquidity Context <span className="npl-asof">{d.quarter}</span></div>

        <div className="npl-subhead">Capital Adequacy</div>
        <div className="npl-kpi-grid npl-kpi-grid-4">
          {caps.map(c => (
            <Kpi key={c.label} label={c.label} value={pct(c.v)} accent={c.color} sub={c.sub + " · min " + c.min + "%"} />
          ))}
          <Kpi label="Equity / Assets" value={num(l.ASSET) > 0 ? pct((num(l.EQ) / num(l.ASSET)) * 100, 1) : "N/A"} accent={num(l.ASSET) > 0 && num(l.EQ) / num(l.ASSET) < 0.06 ? "var(--status-negative)" : null} sub="leverage cushion" />
        </div>

        <div className="npl-subhead">Liquidity &amp; Funding</div>
        <div className="npl-kpi-grid npl-kpi-grid-4">
          <Kpi label="Brokered Deposits" value={dol(brok)} accent={brokPct != null && brokPct > 20 ? "var(--status-negative)" : brokPct != null && brokPct > 10 ? "var(--status-caution)" : null} sub={brokPct != null ? pct(brokPct) + " of deposits" : "—"} />
          <Kpi label="Total Deposits" value={dol(dep)} sub="funding base" />
          <Kpi label="Deposit QoQ Change" value={depQoq == null ? "N/A" : (depQoq > 0 ? "+" : "") + pct(depQoq, 1)} accent={depQoq != null && depQoq < -2 ? "var(--status-negative)" : null} sub="prior-quarter runoff/growth" />
          <Kpi label="Securities" value={dol(sec)} sub={secToAssets != null ? pct(secToAssets) + " of assets" : "—"} />
        </div>
        {brokPct != null && brokPct > 10 && (
          <Caveat tone="warn">
            Brokered deposits are <strong>{pct(brokPct)}</strong> of total deposits{brokPct > 20 ? " — elevated funding risk and a potential distress / forced-seller signal." : " — above average; monitor for liquidity pressure."}
          </Caveat>
        )}
        <SourceNote>Uninsured deposits / deposits, borrowings / assets, and unrealized securities losses / equity are not in the list-level feed. Capital ratios and brokered-deposit share come from the public Call Report and are shown as bank-stress context — capital or liquidity pressure raises the likelihood of a motivated seller, but is a supporting signal, not a primary sourcing dashboard.</SourceNote>
      </div>
    );
  }

  const TABS = [
    { id: "summary", label: "Investor Summary" },
    { id: "npl", label: "NPL / Loans" },
    { id: "hfs", label: "Held For Sale" },
    { id: "sold", label: "Sold Nonaccruals" },
    { id: "chargeoffs", label: "Charge-Offs" },
    { id: "balance", label: "Balance Sheet Context" },
  ];

  window.NPLInvestorDashboard = function NPLInvestorDashboard({ fin, cert }) {
    const [tab, setTab] = useState("summary");
    const d = useNPL(fin, cert);
    if (!d) return <div className="fdic-section"><div className="fdic-skeleton fdic-skeleton-card" style={{ height: 160 }} /></div>;
    return (
      <div className="npl-dash">
        <nav className="npl-pillnav" role="tablist">
          {TABS.map(t => (
            <button key={t.id} role="tab" aria-selected={tab === t.id}
              className={"npl-pill" + (tab === t.id ? " active" : "")}
              onClick={() => setTab(t.id)}>{t.label}</button>
          ))}
        </nav>
        {tab === "summary" && <SummaryTab d={d} cert={cert} />}
        {tab === "npl" && <NPLModule d={d} />}
        {tab === "hfs" && <HFSModule d={d} cert={cert} />}
        {tab === "sold" && <SoldModule d={d} cert={cert} />}
        {tab === "chargeoffs" && <ChargeoffModule d={d} />}
        {tab === "balance" && (
          <>
            <OreoTexasModule d={d} />
            <div className="npl-tab-divider" />
            <CapitalLiquidityModule d={d} />
          </>
        )}
      </div>
    );
  };
})();
