/* src/v2/primitives.jsx — shared React hooks + formatters used across v2/v3.
   Relies on window.TMT_API (src/shared/api.js) and BY_SYM (src/v2/data.jsx). */

const { useState: _useS, useEffect: _useE, useRef: _useR } = React;

// ---------- Formatters ----------
function fmtPx(n) {
  if (n == null || isNaN(n)) return '—';
  const abs = Math.abs(n);
  if (abs >= 10000) return n.toLocaleString('en-US', { maximumFractionDigits: 0 });
  if (abs >= 1000)  return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  if (abs >= 100)   return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  if (abs >= 1)     return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  return n.toLocaleString('en-US', { minimumFractionDigits: 4, maximumFractionDigits: 4 });
}

function fmtPct(n) {
  if (n == null || isNaN(n)) return '—';
  const sign = n > 0 ? '+' : n < 0 ? '−' : '';
  return sign + Math.abs(n).toFixed(2) + '%';
}

function fmtNumCompact(n) {
  if (n == null || isNaN(n)) return '—';
  const abs = Math.abs(n);
  if (abs >= 1e12) return (n / 1e12).toFixed(2) + 'T';
  if (abs >= 1e9)  return (n / 1e9).toFixed(2)  + 'B';
  if (abs >= 1e6)  return (n / 1e6).toFixed(2)  + 'M';
  if (abs >= 1e3)  return (n / 1e3).toFixed(1)  + 'K';
  return String(Math.round(n));
}

function fmtClock(d) {
  if (!(d instanceof Date)) d = new Date();
  const h = String(d.getHours()).padStart(2, '0');
  const m = String(d.getMinutes()).padStart(2, '0');
  const s = String(d.getSeconds()).padStart(2, '0');
  return h + ':' + m + ':' + s;
}

// ---------- Hooks ----------
function useClock(intervalMs) {
  const [now, setNow] = _useS(() => new Date());
  _useE(() => {
    const id = setInterval(() => setNow(new Date()), intervalMs || 1000);
    return () => clearInterval(id);
  }, [intervalMs]);
  return now;
}

// Map Yahoo symbols → their API representation for /batch-quotes and /yahoo-quote.
// Yahoo uses caret-prefixed indices: ^GSPC ^NDX ^DJI ^RUT ^VIX ; crypto = BTC-USD.
const SYM_TO_YAHOO = {
  SPX: '^GSPC',
  NDX: '^NDX',
  DJI: '^DJI',
  RUT: '^RUT',
  VIX: '^VIX',
  DXY: 'DX-Y.NYB',
  XAU: 'GC=F',
  BTC: 'BTC-USD',
};
const YAHOO_TO_SYM = Object.keys(SYM_TO_YAHOO).reduce((m, k) => { m[SYM_TO_YAHOO[k]] = k; return m; }, {});

function toYahoo(sym) { return SYM_TO_YAHOO[sym] || sym; }
function fromYahoo(y) { return YAHOO_TO_SYM[y] || y; }

/**
 * useLivePrices — polls /batch-quotes every `intervalMs` for the given seed rows.
 * Returns a map: { [sym]: { sym, px, ch, chPct } }.
 * On failure, keeps last good values; never crashes the UI.
 */
function useLivePrices(seedRows, intervalMs) {
  const [prices, setPrices] = _useS(() => {
    const m = {};
    (seedRows || []).forEach(r => { m[r.sym] = { sym: r.sym, px: r.px, ch: r.ch, chPct: r.chPct }; });
    return m;
  });
  const lastGood = _useR(prices);

  _useE(() => {
    if (!window.TMT_API) return;
    const symbols = (seedRows || []).map(r => r.sym);
    if (symbols.length === 0) return;
    const yahooSymbols = symbols.map(toYahoo).join(',');

    let aborted = false;
    const poll = async () => {
      try {
        const data = await window.TMT_API.getJSON('/batch-quotes?symbols=' + encodeURIComponent(yahooSymbols));
        if (aborted) return;
        const quotes = (data && data.quotes) || [];
        const next = { ...lastGood.current };
        quotes.forEach(q => {
          const sym = fromYahoo(q.symbol) || q.symbol;
          if (!sym) return;
          const px = q.regularMarketPrice != null ? q.regularMarketPrice : q.price;
          const ch = q.regularMarketChange != null ? q.regularMarketChange : q.change;
          const chPct = q.regularMarketChangePercent != null ? q.regularMarketChangePercent : q.changePercent;
          if (px != null) next[sym] = { sym, px, ch: ch || 0, chPct: chPct || 0 };
        });
        lastGood.current = next;
        setPrices(next);
      } catch (e) {
        // Silently keep last good — don't flash error to UI
        if (!aborted) console.debug('[useLivePrices] poll failed', e.message || e);
      }
    };

    poll();
    const id = setInterval(poll, intervalMs || 20000);
    return () => { aborted = true; clearInterval(id); };
  // We intentionally depend only on the stable seed symbol list
  }, [JSON.stringify((seedRows || []).map(r => r.sym))]);

  return prices;
}

// ---------- Spark (tiny inline SVG sparkline) ----------
function Spark({ series, w, h, color, fill }) {
  if (!series || !series.length) return null;
  w = w || 80; h = h || 24;
  const vs = series.map(p => (typeof p === 'number' ? p : p.c));
  const mn = Math.min.apply(null, vs);
  const mx = Math.max.apply(null, vs);
  const rng = (mx - mn) || 1;
  const step = (w - 2) / (vs.length - 1);
  const path = vs.map((v, i) => (i === 0 ? 'M' : 'L') + (1 + i * step).toFixed(1) + ',' + ((h - 2) - ((v - mn) / rng) * (h - 4)).toFixed(1)).join(' ');
  const last = vs[vs.length - 1], first = vs[0];
  const up = last >= first;
  const stroke = color || (up ? 'var(--up)' : 'var(--down)');
  return (
    <svg width={w} height={h} viewBox={`0 0 ${w} ${h}`} style={{ display: 'block' }}>
      {fill && (
        <path d={path + ` L${(1 + (vs.length - 1) * step).toFixed(1)},${h - 1} L1,${h - 1} Z`} fill={stroke} opacity="0.12" />
      )}
      <path d={path} fill="none" stroke={stroke} strokeWidth="1.3" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

// ---------- Tiny helpers used by row components ----------
function tone(chPct) {
  if (chPct == null || isNaN(chPct)) return '';
  return chPct >= 0 ? 'up' : 'down';
}

function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); }

Object.assign(window, {
  fmtPx, fmtPct, fmtNumCompact, fmtClock,
  useClock, useLivePrices,
  toYahoo, fromYahoo,
  Spark, tone, clamp,
});
