/* ============================================================
   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 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, cat, setCat, cats }) {
  return (
    <div className="ctl-rail">
      <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">
        <span className="ctl-lab">Class</span>
        <select className="cat-select" value={cat} onChange={(e) => setCat(e.target.value)}>
          <option value="all">All classes</option>
          {cats.map((c) => <option key={c} value={c}>{c}</option>)}
        </select>
      </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>);

}

/* -- per-class bull/bear balance ---------------------------- */
const POL_ORDER = [
{ key: 'pro', label: 'Pro-cyclical', sub: 'moves with QQQ' },
{ key: 'counter', label: 'Counter-cyclical', sub: 'vol · rates · dollar' },
{ key: 'haven', label: 'Defensive haven', sub: 'gold · defensives · income' },
{ key: 'cyclical_commod', label: 'Growth commodity', sub: 'moves with global growth' }];


function PolarityBalance({ classed }) {
  const stats = useMemo(() => {
    const m = {};
    POL_ORDER.forEach((p) => {m[p.key] = { n: 0, bull: 0, bear: 0, watch: 0 };});
    classed.forEach((r) => {
      const g = m[r._pol];if (!g) return;
      g.n++;
      if (r._sig === 'bull') g.bull++;else
      if (r._sig === 'bear') g.bear++;else
      if (r._sig === 'watch') g.watch++;
    });
    return m;
  }, [classed]);

  /* axis fixed at 100% of class on each side */
  const maxPct = 100;

  return (
    <div className="pol-bal">
      <div className="pol-bal-head">
        <div className="sum-l">Bull vs bear by class</div>
        <div className="pol-bal-axis"><span className="pba-bear">◄ bearish</span><span className="pba-scale">0–100% of class</span><span className="pba-bull">bullish ►</span></div>
      </div>
      <div className="pol-bal-rows">
        {POL_ORDER.map((p) => {
          const g = stats[p.key];
          const bullPct = g.n ? g.bull / g.n * 100 : 0;
          const bearPct = g.n ? g.bear / g.n * 100 : 0;
          const bullW = bullPct / maxPct * 50;
          const bearW = bearPct / maxPct * 50;
          return (
            <div className="pol-bal-row" key={p.key}>
              <div className="pol-bal-lab">
                <span className="pbl-name">{p.label}</span>
                <span className="pbl-n" title={`${g.n} instruments in class`}>{g.n}</span>
              </div>
              <div className="pbl-bearpct" title={`${g.bear} of ${g.n} bearish`}>{g.bear ? Math.round(bearPct) + '%' : '·'}</div>
              <div className="pol-bal-track">
                <div className="pol-bal-bear" style={{ width: bearW + '%' }} />
                <div className="pol-bal-center" />
                <div className="pol-bal-bull" style={{ width: bullW + '%' }} />
              </div>
              <div className="pbl-bullpct" title={`${g.bull} of ${g.n} bullish`}>{g.bull ? Math.round(bullPct) + '%' : '·'}</div>
            </div>);

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

}

/* -- summary strip ------------------------------------------ */
function SummaryStrip({ classed, counts, pressure }) {
  /* 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 (pressure.tilt >= 20) {
    regime = { t: 'Risk-on tilt', d: `impact-weighted signals lean bullish (tilt +${Math.round(pressure.tilt)})` };
  } else if (pressure.tilt <= -20) {
    regime = { t: 'Risk-off tilt', d: `impact-weighted signals lean bearish (tilt ${Math.round(pressure.tilt)})` };
  } else {
    regime = { t: 'Mixed · rotational', d: `no decisive tilt (${pressure.tilt > 0 ? '+' : ''}${Math.round(pressure.tilt)})` };
  }

  return (
    <React.Fragment>
    <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>
    <PolarityBalance classed={classed} />
    </React.Fragment>);

}

/* -- 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, thr }) {
  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} thr={thr} />)}
      </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(' / ') : '1 / 2 / 3 / 5'}-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 [hl, setHl] = useState('1');
  const [thr, setThr] = useState(0.35);
  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);}).
    catch((e) => setErr(e.message));
  }, []);

  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 = window.hlAt(r, hl);
    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]);

  /* impact-weighted aggregate -> Net QQQ pressure */
  const pressure = useMemo(() => {
    let bull = 0, bear = 0;
    classed.forEach((r) => {
      const c = window.contributionOf(r, r._sig, hl, thr);
      if (c > 0) bull += c;else if (c < 0) bear += -c;
    });
    const net = bull - bear;
    const denom = bull + bear;
    const tilt = denom ? net / denom * 100 : 0;
    return { bull: Math.round(bull), bear: Math.round(bear), net: Math.round(net), tilt };
  }, [classed, hl, thr]);

  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 || [1, 2, 3, 5]).map(String)} thr={thr} setThr={setThr}
  sortKey={sortKey} setSortKey={setSortKey} sigOn={sigOn} toggleSig={toggleSig} counts={counts} cat={cat} setCat={setCat} cats={cats} />;
  const summary = <SummaryStrip classed={classed} counts={counts} pressure={pressure} />;
  const showCards = cards.filter((r) => r._sig === 'bull' || r._sig === 'bear' || r._sig === 'watch');
  const cardsEmpty = counts.bull + counts.bear + counts.watch === 0;

  /* -- layout: Command Console (radar pinned left, signal feed right) -- */
  const 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" thr={thr} /></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="Tank’s Trading Desk home">
            <span className="logo-mark" aria-hidden="true">
              <svg viewBox="0 0 48 48" width="40" height="40"><rect width="48" height="48" rx="12" fill="#4B5E8E" /><rect x="13" y="11" width="22" height="4" rx="1.5" fill="#fff" /><rect x="19" y="18" width="10" height="17" rx="1.5" fill="#fff" /></svg>
            </span>
            <span className="logo-wm">
              <span className="wm-main"><span className="wm-light">Tank</span><span className="wm-dot" aria-hidden="true" />TradingDesk</span>
              <span className="wm-sub">process over prediction</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="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/tank-mark.svg" alt="Tank’s Trading Desk" width="36" height="36" />
          <span className="footer-copy" style={{ color: 'rgba(156,163,174,0.7)' }}>© 2026 Tank’s Trading Desk · 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 />);