/* ============================================================
   radar-app.jsx -- controls, 2 layout directions (A/B), app shell.
   Depends on radar-core.jsx + radar-cards.jsx globals.

   POLARITY-AWARE READOUT:
   - sigOn pills are { bull, bear, watch }
   - SummaryStrip uses a weighted score (counter x1.2, pro x1.0,
     haven x0.8) and surfaces a Fragility flag when bull and
     counter-bear signals fire at the same time.
   ============================================================ */
const { useState, useMemo, useEffect } = React;

const DIRS = [
{ id: 'A', name: 'Radar Brief', hint: 'Editorial -- radar leads, signals follow' },
{ id: 'B', name: 'Command Console', hint: 'Radar pinned left -- signal feed right' }];

const SORTS = [
{ id: 'onset', label: '|onset|' },
{ id: 'now', label: 'β now' },
{ id: 'amove', label: '5d move' },
{ id: 'sym', label: 'A-Z' }];


function fmtAsof(iso) {
  if (!iso) return '';
  const [y, m, d] = iso.split('-').map(Number);
  const mon = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][m - 1];
  return `${mon} ${d}, ${y}`;
}

/* -- control rail (live tweaks) ----------------------------- */
function ControlRail({ hl, setHl, hls, thr, setThr, sortKey, setSortKey, sigOn, toggleSig, counts, dir, setDir }) {
  return (
    <div className="ctl-rail">
      <div className="ctl-grp">
        <span className="ctl-lab">Layout</span>
        <div className="seg">
          {DIRS.map((d) => <button key={d.id} className={dir === d.id ? 'on' : ''} onClick={() => setDir(d.id)}>{d.name}</button>)}
        </div>
      </div>
      <div className="ctl-grp">
        <span className="ctl-lab">Half-life</span>
        <div className="seg">
          {hls.map((h) => <button key={h} className={hl === h ? 'on' : ''} onClick={() => setHl(h)}>{h}d</button>)}
        </div>
      </div>
      <div className="ctl-grp">
        <span className="ctl-lab">Onset threshold</span>
        <div className="thr-wrap">
          <input className="thr-slider" type="range" min="0.1" max="0.6" step="0.05" value={thr} onChange={(e) => setThr(parseFloat(e.target.value))} />
          <span className="thr-val">±{thr.toFixed(2)}</span>
        </div>
      </div>
      <div className="ctl-grp">
        <span className="ctl-lab">Sort</span>
        <div className="seg">
          {SORTS.map((s) => <button key={s.id} className={sortKey === s.id ? 'on' : ''} onClick={() => setSortKey(s.id)}>{s.label}</button>)}
        </div>
      </div>
      <div className="ctl-grp">
        <div className="sig-pills">
          {['bull', 'bear', 'watch'].map((k) => {
            const m = window.signalMeta(k);
            return (
              <button key={k} className={`sig-pill ${k} ${sigOn[k] ? 'on' : 'off'}`} onClick={() => toggleSig(k)}>
                <span className="dot" style={{ background: m.color }} />{m.label}<span className="pn">{counts[k]}</span>
              </button>);

          })}
        </div>
      </div>
    </div>);

}

/* -- summary strip ------------------------------------------ */
function SummaryStrip({ classed, counts }) {
  /* weighted score: counter x1.2, pro x1.0, haven x0.8, mixed x0.5 */
  const score = useMemo(() => {
    let s = 0;
    classed.forEach((r) => {
      const w = window.polarityMeta(r._pol).weight;
      if (r._sig === 'bull') s += w;
      else if (r._sig === 'bear') s -= w;
    });
    return s;
  }, [classed]);

  /* fragility flag: enough pro bulls AND counter assets also turning bear
     (counter-bear = inverse breaking down = haven/vol/bonds being bid alongside risk) */
  const fragility = useMemo(() => {
    let proBulls = 0, counterBears = 0, havenBears = 0;
    classed.forEach((r) => {
      if (r._sig === 'bull' && r._pol === 'pro') proBulls++;
      if (r._sig === 'bear' && r._pol === 'counter') counterBears++;
      if (r._sig === 'bear' && r._pol === 'haven') havenBears++;
    });
    return proBulls >= 3 && (counterBears + havenBears) >= 2;
  }, [classed]);

  let regime;
  if (fragility) {
    regime = { t: 'Fragile · single-factor tape', d: 'breadth bullish but havens/rates also bid -- read regime, not breadth' };
  } else if (score >= 2.5) {
    regime = { t: 'Risk-on tilt', d: `QQQ-bullish signals lead (net ${score.toFixed(1)})` };
  } else if (score <= -2.5) {
    regime = { t: 'Risk-off tilt', d: `QQQ-bearish signals lead (net ${score.toFixed(1)})` };
  } else {
    regime = { t: 'Mixed · rotational', d: `no decisive tilt (net ${score.toFixed(1)})` };
  }

  return (
    <div className="sum-strip">
      <div className="sum-cell">
        <div className="sum-l">Bullish for QQQ</div>
        <div className="sum-v bull">{counts.bull}<span className="u">names</span></div>
        <div className="sum-s">leadership/breadth engaging OR havens exiting</div>
      </div>
      <div className="sum-cell">
        <div className="sum-l">Bearish for QQQ</div>
        <div className="sum-v bear">{counts.bear}<span className="u">names</span></div>
        <div className="sum-s">leadership decoupling OR safety bid coming in</div>
      </div>
      <div className={`sum-cell regime${fragility ? ' fragility' : ''}`}>
        <div className="sum-l">Regime read</div>
        <div className="sum-v">{regime.t}</div>
        <div className="sum-s"><b>{counts.watch}</b> on watch · {regime.d}</div>
      </div>
    </div>);

}

