// Verdict — live BTC chart (Polymarket-style: line + soft area, range tabs, hover crosshair).
//
// Pulls candleSnapshot for the chosen range to backfill, then appends live ticks from
// window.VDLive.state.btc.tail at the right edge. Renders a horizontal target line so the
// user can see where the binary settles relative to current price.

(function () {
  const { useState, useEffect, useRef, useMemo } = React;

  // Tick flash: wraps a numeric child, flashes mint/coral on change. Shared across live-* files.
  function FlashNum({ value, children, style, dim = 1 }) {
    const [flash, setFlash] = useState(null);
    const prev = useRef(value);
    useEffect(() => {
      if (value == null || prev.current == null) { prev.current = value; return; }
      if (value !== prev.current) {
        const dir = value > prev.current ? 'up' : 'down';
        setFlash(dir);
        prev.current = value;
        const t = setTimeout(() => setFlash(null), 700);
        return () => clearTimeout(t);
      }
    }, [value]);
    const accent = flash === 'up' ? '107,229,193' : flash === 'down' ? '255,92,110' : null;
    const base = {
      display: 'inline-block',
      transition: 'background 700ms var(--ease), box-shadow 700ms var(--ease), transform 200ms var(--ease)',
      padding: '0 4px', margin: '0 -4px', borderRadius: 'var(--r)',
      ...(accent ? {
        background: `rgba(${accent},${0.32 * dim})`,
        boxShadow: `0 0 16px rgba(${accent},${0.45 * dim})`,
        transform: 'translateY(-1px)',
      } : { background: 'transparent', boxShadow: 'none', transform: 'translateY(0)' }),
      ...style,
    };
    return <span style={base}>{children}</span>;
  }
  function TickArrow({ value }) {
    const [dir, setDir] = useState(null);
    const prev = useRef(value);
    useEffect(() => {
      if (value == null || prev.current == null) { prev.current = value; return; }
      if (value !== prev.current) {
        const d = value > prev.current ? 'up' : 'down';
        setDir(d);
        prev.current = value;
        const t = setTimeout(() => setDir(null), 700);
        return () => clearTimeout(t);
      }
    }, [value]);
    if (!dir) return <span style={{ display: 'inline-block', width: 12 }} />;
    return (
      <span style={{
        display: 'inline-block', width: 12,
        color: dir === 'up' ? 'var(--mint)' : 'var(--coral)',
        fontSize: 11, fontWeight: 700, opacity: 1,
        transition: 'opacity 700ms var(--ease)',
      }}>{dir === 'up' ? '▲' : '▼'}</span>
    );
  }
  window.VDFlash = { FlashNum, TickArrow };

  const RANGES = [
    // LIVE: pure tail buffer, no candle backfill — every BTC mid change is visible.
    { key: 'LIVE', interval: null, spanMs: 60 * 1000,            ticks: 5, live: true },
    { key: '1H',   interval: '1m',  spanMs: 1  * 60 * 60 * 1000, ticks: 6 },
    { key: '6H',   interval: '5m',  spanMs: 6  * 60 * 60 * 1000, ticks: 6 },
    { key: '1D',   interval: '15m', spanMs: 24 * 60 * 60 * 1000, ticks: 6 },
    { key: '1W',   interval: '1h',  spanMs: 7  * 24 * 60 * 60 * 1000, ticks: 7 },
  ];

  function fmtUsd0(v) { return v == null ? '—' : '$' + Math.round(v).toLocaleString(); }
  function fmtUsd2(v) { return v == null ? '—' : '$' + v.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }
  function fmtTimeShort(ts, span) {
    const d = new Date(ts);
    if (span <= 5 * 60 * 1000) {
      return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });
    }
    if (span <= 24 * 60 * 60 * 1000) {
      return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit', hour12: false });
    }
    return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
  }
  function fmtTimeFull(ts) {
    return new Date(ts).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false });
  }

  // ease-out-cubic — sharp arrival at the new price, decelerating
  function easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); }
  function currentTweenValue(tw, atNow) {
    if (tw.from == null || tw.to == null) return null;
    const t = Math.min(1, Math.max(0, (atNow - tw.startedAt) / tw.duration));
    return tw.from + (tw.to - tw.from) * easeOutCubic(t);
  }

  // Catmull-Rom-style smoothing → cubic bezier path for a {x,y} array.
  function smoothPath(pts) {
    if (pts.length === 0) return '';
    if (pts.length === 1) return `M ${pts[0].x} ${pts[0].y}`;
    let d = `M ${pts[0].x} ${pts[0].y}`;
    for (let i = 0; i < pts.length - 1; i++) {
      const p0 = pts[Math.max(0, i - 1)];
      const p1 = pts[i];
      const p2 = pts[i + 1];
      const p3 = pts[Math.min(pts.length - 1, i + 2)];
      const t = 0.18;
      const c1x = p1.x + (p2.x - p0.x) * t;
      const c1y = p1.y + (p2.y - p0.y) * t;
      const c2x = p2.x - (p3.x - p1.x) * t;
      const c2y = p2.y - (p3.y - p1.y) * t;
      d += ` C ${c1x.toFixed(2)} ${c1y.toFixed(2)}, ${c2x.toFixed(2)} ${c2y.toFixed(2)}, ${p2.x.toFixed(2)} ${p2.y.toFixed(2)}`;
    }
    return d;
  }

  function LiveBTCChart({ targetPrice, height = 280, compact = false }) {
    const [rangeKey, setRangeKey] = useState('LIVE');
    const [history, setHistory] = useState([]); // [{t,p}] from candles
    const [loading, setLoading] = useState(true);
    const [tail, setTail] = useState([]);       // live ticks since session start
    const [hoverIdx, setHoverIdx] = useState(null);
    const [size, setSize] = useState({ w: 800, h: height });
    const wrapRef = useRef(null);

    const range = RANGES.find(r => r.key === rangeKey) || RANGES[2];

    // Animated "now" — drives the chart's time axis to advance every frame so the live
    // tip glides smoothly to the right between ticks instead of waiting for them.
    const [now, setNow] = useState(Date.now());

    // Smoothly interpolate the displayed live price toward the latest WS tick.
    // Each new tick starts a 280ms ease-out from the currently-shown price to the new one,
    // so big tick jumps don't pop — they slide.
    const tweenRef = useRef({ from: null, to: null, startedAt: 0, duration: 280 });
    const lastTickPriceRef = useRef(null);

    useEffect(() => {
      if (!window.VDLive) return;
      return window.VDLive.subscribe((s) => setTail(s.btc.tail.slice()));
    }, []);

    // RAF loop: advance "now" + retick the tween. Runs while the chart is mounted.
    useEffect(() => {
      let raf = 0;
      const loop = () => {
        setNow(Date.now());
        raf = requestAnimationFrame(loop);
      };
      raf = requestAnimationFrame(loop);
      return () => cancelAnimationFrame(raf);
    }, []);

    useEffect(() => {
      // LIVE mode: skip candle backfill, draw purely from the live tail buffer.
      if (range.live) { setHistory([]); setLoading(false); return; }
      let cancel = false;
      setLoading(true);
      const end = Date.now();
      const start = end - range.spanMs;
      window.VDLive.getCandles('BTC', range.interval, start, end).then((cs) => {
        if (cancel) return;
        const pts = (cs || []).map(c => ({ t: c.t, p: +c.c }));
        setHistory(pts);
        setLoading(false);
      }).catch(() => { if (!cancel) setLoading(false); });
      return () => { cancel = true; };
    }, [rangeKey]);

    useEffect(() => {
      if (!wrapRef.current) return;
      const ro = new ResizeObserver((entries) => {
        const cr = entries[0].contentRect;
        setSize({ w: Math.max(320, Math.floor(cr.width)), h: height });
      });
      ro.observe(wrapRef.current);
      return () => ro.disconnect();
    }, [height]);

    // Latest real price (last live tick or last historical close)
    const latestRealPrice = tail.length ? tail[tail.length - 1].p
                          : (history.length ? history[history.length - 1].p : null);

    // When a new tick arrives, kick off a tween from the currently-displayed price
    if (latestRealPrice != null && lastTickPriceRef.current !== latestRealPrice) {
      const startFrom = tweenRef.current.to != null ? currentTweenValue(tweenRef.current, now) : latestRealPrice;
      tweenRef.current = { from: startFrom, to: latestRealPrice, startedAt: now, duration: 280 };
      lastTickPriceRef.current = latestRealPrice;
    }
    const animatedPrice = currentTweenValue(tweenRef.current, now) ?? latestRealPrice;

    // Merge history + live tail + the animated "live tip" point (always at xMax = now).
    const series = useMemo(() => {
      const cutoff = now - range.spanMs;
      const live = tail.filter(t => t.t >= cutoff);
      const lastHistT = history.length ? history[history.length - 1].t : 0;
      const liveTail = live.filter(t => t.t > lastHistT);
      const merged = history.concat(liveTail).filter(t => t.t >= cutoff);
      // Append a virtual point at "now" using the animated (eased) price so the
      // line tip glides instead of jumping. Drop any real tick at >= now (defensive).
      const filtered = merged.filter(p => p.t < now - 50);
      if (animatedPrice != null) filtered.push({ t: now, p: animatedPrice, virtual: true });
      return filtered;
    }, [history, tail, range.spanMs, now, animatedPrice]);

    const M = compact
      ? { l: 8,  r: 8,  t: 6,  b: 18 }
      : { l: 12, r: 56, t: 12, b: 28 };
    const W = size.w, H = size.h;
    const innerW = Math.max(10, W - M.l - M.r);
    const innerH = Math.max(10, H - M.t - M.b);

    const xMin = now - range.spanMs;
    const xMax = now;
    // Y-axis: include the target line in the visible range only when it's reasonably close
    // to the price action — otherwise a far-off target compresses the line into a flat strip.
    const priceYs = series.map(s => s.p);
    const priceMin = priceYs.length ? Math.min(...priceYs) : 0;
    const priceMax = priceYs.length ? Math.max(...priceYs) : 1;
    const priceRange = priceMax - priceMin;
    // Heuristic: include target only if it's within 1.5× the visible price range
    const includeTarget = targetPrice != null
      && targetPrice >= priceMin - priceRange * 1.5
      && targetPrice <= priceMax + priceRange * 1.5;
    const ys = includeTarget ? priceYs.concat([targetPrice]) : priceYs;
    const yMinR = Math.min(...ys);
    const yMaxR = Math.max(...ys);
    // Tighter padding — 4% in LIVE/short ranges so small ticks register big visually,
    // 8% in historical ranges where bigger swings exist. Floor on the pad so the line
    // doesn't slam into the top/bottom of the canvas.
    const padPct = range.live ? 0.04 : 0.08;
    const yPad = Math.max((yMaxR - yMinR) * padPct, yMaxR * 0.0001 || 0.5);
    const yMin = yMinR - yPad;
    const yMax = yMaxR + yPad;

    const xScale = (t) => M.l + ((t - xMin) / (xMax - xMin || 1)) * innerW;
    const yScale = (p) => M.t + (1 - (p - yMin) / (yMax - yMin || 1)) * innerH;

    const pts = series.map(s => ({ x: xScale(s.t), y: yScale(s.p) }));
    const linePath = smoothPath(pts);
    const areaPath = pts.length
      ? linePath + ` L ${pts[pts.length-1].x} ${M.t + innerH} L ${pts[0].x} ${M.t + innerH} Z`
      : '';

    const last = series[series.length - 1];
    const first = series[0];
    const change = last && first ? ((last.p - first.p) / first.p) * 100 : 0;
    const lineColor = change >= 0 ? 'var(--mint)' : 'var(--coral)';

    // Hover handling
    function onMove(e) {
      if (!series.length) return;
      const r = e.currentTarget.getBoundingClientRect();
      const mx = e.clientX - r.left;
      // Find nearest point by x
      let best = 0, bestDist = Infinity;
      for (let i = 0; i < pts.length; i++) {
        const d = Math.abs(pts[i].x - mx);
        if (d < bestDist) { best = i; bestDist = d; }
      }
      setHoverIdx(best);
    }
    function onLeave() { setHoverIdx(null); }

    const hover = hoverIdx != null && series[hoverIdx] ? { ...series[hoverIdx], x: pts[hoverIdx].x, y: pts[hoverIdx].y } : null;

    // X-axis tick positions — recomputed each render so they stay anchored to "now".
    const xTicks = (() => {
      const n = range.ticks;
      const out = [];
      for (let i = 0; i < n; i++) {
        const t = xMin + (i / (n - 1)) * (xMax - xMin);
        out.push({ t, x: xScale(t) });
      }
      return out;
    })();

    // Y-axis labels (sparse)
    const yLabels = compact ? [] : [
      { p: yMaxR, label: fmtUsd0(yMaxR) },
      { p: yMinR, label: fmtUsd0(yMinR) },
      ...(targetPrice != null ? [{ p: targetPrice, label: 'Tgt ' + fmtUsd0(targetPrice), tone: 'acid' }] : []),
    ];

    return (
      <div ref={wrapRef} style={{ width: '100%' }}>
        {!compact && (
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
            <div style={{ display: 'flex', alignItems: 'baseline', gap: 12 }}>
              <span className="cap-sm" style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                ↳ BTC live (HL) <TickArrow value={last && last.p} />
              </span>
              <span className="num" style={{ fontSize: 18, fontWeight: 500 }}>
                <FlashNum value={last && last.p}>{fmtUsd2(last && last.p)}</FlashNum>
              </span>
              <span className="num" style={{ fontSize: 12, color: change >= 0 ? 'var(--mint)' : 'var(--coral)', fontWeight: 600 }}>
                {change >= 0 ? '+' : ''}{change.toFixed(2)}% · {rangeKey}
              </span>
            </div>
            <div style={{ display: 'flex', gap: 4, border: '1px solid var(--line-subtle)' }}>
              {RANGES.map(r => {
                const active = r.key === rangeKey;
                const isLive = r.live;
                return (
                  <button
                    key={r.key}
                    onClick={() => setRangeKey(r.key)}
                    className="cap-sm"
                    style={{
                      padding: '4px 10px',
                      background: active ? (isLive ? 'var(--mint-bg10)' : 'var(--acid-bg10)') : 'transparent',
                      color: active ? (isLive ? 'var(--mint)' : 'var(--acid)') : 'var(--text-3)',
                      borderRight: '1px solid var(--line-subtle)',
                      display: 'inline-flex', alignItems: 'center', gap: 5,
                    }}
                  >
                    {isLive && <span className="live-dot" style={{ width: 5, height: 5, borderRadius: '50%', background: active ? 'var(--mint)' : 'var(--text-3)', display: 'inline-block' }} />}
                    {r.key}
                  </button>
                );
              })}
            </div>
          </div>
        )}

        <div style={{ position: 'relative', width: '100%', height: H, overflow: 'hidden' }}>
          <svg width={W} height={H} style={{ display: 'block' }}>
            <defs>
              <linearGradient id="vd-area-grad" x1="0" y1="0" x2="0" y2="1">
                <stop offset="0%"   stopColor={change >= 0 ? '#6BE5C1' : '#FF5C6E'} stopOpacity="0.22" />
                <stop offset="100%" stopColor={change >= 0 ? '#6BE5C1' : '#FF5C6E'} stopOpacity="0" />
              </linearGradient>
            </defs>

            {/* Y grid lines */}
            {!compact && yLabels.map((yl, i) => (
              <g key={i}>
                <line x1={M.l} y1={yScale(yl.p)} x2={M.l + innerW} y2={yScale(yl.p)}
                      stroke={yl.tone === 'acid' ? 'var(--acid)' : 'var(--line-subtle)'}
                      strokeOpacity={yl.tone === 'acid' ? 0.45 : 0.6}
                      strokeDasharray={yl.tone === 'acid' ? '4 4' : '0'} />
                <text x={M.l + innerW + 6} y={yScale(yl.p)} dy="0.32em"
                      style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 10,
                        fill: yl.tone === 'acid' ? 'var(--acid)' : 'var(--text-3)' }}>
                  {yl.label}
                </text>
              </g>
            ))}

            {/* Horizontal "current price" guide line — visibly translates up/down with each tick */}
            {last && !compact && (
              <line x1={M.l} y1={pts[pts.length - 1].y}
                    x2={M.l + innerW} y2={pts[pts.length - 1].y}
                    stroke={lineColor} strokeOpacity="0.25" strokeDasharray="2 4" strokeWidth="1" />
            )}

            {/* Area + line */}
            {areaPath && <path d={areaPath} fill="url(#vd-area-grad)" />}
            {linePath && <path d={linePath} fill="none" stroke={lineColor} strokeWidth="1.6" strokeLinejoin="round" strokeLinecap="round" />}

            {/* Live "now" pulse — extra-prominent in LIVE mode */}
            {last && (
              <g>
                <circle cx={pts[pts.length - 1].x} cy={pts[pts.length - 1].y} r="6"
                        fill={lineColor} opacity="0.18">
                  <animate attributeName="r" values={range.live ? '6;14;6' : '4;9;4'} dur={range.live ? '1.4s' : '2.4s'} repeatCount="indefinite" />
                  <animate attributeName="opacity" values="0.55;0;0.55" dur={range.live ? '1.4s' : '2.4s'} repeatCount="indefinite" />
                </circle>
                <circle cx={pts[pts.length - 1].x} cy={pts[pts.length - 1].y} r={range.live ? '3.6' : '2.6'} fill={lineColor} />
              </g>
            )}

            {/* Right-edge floating price tag (rendered as part of SVG so it tracks y exactly) */}
            {last && !compact && (
              <g transform={`translate(${M.l + innerW + 6}, ${pts[pts.length - 1].y})`}>
                <rect x="0" y="-9" width="48" height="18" fill={lineColor} opacity="0.92" rx="2" />
                <text x="24" y="0" dy="0.32em" textAnchor="middle"
                      style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 10, fontWeight: 700, fill: 'var(--bg-base)' }}>
                  {fmtUsd0(last.p)}
                </text>
              </g>
            )}

            {/* X-axis ticks */}
            {!compact && xTicks.map((xt, i) => (
              <text key={i} x={xt.x} y={H - 6} textAnchor="middle"
                    style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 9.5, fill: 'var(--text-3)' }}>
                {fmtTimeShort(xt.t, range.spanMs)}
              </text>
            ))}

            {/* Hover crosshair */}
            {hover && (
              <g pointerEvents="none">
                <line x1={hover.x} y1={M.t} x2={hover.x} y2={M.t + innerH}
                      stroke="var(--text-2)" strokeOpacity="0.4" strokeDasharray="3 3" />
                <circle cx={hover.x} cy={hover.y} r="4" fill="var(--bg-base)" stroke={lineColor} strokeWidth="1.5" />
              </g>
            )}

            {/* Mouse capture overlay */}
            <rect x={M.l} y={M.t} width={innerW} height={innerH} fill="transparent"
                  onMouseMove={onMove} onMouseLeave={onLeave}
                  style={{ cursor: 'crosshair' }} />
          </svg>

          {/* Floating tooltip */}
          {hover && (
            <div style={{
              position: 'absolute',
              left: Math.max(8, Math.min(W - 168, hover.x + 10)),
              top:  Math.max(8, Math.min(H - 60, hover.y - 60)),
              padding: '8px 10px',
              background: 'var(--bg-elev-3)',
              border: '1px solid var(--line)',
              borderRadius: 'var(--r)',
              pointerEvents: 'none',
              minWidth: 150,
            }}>
              <div className="num" style={{ fontSize: 14, fontWeight: 600 }}>{fmtUsd2(hover.p)}</div>
              <div className="cap-sm" style={{ marginTop: 2 }}>{fmtTimeFull(hover.t)}</div>
              {targetPrice != null && (
                <div className="num" style={{ fontSize: 10, color: hover.p >= targetPrice ? 'var(--mint)' : 'var(--coral)', marginTop: 4 }}>
                  {hover.p >= targetPrice ? 'above' : 'below'} target by {Math.abs((hover.p - targetPrice) / targetPrice * 100).toFixed(2)}%
                </div>
              )}
            </div>
          )}

          {loading && (
            <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
                          color: 'var(--text-3)', fontSize: 11, letterSpacing: '0.1em', textTransform: 'uppercase' }}>
              loading {rangeKey}…
            </div>
          )}
        </div>
      </div>
    );
  }

  window.VDLiveChart = { LiveBTCChart };
})();
