/* ═══════════════════════════════════════════════════════════════════
   APP · Shadow Terminal Dashboard — wired to live Railway API
   ═══════════════════════════════════════════════════════════════════ */

ST_fmt.price = (n) => {
  if (n == null) return '—';
  const a = Math.abs(n);
  if (a < 0.0001) return n.toExponential(3);
  if (a < 0.01)   return n.toFixed(6);
  if (a < 1)      return n.toFixed(4);
  if (a < 100)    return n.toFixed(2);
  if (a < 10000)  return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
  return n.toLocaleString('en-US', { maximumFractionDigits: 0 });
};

function genCandlesScaled(count, seed, basePrice, volPct = 0.16) {
  const rng = ST_rng(seed);
  let price = basePrice * (1 - volPct * 0.55);
  const out = [];
  for (let i = 0; i < count; i++) {
    const env = 0.93 + 0.07 * (i / count);
    const wave = (Math.sin(i / 13) * 0.5 + Math.sin(i / 5.3) * 0.16) * volPct;
    const target = basePrice * env * (1 + wave);
    const open = price;
    const close = open + (target - open) * 0.20 + (rng() - 0.5) * basePrice * volPct * 0.05;
    const hi = Math.max(open, close) + rng() * basePrice * volPct * 0.035;
    const lo = Math.min(open, close) - rng() * basePrice * volPct * 0.035;
    const vol = (0.5 + rng()) * (Math.abs(close - open) / basePrice * 40 + 0.4);
    out.push({ o: open, h: hi, l: lo, c: close, v: vol, t: i });
    price = close;
  }
  const last = out[out.length - 1];
  last.c = basePrice; last.h = Math.max(last.h, basePrice); last.l = Math.min(last.l, basePrice);
  return out;
}

const COIN_GROUP_MAP = {
  BTC:'institutional',ETH:'institutional',BNB:'institutional',XRP:'institutional',
  ADA:'institutional',TON:'institutional',MNT:'institutional',LTC:'institutional',
  SOL:'momentum',AVAX:'momentum',SUI:'momentum',NEAR:'momentum',APT:'momentum',SEI:'momentum',
  HYPE:'momentum',
  LINK:'defi',ATOM:'defi',ARB:'defi',OP:'defi',INJ:'defi',JUP:'defi',PYTH:'defi',DOT:'defi',
  AAVE:'defi',POL:'defi',
  DOGE:'meme',PEPE:'meme',WIF:'meme',BONK:'meme',POPCAT:'meme',SPX:'meme',
  USDC:'fee reserve',USDT:'fee reserve',
};

function normApiCoins(raw) {
  /* API wraps in {coins:[...], count:N} */
  const arr = Array.isArray(raw) ? raw : (Array.isArray(raw?.coins) ? raw.coins : []);
  return arr.map((c, idx) => ({
    sym:   c.symbol || c.sym  || '???',
    name:  c.full   || c.name || c.symbol || '???',
    price: parseFloat(c.price || 0),
    chg:   parseFloat(c.change_pct || 0),
    group: COIN_GROUP_MAP[c.symbol || c.sym] || 'momentum',
    vol:   parseFloat(c.volume_24h || c.vol || 0),
    rank:  idx + 1,
  }));
}

function normCandles(raw) {
  /* API wraps in {candles:[...]} */
  const arr = Array.isArray(raw) ? raw : (Array.isArray(raw?.candles) ? raw.candles : []);
  if (!arr.length) return null;
  return arr.map((c, i) => ({
    o: parseFloat(c.open  || c.o || 0),
    h: parseFloat(c.high  || c.h || 0),
    l: parseFloat(c.low   || c.l || 0),
    c: parseFloat(c.close || c.c || 0),
    v: parseFloat(c.volume|| c.v || 0),
    t: i,
  })).filter(c => c.h > 0);
}