/* -- section pieces ----------------------------------------- */
function RadarSection({ radarRows, hl, thr, compact, meta }) {
  return (
    <div className="sec">
      <h2 className="sec-h">What just turned bullish or bearish for QQQ</h2>
      <p className="sec-d">Each active instrument sits on a spoke grouped by class. Distance from the center is its <b>beta to QQQ</b>; the faint ring marks beta 5 sessions ago and the solid dot is today -- the <b>drift between them is the coupling onset</b>. Color reads polarity-aware: green = BULL-QQQ, red = BEAR-QQQ, amber = WATCH.</p>
      {meta &&
      <div className="asof-bar in-sec">
          <span className="asof-dot" />
          <span className="asof-txt">As of prior close<span className="sep">·</span>before open <b>{fmtAsof(meta.asof)}</b></span>
          <span className="asof-right">{meta.n} instruments scanned</span>
        </div>
      }
      {radarRows.length ? <window.RadarPlot rows={radarRows} hl={hl} thr={thr} betaCap={2.5} /> :
      <p className="sec-d" style={{ marginTop: 18, fontStyle: 'italic' }}>No active signals under the current threshold -- the universe is sitting quiet.</p>}
    </div>);

}

function CardsSection({ cards, hl, title, cols }) {
  if (!cards.length) {
    return (
      <div className="sec">
        <h2 className="sec-h">{title || 'QQQ signal feed'}</h2>
        <div className="empty">
          <div className="empty-mark"><svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M20 6L9 17l-5-5" /></svg></div>
          <h3 className="empty-h">No QQQ-implication shifts today -- universe stable</h3>
          <p className="empty-d">Nothing crossed the dual-gate at the current half-life and threshold, given each name's polarity. Every tracked instrument is holding its expected relationship to QQQ. Lower the threshold or switch half-life to surface early drift, or scan the full universe below.</p>
        </div>
      </div>);

  }
  return (
    <div className="sec">
      <h2 className="sec-h">{title || 'QQQ signal feed'}<span className="ct">{cards.length}</span></h2>
      <p className="sec-d">Each card is labelled by what the onset means <b>for QQQ direction</b>, not by the raw beta shift. The underlying mechanic (COUPLE+/FLIP-) is shown inside the card.</p>
      <div className={`cards-grid${cols === 2 ? ' cols-2' : ''}`} style={{ marginTop: 16 }}>
        {cards.map((r) => <window.AlertCard key={r.sym} row={r} hl={hl} sig={r._sig} />)}
      </div>
    </div>);

}

function TableSection({ rows, hl, thr }) {
  return (
    <div className="sec">
      <div className="sec-eb">full universe</div>
      <h2 className="sec-h">Every instrument we scan<span className="ct">{rows.length}</span></h2>
      <p className="sec-d">Sortable, filterable. Click any header to re-sort. Rows tinted by QQQ-implication signal; ● marks a name with news context in its alert card.</p>
      <div style={{ marginTop: 16 }}><window.UniverseTable rows={rows} hl={hl} thr={thr} /></div>
    </div>);

}

