/* ============================================================
   market-sensor-decoupling.jsx — Decoupling Breadth summary.
   A simple 3-stat sector (State · Opportunity · Risk) that slots
   under the Macro watchlist on the Risk Sensing page. The
   Opportunity and Risk counts are clickable: they reveal the
   underlying name list inline.

   Self-loads data/decoupling_breadth.json (published by the
   planner at Step 1e). Renders nothing if the feed is absent, so
   it can never break the page. Exports window.MsrDecoupling.
   Reuses .msr tokens + window.msrTone from the core file.
   ============================================================ */
(function () {
  const { useState: dbState, useEffect: dbEffect, useCallback: dbCb } = React;

  /* long state label -> short headline word */
  const DB_SHORT = {
    'BROAD RESOLUTION': 'Broad',
    'PARTIAL RESOLUTION': 'Partial',
    'LOADED / RISK-OFF': 'Loaded',
    'QUIET': 'Quiet',
  };
  const dbShort = (lbl) => DB_SHORT[lbl] || (lbl ? lbl.split(' ')[0].replace(/^./, (c) => c.toUpperCase()) : '—');
  const dbFmt = (v) => (v == null || isNaN(v)) ? '—' : (v < 0 ? '\u2212' : '+') + Math.abs(v).toFixed(2);
  const dbSym = (s) => (s || '').replace('.NYB', '');
  const dbGrp = (g) => (g || '').replace('/', ' / ');

  function MsrDecoupling() {
    const [data, setData] = dbState(null);
    const [open, setOpen] = dbState({ opp: false, risk: false });

    const load = dbCb(() => {
      const R = window.__resources || {};
      const url = R.decoupling || 'data/decoupling_breadth.json';
      fetch(url, { cache: 'no-store' }).
        then((r) => { if (!r.ok) throw new Error('decoupling_breadth.json ' + r.status); return r.json(); }).
        then((d) => setData(d)).
        catch(() => setData(false)); /* false = checked, absent → render nothing */
    }, []);
    dbEffect(() => { load(); }, [load]);

    if (!data) return null;

    const b = data.breadth || {};
    const st = data.state || {};
    const wl = data.watchlist || [];
    const bySym = {};
    wl.forEach((w) => { bySym[w.sym] = w; });

    const count = b.count != null ? b.count : 0;
    const thr = b.threshold != null ? b.threshold : 3;
    const nScored = b.n_scored != null ? b.n_scored : wl.length;
    const loaded = b.regime_loading_inverse != null ? b.regime_loading_inverse : wl.filter((w) => w.state === 'INVERSE').length;

    const resolving = (b.resolving_now || []).map((s) => bySym[s]).filter(Boolean);
    const inverse = wl.filter((w) => w.state === 'INVERSE').sort((a, c) => (a.corr_now || 0) - (c.corr_now || 0));

    const tone = window.msrTone ? window.msrTone(st.color) : '#DCB482';
    const stateSub = count >= thr
      ? `${count} resolving together — high-conviction setup`
      : `${count} name${count === 1 ? '' : 's'} resolving — below the breadth-${thr} bar`;

    const toggle = (key) => setOpen((o) => ({ ...o, [key]: !o[key] }));

    return (
      <div className="msr-sec">
        <div className="msr-sec-head">
          <div>
            <div className="msr-sec-eb">Macro overlay · Step 1e</div>
            <h2 className="msr-sec-h">Decoupling Breadth</h2>
          </div>
          <span className="msr-sec-note">A 14-name macro-inverse basket for QQQ longs. Tap the Opportunity or Risk count to see the names behind it. Conviction overlay, 20–40d — not an entry trigger.</span>
        </div>

        <div className="msr-db">
          {/* STATE */}
          <div className="msr-db-card">
            <div className="msr-db-k">Today's state</div>
            <div className="msr-db-v state" style={{ color: tone }}>{dbShort(st.label)}</div>
            <p className="msr-db-d">{stateSub}</p>
          </div>

          {/* OPPORTUNITY */}
          <div className={`msr-db-card clickable${open.opp ? ' open' : ''}`}>
            <div className="msr-db-k">Breadth · the opportunity</div>
            <button className="msr-db-btn" aria-expanded={open.opp} onClick={() => toggle('opp')}>
              <span className="msr-db-v bull">{count}</span>
              <span className="msr-db-u">/ {nScored}</span>
              <span className="msr-db-cue"><span className="lbl">{open.opp ? 'hide' : 'view names'}</span><span className="chev">{'\u2304'}</span></span>
            </button>
            <p className="msr-db-d">{`\u2265 ${thr} resolving = high-conviction setup`}</p>
            {open.opp &&
            <div className="msr-db-list">
              <div className="msr-db-lh">Resolving now · {resolving.length}</div>
              <div className="msr-db-items">
                {resolving.length === 0
                  ? <div className="msr-db-item"><span className="grp">No names resolving yet — spring still loaded.</span></div>
                  : resolving.map((w, i) => (
                    <div className="msr-db-item res" key={i}>
                      <span className="sw" /><span className="sym">{dbSym(w.sym)}</span><span className="grp">{dbGrp(w.group)}</span>
                      <span className="corr">{dbFmt(w.corr_now)} <span className="from">{'\u2190'} {dbFmt(w.trough_10d)}</span></span>
                    </div>))}
              </div>
            </div>}
          </div>

          {/* RISK */}
          <div className={`msr-db-card clickable${open.risk ? ' open' : ''}`}>
            <div className="msr-db-k">Regime loading · the risk</div>
            <button className="msr-db-btn" aria-expanded={open.risk} onClick={() => toggle('risk')}>
              <span className="msr-db-v bear">{loaded}</span>
              <span className="msr-db-cue"><span className="lbl">{open.risk ? 'hide' : 'view names'}</span><span className="chev">{'\u2304'}</span></span>
            </button>
            <p className="msr-db-d">names still deeply inverse — spring loaded</p>
            {open.risk &&
            <div className="msr-db-list">
              <div className="msr-db-lh">Loaded · deeply inverse to QQQ · {inverse.length}</div>
              <div className="msr-db-items">
                {inverse.length === 0
                  ? <div className="msr-db-item"><span className="grp">Nothing deeply inverse right now.</span></div>
                  : inverse.map((w, i) => (
                    <div className="msr-db-item inv" key={i}>
                      <span className="sw" /><span className="sym">{dbSym(w.sym)}</span><span className="grp">{dbGrp(w.group)}</span>
                      <span className="corr">{dbFmt(w.corr_now)}</span>
                    </div>))}
              </div>
            </div>}
          </div>
        </div>
      </div>);
  }

  Object.assign(window, { MsrDecoupling });
})();