function App() {
  const [activeSym, setActiveSym] = React.useState('BTC');
  const [coins, setCoins]         = React.useState(ST_COINS);
  const [intel, setIntel]         = React.useState(null);
  const [thoughts, setThoughts]   = React.useState([
    { time: '—', type: 'SCAN', message: 'Connecting to Shadow AI…', meta: '' },
  ]);
  const [movers, setMovers]       = React.useState([]);
  const [portfolioTotal, setPortfolioTotal] = React.useState(null);
  const [holdingsOpen, setHoldingsOpen]     = React.useState(false);
  const [holdingsTotal, setHoldingsTotal]   = React.useState(null);
  const [holdingsPnl, setHoldingsPnl]       = React.useState(null);
  const [orders, setOrders]       = React.useState([]);
  const [support, setSupport]     = React.useState([]);
  const [resist, setResist]       = React.useState([]);
  const [lows, setLows]           = React.useState([]);
  const [position, setPosition]   = React.useState(null);

  const coin = React.useMemo(() => coins.find(c => c.sym === activeSym) || coins[0] || ST_COINS[0], [coins, activeSym]);

  const FALLBACK = (sym, pr) => ({
    price: pr, chg: 0, high: pr*1.01, low: pr*0.98, vol: 1,
    avg24: pr*0.999, channel: [pr*0.9, pr*1.1],
  });
  const getPriceBase = (sym) => {
    /* Always prefer live coin data — the BASES below are only used
       on first render before the API has responded. */
    const live_c = coins.find(x => x.sym === sym);
    if (live_c && live_c.price > 0) return FALLBACK(sym, live_c.price);
    /* True first-render fallback (no live data yet) */
    const BASES = {
      BTC:{ price:104832.10, chg:0.42, high:105840, low:103200, vol:38.4, avg24:104394, channel:[101000,108000]},
      ETH:{ price:3284.50,  chg:1.15, high:3320,   low:3210,   vol:18.1, avg24:3247,   channel:[3140,3380]},
      SOL:{ price:86.24,    chg:2.74, high:87.42,  low:82.10,  vol:1.84, avg24:83.94,  channel:[80,92]},
    };
    if (BASES[sym]) return BASES[sym];
    return FALLBACK(sym, 100);
  };

  const [live, setLive] = React.useState(() => {
    const b = getPriceBase('BTC');
    return { price:b.price, chg:b.chg, chgAbs:b.price*b.chg/100, high:b.high, low:b.low, vol:b.vol, lastTs:'now' };
  });
  const [candles, setCandles] = React.useState(() => genCandlesScaled(96, 7, 104832.10));

  const ema21 = React.useMemo(() => ST_ema(candles, 21), [candles]);
  const ema55 = React.useMemo(() => ST_ema(candles, 55), [candles]);
  const vwap  = React.useMemo(() => ST_vwap(candles), [candles]);
  const rsi   = React.useMemo(() => ST_rsi(candles, 14), [candles]);

  /* ── 30s coin refresh ── */
  const fetchCoins = React.useCallback(async () => {
    try {
      const raw = await ST_fetch('/market/coins');
      /* API: {coins:[...], count:N} */
      const normalised = normApiCoins(raw);
      if (normalised.length > 0) {
        setCoins(normalised);
        /* update live price for current active coin from fresh API data */
        const fresh = normalised.find(c => c.sym === activeSym);
        if (fresh) {
          setLive(prev => ({
            ...prev,
            price:   fresh.price,
            chg:     fresh.chg,
            chgAbs:  fresh.price * fresh.chg / 100,
            high:    Math.max(prev.high || 0, fresh.price),
            low:     Math.min(prev.low  || fresh.price * 2, fresh.price),
            lastTs:  'now',
          }));
        }
      }
    } catch (_) {}
  }, [activeSym]);
  React.useEffect(() => { fetchCoins(); const id = setInterval(fetchCoins, 30000); return () => clearInterval(id); }, []);

  /* ── intel bar ── normalize nested API response to flat shape */
  React.useEffect(() => {
    ST_fetch('/market/intel').then(raw => {
      if (!raw) return;
      /* API returns: { btc_dominance:{value,label}, fear_greed:{value,label},
         macro:{weekly_risk,risk_label}, equities:{sp500,nasdaq,dxy},
         funding_bias:"BEARISH" } */
      const eq = raw.equities?.sp500?.label || 'FLAT';
      setIntel({
        fear_greed:    parseFloat(raw.fear_greed?.value    ?? 25)   || 25,
        btc_dominance: parseFloat(raw.btc_dominance?.value ?? 58.2) || 58.2,
        macro_risk:    parseFloat(raw.macro?.weekly_risk   ?? 42)   || 42,
        equity_outlook: eq === 'BULL' || eq === 'BULLISH' ? 'BULLISH'
                      : eq === 'BEAR' || eq === 'BEARISH' ? 'BEARISH' : 'NEUTRAL',
        equity_pct:   null,
        funding_rate: raw.funding_bias === 'BEARISH' ? -0.0010
                    : raw.funding_bias === 'BULLISH' ? +0.0042 : 0.0002,
        regime: raw.btc_dominance?.label === 'BTC_STRONG' ? 'BEARISH'
              : raw.btc_dominance?.label === 'BTC_WEAK'   ? 'BULLISH' : 'NEUTRAL',
        regime_flip_note: 'awaiting BTC structure change',
      });
    }).catch(() => {});
  }, []);

  /* ── portfolio total (Bybit, if reachable) ── */
  React.useEffect(() => {
    ST_fetch('/health')
      .then(h => {
        /* API: {status, bybit_total, live_prices, feed_active} */
        const total = parseFloat(h.bybit_total || h.portfolio_total || h.total_value || 0);
        setPortfolioTotal(total > 0 ? total : null);
      }).catch(() => {});
  }, []);

  /* ── my holdings total (manual, API-independent) ── */
  const fetchHoldings = React.useCallback(() => {
    ST_fetch('/market/assets').then(d => {
      setHoldingsTotal(parseFloat(d.total_value || 0) || null);
      setHoldingsPnl(d.total_value > 0 ? parseFloat(d.total_pnl_pct || 0) : null);
    }).catch(() => {});
  }, []);
  React.useEffect(() => { fetchHoldings(); }, [fetchHoldings]);

  /* ── thoughts: use shadow/interesting coins + SSE stream ── */
  React.useEffect(() => {
    let evtSrc = null;

    /* Seed thought stream from interesting coins (Shadow's observations) */
    ST_fetch('/shadow/interesting').then(raw => {
      const coinArr = Array.isArray(raw) ? raw : (Array.isArray(raw?.coins) ? raw.coins : []);
      if (coinArr.length > 0) {
        const now = new Date();
        const fmt = (d) => d.toISOString().slice(11,19);
        const events = coinArr.slice(0,8).map((c, i) => {
          const sym = c.symbol || c.sym || '?';
          const dir = c.momentum?.direction || 'FLAT';
          const pct = parseFloat(c.change_pct || 0);
          const t = new Date(now - i * 60000);
          const type = Math.abs(pct) > 3 ? 'SIGNAL' : dir !== 'FLAT' ? 'HYPOTHESIS' : 'SCAN';
          const msg = `${sym} ${pct >= 0 ? '+' : ''}${pct.toFixed(2)}% · momentum ${dir} · vol ${c.momentum?.vol_change >= 0 ? '+' : ''}${parseFloat(c.momentum?.vol_change || 0).toFixed(1)}%`;
          return { time: fmt(t), type, message: msg, meta: `${sym} · live scan` };
        });
        setThoughts(events);
      }
    }).catch(() => {});

    /* SSE live stream */
    try {
      evtSrc = new EventSource(window.ST_API + '/shadow/thoughts');
      evtSrc.onmessage = (evt) => {
        try {
          const e = JSON.parse(evt.data);
          setThoughts(prev => [{
            time:    e.timestamp || e.time || new Date().toISOString().slice(11,19),
            type:    (e.type || 'SCAN').toUpperCase(),
            message: e.message || e.msg || '',
            meta:    e.meta || '',
          }, ...prev].slice(0, 60));
        } catch (_) {}
      };
      evtSrc.onerror = () => evtSrc.close();
    } catch (_) {}
    return () => { if (evtSrc) evtSrc.close(); };
  }, []);

  /* ── movers from predictions + top movers by change ── */
  React.useEffect(() => {
    ST_fetch('/shadow/predictions').then(raw => {
      /* API: {predictions:[], stats:{}, count:0} */
      const predsArr = Array.isArray(raw) ? raw : (Array.isArray(raw?.predictions) ? raw.predictions : []);
      const coinMap = {};
      coins.forEach(c => { coinMap[c.sym] = c; });

      if (predsArr.length > 0) {
        setMovers(predsArr.slice(0,4).map(p => {
          const c = coinMap[p.sym || p.symbol] || {};
          return { sym: p.sym||p.symbol, price: c.price||parseFloat(p.entry||0), chg: c.chg||0, group: c.group||'momentum', signal: p.dir||'LONG', conf: parseFloat(p.confidence||p.conf||0) };
        }));
      } else {
        /* Fallback: top movers by absolute % change from coins list */
        const sorted = [...coins].sort((a,b) => Math.abs(b.chg) - Math.abs(a.chg)).slice(0,4);
        setMovers(sorted.map(c => ({ ...c, signal: null, conf: 0 })));
      }
    }).catch(() => {
      /* Fallback movers from coins */
      const sorted = [...coins].sort((a,b) => Math.abs(b.chg) - Math.abs(a.chg)).slice(0,4);
      setMovers(sorted.map(c => ({ ...c, signal: null, conf: 0 })));
    });
  }, [coins]);

  /* ── per-coin data on symbol change ── */
  React.useEffect(() => {
    const b = getPriceBase(activeSym);
    setLive({ price:b.price, chg:b.chg, chgAbs:b.price*b.chg/100, high:b.high, low:b.low, vol:b.vol, lastTs:'now' });
    setCandles(genCandlesScaled(96, activeSym.charCodeAt(0) + activeSym.length * 7, b.price));
    setOrders([]); setSupport([]); setResist([]); setLows([]); setPosition(null);

    Promise.allSettled([
      ST_fetch(`/market/candles?symbol=${activeSym}&interval=D&limit=200`),
      ST_fetch(`/market/orders?symbol=${activeSym}`),
      ST_fetch(`/market/balance/${activeSym}`),
      ST_fetch(`/market/lows?symbol=${activeSym}`),
      ST_fetch(`/shadow/snapshot/${activeSym}`),
    ]).then(([candR, ordR, balR, lowR, snapR]) => {
      /* candles: API → {candles:[]} */
      if (candR.status==='fulfilled') { const nc = normCandles(candR.value); if (nc?.length) setCandles(nc); }

      /* orders: API → {orders:[], count:N} */
      if (ordR.status==='fulfilled') {
        const ordArr = Array.isArray(ordR.value) ? ordR.value : (ordR.value?.orders || []);
        setOrders(ordArr.map(o => ({
          price:   parseFloat(o.price || o.order_price || 0),
          qty:     parseFloat(o.qty   || o.order_qty   || o.quantity || 0),
          verdict: o.verdict || 'AMBER',
        })).filter(o => o.price > 0));
      }

      /* balance: API → {symbol, qty, usdt_value, avg_cost, current_price, has_position} */
      if (balR.status==='fulfilled' && balR.value) {
        const qty = parseFloat(balR.value.qty || 0);
        if (qty > 0) setPosition({ qty, avg: parseFloat(balR.value.avg_cost || 0) });
      }

      /* lows: API → {symbol, interval, lows:[{price, date_str, days_ago}]}.
         pct ("from now") is computed in the component against the LIVE price —
         not here — so it never uses a stale/synthetic price (was showing wrong %). */
      if (lowR.status==='fulfilled') {
        const lowArr = lowR.value?.lows || lowR.value || [];
        if (Array.isArray(lowArr) && lowArr.length) {
          setLows(lowArr.slice(0,5).map(l => ({
            p: parseFloat(l.price || 0),
            d: l.date_str || l.date || '—',
          })));
        }
      }

      /* snapshot: API → {symbol, snapshot:{...}} */
      if (snapR.status==='fulfilled' && snapR.value) {
        const s = snapR.value?.snapshot || snapR.value;
        if (Array.isArray(s.support_zones))    setSupport(s.support_zones.map(z => ({ lo: z.lo||z.low, hi: z.hi||z.high })));
        if (Array.isArray(s.resistance_zones)) setResist(s.resistance_zones.map(z => ({ lo: z.lo||z.low, hi: z.hi||z.high })));
      }
    });
  }, [activeSym]);

  /* ── live timestamp updater (lightweight — no SVG repaint) ── */
  React.useEffect(() => {
    const start = Date.now();
    const id = setInterval(() => {
      const elapsed = Math.floor((Date.now() - start) / 1000);
      setLive(prev => ({
        ...prev,
        lastTs: elapsed < 60 ? `${elapsed}s ago` : `${Math.floor(elapsed / 60)}m ago`,
      }));
    }, 10000); /* every 10s — just updates the timestamp label, not the price */
    return () => clearInterval(id);
  }, [activeSym]);

  return (
    <div style={{ display:'flex', height:'100vh', minHeight:0, overflow:'hidden' }}>
      <CoinRail activeSym={activeSym} onSelect={setActiveSym} coins={coins}
        portfolioTotal={portfolioTotal != null ? portfolioTotal : holdingsTotal}
        portfolioPnl={holdingsPnl}
        onEditHoldings={() => setHoldingsOpen(true)} />
      <main style={{ flex:1, display:'flex', flexDirection:'column', minWidth:0, minHeight:0, overflow:'hidden' }}>
        <IntelBar intel={intel} />
        <MarketClock />
        <div style={{ flex:1, display:'flex', flexDirection:'column', gap:10, padding:10, minHeight:0, overflow:'hidden' }}>
          <HeroChart coin={coin} live={live} candles={candles}
            ema21={ema21} ema55={ema55} vwap={vwap} rsi={rsi}
            orders={orders} supportZones={support} resistanceZones={resist} />
          <DashBottomStrip coin={coin} currentPrice={live.price} lows={lows} position={position} />
        </div>
      </main>
      <ShadowPanel thoughts={thoughts} movers={movers} intel={intel} />
      <HoldingsModal open={holdingsOpen} onClose={() => setHoldingsOpen(false)} coins={coins} onSaved={fetchHoldings} />
    </div>
  );
}