function MethodFooter({ meta }) {
  return (
    <div className="method">
      <div className="method-card">
        <div className="method-eb">how to read this radar</div>
        <div className="method-grid">
          <div className="method-item"><h4>Polarity matters</h4><p>A name's natural relationship to QQQ depends on what it is. Tech is <b>pro-cyclical</b> (should move with QQQ); TLT and VIX are <b>counter-cyclical</b> (should move against). The signal is read against that expectation.</p></div>
          <div className="method-item"><h4>BULL / BEAR / WATCH</h4><p>The label tells you what the onset implies <i>for QQQ direction</i> -- not the raw beta shift. Tech coupling up = BULL-QQQ. TLT coupling positively = BEAR-QQQ (safety bid). Same mechanic, opposite QQQ read.</p></div>
          <div className="method-item"><h4>Mechanic underneath</h4><p>The classifier still computes <b>COUPLE+</b> (beta rising + agreeing) and <b>FLIP-</b> (beta falling + diverging). You see the mechanic inside each card and tooltip; it's preserved for transparency.</p></div>
          <div className="method-item"><h4>Fragility flag</h4><p>When pro-cyclical names couple up <i>and</i> havens/rates also turn bear (positive coupling), the regime cell flips to <b>Fragile · single-factor tape</b> -- single macro driver, not real breadth. Size smaller, not bigger.</p></div>
          <div className="method-item"><h4>Trust the trend</h4><p>Scanning ~{meta.n} series, some cross by chance. The <b>25-day sparkline</b> -- sustained drift, not one spike -- is the signal to trust.</p></div>
        </div>
        <p className="method-foot">Daily-close snapshot, computed before the open -- not a live tick and not a trade trigger. Review &amp; monitoring only. Half-lives {meta.hls ? meta.hls.join(' / ') : '3 / 5 / 10'}-day · default {meta.defaultHL}d.{meta.failed_symbols && meta.failed_symbols.length ? ` ${meta.failed_symbols.length} symbol(s) dropped (no fabrication).` : ' Full universe resolved cleanly.'}</p>
      </div>
    </div>);

}

