/* ═══════════════════════════════════════════════════════════════════
   st-chart.jsx — SHARED PRO CHART (dashboard · coin · trade)  v3

   Rendering core = lightweight-charts (TradingView's open-source
   engine — the same candle DNA as Bybit's chart): native wheel zoom,
   drag pan, pixel-crisp candles, auto-scaling price axis, kinetic
   scroll. We add: working timeframes, EMA21/55 + VWAP overlays,
   volume pane, RSI pane (synced), S/R zones and order price-lines,
   live OHLC readout, 30s auto-refresh pinned to the live edge.

   Usage (unchanged from v2):
     <STProChart sym="BTC" orders={[]} supportZones={[]}
                 resistanceZones={[]} defaultTf="15m" showRSI={true} />
   Requires the lightweight-charts standalone script (see index.html).
   ═══════════════════════════════════════════════════════════════════ */

/* Timeframe presets = candle interval + how many bars to show on first paint.
   Fewer visible bars = bigger, taller candles — tuned for a Bybit-style zoom
   (15m is the default and is the most zoomed-in / readable). Order here is the
   order the buttons render in. */
const ST_TF = {
  '1m':  { iv: '1',   bars: 55 },    // ~55 min, big candles
  '15m': { iv: '15',  bars: 50 },    // ~12 h — super zoomed (default)
  '30m': { iv: '30',  bars: 56 },    // ~28 h
  '1h':  { iv: '60',  bars: 64 },    // ~2.7 d
  '4h':  { iv: '240', bars: 70 },    // ~11 d
  '1d':  { iv: 'D',   bars: 75 },    // ~2.5 mo
  '1w':  { iv: 'W',   bars: 60 },    // ~14 mo
  '1M':  { iv: 'M',   bars: 36 },    // 3 yr of monthly
};

function stPrecision(p) {
  const a = Math.abs(p || 1);
  if (a >= 1000) return { precision: 1, minMove: 0.1 };
  if (a >= 50)   return { precision: 2, minMove: 0.01 };
  if (a >= 1)    return { precision: 3, minMove: 0.001 };
  if (a >= 0.01) return { precision: 5, minMove: 0.00001 };
  return { precision: 8, minMove: 0.00000001 };
}

function stFmtPrice(p) {
  if (p == null || isNaN(p)) return '—';
  const a = Math.abs(p);
  if (a >= 1000) return p.toLocaleString('en-US', { maximumFractionDigits: 1 });
  if (a >= 50)   return p.toFixed(2);
  if (a >= 1)    return p.toFixed(3);
  if (a >= 0.01) return p.toFixed(5);
  return p.toPrecision(4);
}

function stEma(closes, period) {
  const k = 2 / (period + 1);
  let e = closes[0];
  return closes.map(c => (e = c * k + e * (1 - k)));
}

function stVwap(cs) {
  let pv = 0, vv = 0;
  return cs.map(c => {
    const t = (c.h + c.l + c.c) / 3;
    pv += t * c.v; vv += c.v;
    return vv ? pv / vv : c.c;
  });
}

function stRsi(closes, period = 14) {
  const out = [50];
  let ag = 0, al = 0;
  for (let i = 1; i < closes.length; i++) {
    const d = closes[i] - closes[i - 1];
    const g = Math.max(0, d), l = Math.max(0, -d);
    if (i <= period) {
      ag += g / period; al += l / period;
      out.push(i === period ? 100 - 100 / (1 + ag / (al || 1e-9)) : 50);
    } else {
      ag = (ag * (period - 1) + g) / period;
      al = (al * (period - 1) + l) / period;
      out.push(100 - 100 / (1 + ag / (al || 1e-9)));
    }
  }
  return out;
}