function DashBottomStrip({ coin, currentPrice, lows, position }) {
  if (!lows || !lows.length) {
    return (
      <div style={{ padding:'12px 16px', background:ST.bg2, border:`1px solid ${ST.border}`, borderRadius:10, flexShrink:0 }}>
        <span className="eyebrow">SWING LOWS · {coin.sym}</span>
        <div className="mono" style={{ fontSize:11, color:ST.ink4, marginTop:6 }}>Shadow is scanning for historical lows…</div>
      </div>
    );
  }
  return (
    <div style={{ display:'grid', gridTemplateColumns:'1fr 280px', gap:10, flexShrink:0 }}>
      <div style={{ padding:'12px 16px', background:ST.bg2, border:`1px solid ${ST.border}`, borderRadius:10 }}>
        <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between' }}>
          <span className="eyebrow">SWING LOWS · {coin.sym} · LAST 60D</span>
          <span className="mono" style={{ fontSize:9, color:ST.ink4 }}>Shadow watches as support levels</span>
        </div>
        <div style={{ display:'grid', gridTemplateColumns:`repeat(${lows.length},1fr)`, gap:12, marginTop:8 }}>
          {lows.map((l, i) => {
            const last = i===lows.length-1;
            const c = last ? ST.amber : ST.ink2;
            const pct = (currentPrice > 0 && l.p > 0) ? ((l.p - currentPrice) / currentPrice * 100) : 0;
            return (
              <div key={i} style={{ display:'flex', flexDirection:'column', gap:2, paddingLeft:i?12:0, borderLeft:i?`1px solid ${ST.border}`:'none' }}>
                <span className="mono" style={{ fontSize:9, color:ST.violet, fontWeight:600, letterSpacing:'0.08em' }}>{l.d}</span>
                <span className="num" style={{ fontSize:15, color:c, fontWeight:last?600:500 }}>${l.p.toLocaleString('en-US',{maximumFractionDigits:0})}</span>
                <span className="mono" style={{ fontSize:9, color: pct < 0 ? ST.red : ST.green, fontWeight:500 }}>{pct >= 0 ? '+' : ''}{pct.toFixed(1)}% from now</span>
              </div>
            );
          })}
        </div>
      </div>
      {position && (() => {
        const value = position.qty * currentPrice;
        const pct   = ((currentPrice - position.avg) / position.avg) * 100;
        return (
          <div style={{ padding:'12px 14px', background:ST.bg2, border:`1px solid ${ST.border}`, borderRadius:10 }}>
            <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between' }}>
              <span className="eyebrow">MY POSITION · {coin.sym}</span>
              <span className="mono" style={{ fontSize:9, fontWeight:700, color:ST.green, background:ST.greenFog, padding:'2px 5px', borderRadius:2, border:`1px solid ${ST.green}40` }}>SPOT</span>
            </div>
            <div style={{ display:'flex', alignItems:'baseline', gap:10, marginTop:6 }}>
              <span className="num" style={{ fontSize:18, fontWeight:500, color:ST.ink }}>${ST_fmt.price(value)}</span>
              <span className="num" style={{ fontSize:12, fontWeight:600, color:pct>=0?ST.green:ST.red }}>{pct>=0?'+':''}{pct.toFixed(1)}%</span>
            </div>
            <div style={{ display:'flex', justifyContent:'space-between', marginTop:4 }}>
              <span className="mono" style={{ fontSize:9, color:ST.ink4 }}>qty <span className="num" style={{ color:ST.ink2 }}>{position.qty.toFixed(4)}</span></span>
              <span className="mono" style={{ fontSize:9, color:ST.ink4 }}>avg <span className="num" style={{ color:ST.ink2 }}>${position.avg.toLocaleString('en-US')}</span></span>
            </div>
          </div>
        );
      })()}
    </div>
  );
}

window.App = App;