/* -- app ---------------------------------------------------- */
function RadarApp() {
  const [payload, setPayload] = useState(null);
  const [err, setErr] = useState(null);
  const [dir, setDir] = useState(() => {try {const d = localStorage.getItem('radar-dir');return d === 'A' || d === 'B' ? d : 'B';} catch (e) {return 'B';}});
  const [hl, setHl] = useState('5');
  const [thr, setThr] = useState(0.25);
  const [sortKey, setSortKey] = useState('onset');
  const [cat, setCat] = useState('all');
  const [sigOn, setSigOn] = useState({ bull: true, bear: true, watch: true });

  useEffect(() => {
    const R = window.__resources || {};
    fetch(R.radar || 'data/radar.json').
    then((r) => {if (!r.ok) throw new Error('radar.json ' + r.status);return r.json();}).
    then((d) => {setPayload(d);if (d.meta && d.meta.defaultHL) setHl(String(d.meta.defaultHL));}).
    catch((e) => setErr(e.message));
  }, []);

  useEffect(() => {try {localStorage.setItem('radar-dir', dir);} catch (e) {}}, [dir]);
  const toggleSig = (k) => setSigOn((s) => ({ ...s, [k]: !s[k] }));

  const meta = payload ? payload.meta : {};
  const allRows = payload ? payload.rows : [];
  const cats = useMemo(() => {
    const set = [...new Set(allRows.map((r) => r.cat))];
    set.sort((a, b) => window.catRank(a) - window.catRank(b) || a.localeCompare(b));
    return set;
  }, [allRows]);

  /* classify everything once, attach polarity + mechanic too */
  const classed = useMemo(() => allRows.map((r) => {
    const h = r.hl[hl] || r.hl['5'];
    return { ...r,
      _now: h.now, _ago: h.ago5, _onset: h.onset, _series: h.series,
      _sig: window.classify(r, hl, thr),
      _mech: window.mechanicOf(r, hl, thr),
      _pol: window.polarityOf(r.sym, r.cat),
    };
  }), [allRows, hl, thr]);

  const counts = useMemo(() => {
    const c = { bull: 0, bear: 0, watch: 0 };
    classed.forEach((r) => {if (c[r._sig] !== undefined) c[r._sig]++;});
    return c;
  }, [classed]);

  const catRows = useMemo(() => cat === 'all' ? classed : classed.filter((r) => r.cat === cat), [classed, cat]);

  /* alert cards: active (non-stable) rows passing signal filter, sorted */
  const cards = useMemo(() => {
    const dirv = sortKey === 'sym' ? 1 : -1;
    const val = (d) => sortKey === 'sym' ? d.sym : sortKey === 'now' ? d._now : sortKey === 'amove' ? d.amove : Math.abs(d._onset);
    return catRows.filter((r) => r._sig !== 'stable' && sigOn[r._sig]).
    sort((a, b) => {const va = val(a),vb = val(b);return typeof va === 'string' ? va.localeCompare(vb) * dirv : (va - vb) * dirv;});
  }, [catRows, sortKey, sigOn]);

  /* radar set: active rows passing filters, capped for readability */
  const radarRows = useMemo(() => {
    const set = catRows.filter((r) => r._sig !== 'stable' && sigOn[r._sig]);
    set.sort((a, b) => Math.abs(b._onset) - Math.abs(a._onset));
    return set.slice(0, 28);
  }, [catRows, sigOn]);

  if (err) return (
    <div className="radar-shell"><div className="sec"><div className="sec-eb">error</div><h2 className="sec-h">Couldn't load the pre-market radar feed.</h2><p className="sec-d">{err}. The planner writes <code>data/radar.json</code> before the open; check that the file is present.</p></div></div>);

  if (!payload) return (
    <div className="radar-shell"><div className="sec" style={{ paddingTop: 80, textAlign: 'center' }}><div className="sec-eb" style={{ color: 'var(--stone-1)' }}>loading pre-market feed...</div></div></div>);


  const ctl = <ControlRail hl={hl} setHl={setHl} hls={(meta.hls || [3, 5, 10]).map(String)} thr={thr} setThr={setThr}
  sortKey={sortKey} setSortKey={setSortKey} sigOn={sigOn} toggleSig={toggleSig} counts={counts} dir={dir} setDir={setDir} />;
  const summary = <SummaryStrip classed={classed} counts={counts} />;
  const showCards = cards.filter((r) => r._sig === 'bull' || r._sig === 'bear' || r._sig === 'watch');
  const cardsEmpty = counts.bull + counts.bear + counts.watch === 0;

  /* -- layout directions -- */
  let layout;
  if (dir === 'A') {
    layout =
    <React.Fragment>
        {summary}
        <RadarSection radarRows={radarRows} hl={hl} thr={thr} meta={meta} />
        <CardsSection cards={cardsEmpty ? [] : showCards} hl={hl} cols={2} />
        <TableSection rows={catRows} hl={hl} thr={thr} />
        <MethodFooter meta={meta} />
      </React.Fragment>;

  } else if (dir === 'B') {
    layout =
    <React.Fragment>
        {summary}
        <div className="dir-b-grid">
          <div className="dir-b-left"><RadarSection radarRows={radarRows} hl={hl} thr={thr} meta={meta} /></div>
          <div className="dir-b-right"><CardsSection cards={cardsEmpty ? [] : showCards} hl={hl} cols={1} title="QQQ signal feed" /></div>
        </div>
        <TableSection rows={catRows} hl={hl} thr={thr} />
        <MethodFooter meta={meta} />
      </React.Fragment>;

  }

  return (
    <React.Fragment>
      <div className="nav" role="navigation">
        <div className="nav-inner">
          <a className="logo" href="index.html" aria-label="18% Club home">
            <img className="logo-mark" src="assets/logo/18club-logo.png" alt="18% Club" width="40" height="40" />
            <span className="logo-wm">
              <span className="wm-main">18% Club</span>
              <span className="wm-sub">trade desk</span>
            </span>
          </a>
          <nav className="nav-links" aria-label="Primary">
            <a className="nav-link" href="index.html">Trade Desk</a>
            <a className="nav-link" href="dashboard.html?from=radar">Performance Dashboard</a>
            <a className="nav-link" href="structural-bias.html">Structural Bias</a>
            <a className="nav-link active" href="market-sensing.html">Market Sensing</a>
            <a className="nav-link" href="rulebook.html">Rulebook</a>
            <a className="nav-link" href="indicator.html">Indicator</a>
          </nav>
          <a className="nav-discord" href="https://discord.gg/sbpjpCBbFv" target="_blank" rel="noopener noreferrer">
            <div className="nav-discord-copy">
              <span className="nav-discord-eb">in the discord · live</span>
              <span className="nav-discord-h">Today · <em>running live</em></span>
            </div>
            <span className="nav-discord-btn">Join Free →</span>
          </a>
        </div>
      </div>
    <div className="radar-page">
      <div className="radar-shell">
        <div className="radar-head">
          <a className="rh-back" href="index.html"><span className="arr">←</span> Back to the trade desk</a>
          <h1 className="rh-h">Market <em>Sensing</em></h1>
          <p className="rh-lead">Before every open we scan <b>{meta.n} instruments</b> and flag each one as <b>bullish</b>, <b>bearish</b>, or <b>on watch</b> for QQQ -- read against the asset's natural polarity, not just the raw coupling. Built for the morning plan, not the tick.</p>
        </div>
        {ctl}
        {layout}
        <div className="footer footer-simple" style={{ background: 'var(--navy-2)', borderTop: '1px solid rgba(255,255,255,0.06)' }}>
          <img className="footer-logo" src="assets/logo/18club-logo.png" alt="18% Club" width="36" height="36" />
          <span className="footer-copy" style={{ color: 'rgba(149,169,171,0.7)' }}>© 2026 18% Club · All rights reserved. Education only -- not financial advice. Operated from Singapore. Past performance is not indicative of future results.</span>
        </div>
      </div>
    </div>
    </React.Fragment>);

}

ReactDOM.createRoot(document.getElementById('root')).render(<RadarApp />);