function STProChart({ sym, orders = [], supportZones = [], resistanceZones = [], defaultTf = '15m', showRSI = true }) {
  const [tf, setTf]     = React.useState(defaultTf);
  const [ohlc, setOhlc] = React.useState(null);   /* crosshair readout */
  const [meta, setMeta] = React.useState(null);   /* last bar + ema values for legend */
  const [err, setErr]   = React.useState(null);

  const hostRef = React.useRef(null);
  const rsiRef  = React.useRef(null);
  const chRef   = React.useRef({});               /* charts + series + data */

  /* ── build charts once per mount ── */
  React.useEffect(() => {
    if (!window.LightweightCharts || !hostRef.current) {
      setErr('chart engine failed to load');
      return;
    }
    const LW = window.LightweightCharts;
    const base = {
      layout: { background: { type: 'solid', color: 'transparent' },
                textColor: ST.ink2, fontFamily: 'JetBrains Mono, monospace', fontSize: 12 },
      grid: { vertLines: { color: ST.grid }, horzLines: { color: ST.grid } },
      rightPriceScale: { borderColor: ST.border,
                         scaleMargins: { top: 0.06, bottom: 0.18 } },
      timeScale: { borderColor: ST.border, timeVisible: true, secondsVisible: false,
                   rightOffset: 4, barSpacing: 16, minBarSpacing: 4,
                   /* stability: no scrolling/zooming into empty void on either
                      side (the "drifts left, blank on the right" bug), and the
                      view stays put when the panel resizes (rail collapse) */
                   fixLeftEdge: true, fixRightEdge: true,
                   lockVisibleTimeRangeOnResize: true, rightBarStaysOnScroll: true },
      crosshair: {
        mode: LW.CrosshairMode.Normal,
        /* time label stays neutral; the PRICE label (right axis) is amber so the
           number you're hovering pops off the dark candles instead of blending
           into the black/white background — lightweight-charts auto-picks dark
           text on the amber fill (= yellow chip, black numbers) */
        vertLine: { color: ST.ink3, width: 1, style: LW.LineStyle.Dashed, labelBackgroundColor: ST.panel },
        horzLine: { color: ST.amber, width: 1, style: LW.LineStyle.Dashed, labelBackgroundColor: ST.amber },
      },
      autoSize: true,
    };

    const chart = LW.createChart(hostRef.current, base);
    const candle = chart.addCandlestickSeries({
      upColor: ST.green, downColor: ST.red,
      wickUpColor: ST.green, wickDownColor: ST.red,
      borderVisible: false,
    });
    const volume = chart.addHistogramSeries({
      priceScaleId: 'vol', priceFormat: { type: 'volume' },
      lastValueVisible: false, priceLineVisible: false,
    });
    chart.priceScale('vol').applyOptions({ scaleMargins: { top: 0.84, bottom: 0 } });
    const mkLine = (color, width, style) => chart.addLineSeries({
      color, lineWidth: width, lineStyle: style || 0,
      priceLineVisible: false, lastValueVisible: false, crosshairMarkerVisible: false,
    });
    const ema21 = mkLine(ST.amber, 2);
    const ema55 = mkLine(ST.blue, 2);
    const vwap  = mkLine(ST.ink2, 1, 2);

    let rsiChart = null, rsiLine = null;
    if (showRSI && rsiRef.current) {
      rsiChart = LW.createChart(rsiRef.current, {
        ...base,
        rightPriceScale: { borderColor: ST.border,
          scaleMargins: { top: 0.12, bottom: 0.08 } },
        timeScale: { ...base.timeScale, visible: false },
      });
      rsiLine = rsiChart.addLineSeries({
        color: ST.amber, lineWidth: 1,
        priceLineVisible: false, lastValueVisible: true,
        priceFormat: { type: 'price', precision: 1, minMove: 0.1 },
      });
      rsiLine.createPriceLine({ price: 70, color: `${ST.red}80`, lineWidth: 1, lineStyle: LW.LineStyle.Dashed, title: '70' });
      rsiLine.createPriceLine({ price: 30, color: `${ST.green}80`, lineWidth: 1, lineStyle: LW.LineStyle.Dashed, title: '30' });
      /* keep the two panes locked together */
      let guard = false;
      const link = (a, b) => a.timeScale().subscribeVisibleLogicalRangeChange(r => {
        if (guard || !r) return;
        guard = true; b.timeScale().setVisibleLogicalRange(r); guard = false;
      });
      link(chart, rsiChart); link(rsiChart, chart);
    }

    /* Update the OHLC readout only when the hovered BAR changes — per-pixel
       setState re-rendered the toolbar ~60×/s and made the whole card flicker */
    let lastBarT = undefined;
    chart.subscribeCrosshairMove(p => {
      const d = p && p.seriesData ? p.seriesData.get(candle) : null;
      const t = d ? p.time : null;
      if (t === lastBarT) return;
      lastBarT = t;
      setOhlc(d ? { o: d.open, h: d.high, l: d.low, c: d.close, t } : null);
    });

    chRef.current = { LW, chart, candle, volume, ema21, ema55, vwap, rsiChart, rsiLine, priceLines: [] };
    return () => { chart.remove(); if (rsiChart) rsiChart.remove(); chRef.current = {}; };
  }, [showRSI]);

  /* ── data load (tf / sym change + 30s refresh) ── */
  const load = React.useCallback((quiet) => {
    const R = chRef.current;
    if (!R.chart) return;
    const preset = ST_TF[tf] || ST_TF['15m'];
    const limit  = Math.min(1000, preset.bars + 120);   // +warmup for EMA55
    ST_fetch(`/market/candles?symbol=${sym}&interval=${preset.iv}&limit=${limit}`)
      .then(d => {
        const cs = (d.candles || []).map(c => ({
          time: Math.floor(c.open_time / 1000),
          o: c.open, h: c.high, l: c.low, c: c.close, v: c.volume,
        }));
        if (!cs.length) { setErr('no data yet — syncing from Bybit, retry in a few sec'); return; }
        setErr(null);

        const closes = cs.map(c => c.c);
        const e21 = stEma(closes, 21), e55 = stEma(closes, 55), vw = stVwap(cs), rs = stRsi(closes);

        const stay = quiet ? R.chart.timeScale().getVisibleLogicalRange() : null;
        const pinned = !quiet || (R.chart.timeScale().scrollPosition() > -3);

        const prec = stPrecision(closes[closes.length - 1]);
        R.candle.applyOptions({ priceFormat: { type: 'price', ...prec } });
        R.candle.setData(cs.map(c => ({ time: c.time, open: c.o, high: c.h, low: c.l, close: c.c })));
        R.volume.setData(cs.map(c => ({ time: c.time, value: c.v, color: (c.c >= c.o ? ST.green : ST.red) + '4D' })));
        R.ema21.setData(cs.map((c, i) => ({ time: c.time, value: e21[i] })));
        R.ema55.setData(cs.map((c, i) => ({ time: c.time, value: e55[i] })));
        R.vwap.setData(cs.map((c, i) => ({ time: c.time, value: vw[i] })));
        if (R.rsiLine) R.rsiLine.setData(cs.map((c, i) => ({ time: c.time, value: rs[i] })));

        if (!quiet) {
          /* first paint: show exactly the preset's window — big readable candles */
          R.chart.timeScale().setVisibleLogicalRange(
            { from: Math.max(0, cs.length - preset.bars), to: cs.length + 2 });
        } else if (!pinned && stay) {
          R.chart.timeScale().setVisibleLogicalRange(stay);
        }

        const last = cs[cs.length - 1];
        setMeta({ last: last.c, up: last.c >= last.o,
                  e21: e21[e21.length - 1], e55: e55[e55.length - 1],
                  vw: vw[vw.length - 1], rsi: rs[rs.length - 1] });
      })
      .catch(e => setErr(String(e.message || e)));
  }, [sym, tf]);

  React.useEffect(() => { load(false); }, [load]);
  React.useEffect(() => { const t = setInterval(() => load(true), 30000); return () => clearInterval(t); }, [load]);

  /* ── premium watermark: faint pair name behind the candles ── */
  React.useEffect(() => {
    const R = chRef.current;
    if (!R.chart) return;
    R.chart.applyOptions({ watermark: {
      visible: true, text: `${sym}/USDT`, color: 'rgba(255,255,255,0.045)',
      fontSize: 46, fontFamily: 'Geist, sans-serif',
      horzAlign: 'center', vertAlign: 'center',
    }});
  }, [sym]);

  /* ── overlays: order lines + S/R zone lines ── */
  React.useEffect(() => {
    const R = chRef.current;
    if (!R.candle) return;
    R.priceLines.forEach(pl => { try { R.candle.removePriceLine(pl); } catch (e) {} });
    R.priceLines = [];
    const add = (price, color, title, style) => {
      if (price == null || isNaN(price)) return;
      R.priceLines.push(R.candle.createPriceLine({
        price, color, lineWidth: 1,
        lineStyle: style != null ? style : 2, title,
        axisLabelVisible: true,
      }));
    };
    orders.forEach(o => {
      const col = o.verdict === 'GOOD' ? ST.green : o.verdict === 'AVOID' ? ST.red : ST.amber;
      add(o.price, col, `BUY ${o.qty != null ? o.qty : ''}`);
    });
    supportZones.forEach(z => { add(z.lo, ST.green, 'SUP', 3); add(z.hi, ST.green, '', 3); });
    resistanceZones.forEach(z => { add(z.lo, ST.red, '', 3); add(z.hi, ST.red, 'RES', 3); });
  }, [orders, supportZones, resistanceZones, sym, tf]);

  /* ── UI ── */
  const TfBtn = t => (
    <button key={t} onClick={() => setTf(t)} className="mono" style={{
      padding: '4px 10px', fontSize: 11, fontWeight: 600,
      background: t === tf ? `${ST.amber}1A` : 'transparent',
      color: t === tf ? ST.amber : ST.ink3,
      border: 'none', cursor: 'pointer', borderRadius: 3,
    }}>{t}</button>
  );

  const Leg = ({ color, label, value, dash }) => (
    <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
      <svg width="12" height="6"><line x1="0" y1="3" x2="12" y2="3" stroke={color} strokeWidth="1.5" strokeDasharray={dash ? '2 2' : undefined} /></svg>
      <span className="mono" style={{ fontSize: 9.5, color: ST.ink3 }}>{label}</span>
      <span className="num" style={{ fontSize: 10, color, fontWeight: 600 }}>{value}</span>
    </div>
  );

  return (
    <div style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>
      {/* toolbar */}
      <div style={{ padding: '7px 12px', borderBottom: `1px solid ${ST.border}`,
        display: 'flex', alignItems: 'center', gap: 12, flexShrink: 0, background: ST.bg, flexWrap: 'wrap' }}>
        <div style={{ display: 'flex', gap: 2, padding: 2, background: ST.panel, borderRadius: 5, border: `1px solid ${ST.border}` }}>
          {Object.keys(ST_TF).map(TfBtn)}
        </div>
        <div style={{ width: 1, height: 16, background: ST.border }} />
        {meta && <Leg color={ST.amber} label="EMA21" value={stFmtPrice(meta.e21)} />}
        {meta && <Leg color={ST.blue}  label="EMA55" value={stFmtPrice(meta.e55)} />}
        {meta && <Leg color={ST.ink2}  label="VWAP"  value={stFmtPrice(meta.vw)} dash />}
        {/* fixed-width right block — swapping last↔OHLC must never reflow the
            toolbar. The readout sits in a constant-width, right-aligned slot so
            hovering (which swaps the short "last" line for the wider O/H/L/C
            line) can't change the wrap point and nudge the chart down. */}
        <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 10,
                      flexShrink: 0, justifyContent: 'flex-end', whiteSpace: 'nowrap' }}>
          <div style={{ minWidth: 250, textAlign: 'right' }}>
            {ohlc ? (
              <span className="mono" style={{ fontSize: 10, color: ST.ink3 }}>
                O <b style={{ color: ST.ink }}>{stFmtPrice(ohlc.o)}</b>{' '}
                H <b style={{ color: ST.green }}>{stFmtPrice(ohlc.h)}</b>{' '}
                L <b style={{ color: ST.red }}>{stFmtPrice(ohlc.l)}</b>{' '}
                C <b style={{ color: ohlc.c >= ohlc.o ? ST.green : ST.red }}>{stFmtPrice(ohlc.c)}</b>
              </span>
            ) : meta ? (
              <span className="mono" style={{ fontSize: 10, color: ST.ink3 }}>
                last <b style={{ color: meta.up ? ST.green : ST.red }}>{stFmtPrice(meta.last)}</b>
                {showRSI && <span> · RSI <b style={{ color: meta.rsi > 70 ? ST.red : meta.rsi < 30 ? ST.green : ST.ink }}>{meta.rsi.toFixed(1)}</b></span>}
              </span>
            ) : null}
          </div>
          <span className="mono" style={{ fontSize: 9, color: ST.ink4 }}>scroll = zoom · drag = pan</span>
        </div>
      </div>

      {/* price + volume pane */}
      <div ref={hostRef} style={{ flex: 1, minHeight: 0, position: 'relative' }}>
        {err && (
          <div className="mono" style={{ position: 'absolute', inset: 0, display: 'grid',
            placeItems: 'center', fontSize: 11, color: ST.ink3, zIndex: 5 }}>{err}</div>
        )}
      </div>

      {/* RSI pane */}
      {showRSI && (
        <div style={{ height: 92, flexShrink: 0, borderTop: `1px solid ${ST.border}`, position: 'relative' }}>
          <span className="mono" style={{ position: 'absolute', top: 4, left: 10, zIndex: 5,
            fontSize: 9, color: ST.amber, fontWeight: 700, letterSpacing: '0.08em' }}>RSI · 14</span>
          <div ref={rsiRef} style={{ position: 'absolute', inset: 0 }} />
        </div>
      )}
    </div>
  );
}

window.STProChart = STProChart;
