;(function () {
/* TVChart — thin React wrapper around LightweightCharts.createChart.

   kind:
     - 'area' (default): line + filled area, takes data of {time, value}.
       Supports liveTick {time, value} for incremental updates.
     - 'candlestick': renders OHLC candles. We accept the same {time,value}
       data and group every CANDLE_GROUP adjacent points into one candle
       (open=first, close=last, high=max, low=min). liveTick is ignored
       (caller should setData a fresh array on each refresh).

   Props:
     - data:    array of { time, value }
     - liveTick: optional { time, value } — area only
     - color:    primary line/fill color (hex). Defaults to teal. Area only.
     - height:   chart height in px. Defaults to 80.
     - width:    chart width in px. Defaults to 100% of container.
     - kind:     'area' | 'candlestick'
*/

const CANDLE_GROUP = 4;

const TVChart = ({ data, ohlc, liveTick, color = '#00AFAB', height = 80, width = '100%', kind = 'area' }) => {
  const containerRef = React.useRef(null);
  const chartRef = React.useRef(null);
  const seriesRef = React.useRef(null);
  const seriesKindRef = React.useRef(kind);

  /* Re-create the chart when kind changes. */
  React.useEffect(() => {
    if (!containerRef.current || !window.LightweightCharts) return;
    const chart = window.LightweightCharts.createChart(containerRef.current, {
      width: containerRef.current.clientWidth,
      height: height,
      layout: {
        background: { color: 'transparent' },
        textColor: '#98A2B3',
        /* lightweight-charts v4.2.0+ exposes `attributionLogo` on the
           layout object. false hides the small TV watermark in the
           chart's bottom-left. If a future build drops the option it
           will be ignored harmlessly. */
        attributionLogo: false,
      },
      grid: { vertLines: { visible: false }, horzLines: { visible: false } },
      timeScale: { visible: false, borderVisible: false },
      rightPriceScale: { visible: false, borderVisible: false },
      handleScale: false,
      handleScroll: false,
      crosshair: { vertLine: { visible: false }, horzLine: { visible: false } },
    });

    let series;
    if (kind === 'candlestick') {
      series = chart.addCandlestickSeries({
        upColor: '#079455',
        downColor: '#F04438',
        borderUpColor: '#079455',
        borderDownColor: '#F04438',
        wickUpColor: '#079455',
        wickDownColor: '#F04438',
        priceLineVisible: false,
        lastValueVisible: false,
      });
    } else {
      series = chart.addAreaSeries({
        lineColor: color,
        topColor: hexToRgba(color, 0.28),
        bottomColor: hexToRgba(color, 0.0),
        lineWidth: 2,
        priceLineVisible: false,
        lastValueVisible: false,
      });
    }
    chartRef.current = chart;
    seriesRef.current = series;
    seriesKindRef.current = kind;

    /* Resize handler — use ResizeObserver for the container width. */
    const ro = new ResizeObserver(() => {
      if (containerRef.current) chart.applyOptions({ width: containerRef.current.clientWidth });
    });
    ro.observe(containerRef.current);
    return () => {
      ro.disconnect();
      try { chart.remove(); } catch (_) { /* already torn down */ }
      chartRef.current = null;
      seriesRef.current = null;
    };
  }, [kind]);

  /* Push the dataset whenever `data` or `ohlc` reference changes — branch
     on kind. After every setData, fitContent so candles fill the canvas
     instead of clustering to one side (the lib's default barSpacing
     leaves empty space when point count is small). */
  React.useEffect(() => {
    if (!seriesRef.current) return;
    if (seriesKindRef.current === 'candlestick') {
      let candles;
      if (Array.isArray(ohlc) && ohlc.length > 0) {
        /* Real OHLC array (e.g. from the dummy feed). Sanitize duplicates. */
        const seen = new Set();
        candles = [];
        for (const c of ohlc) {
          if (!c || c.time == null) continue;
          if (seen.has(c.time)) continue;
          seen.add(c.time);
          candles.push(c);
        }
        candles.sort((a, b) => a.time - b.time);
      } else if (Array.isArray(data)) {
        candles = pointsToCandles(data, CANDLE_GROUP);
      } else {
        return;
      }
      try {
        seriesRef.current.setData(candles);
        if (chartRef.current) chartRef.current.timeScale().fitContent();
      } catch (_) { /* ignore lib errors */ }
      return;
    }
    if (!Array.isArray(data)) return;
    /* Area: sanitise and push line points. */
    const seen = new Set();
    const sanitized = [];
    for (const p of data) {
      if (!p || p.time == null || p.value == null) continue;
      if (seen.has(p.time)) continue;
      seen.add(p.time);
      sanitized.push(p);
    }
    sanitized.sort((a, b) => a.time - b.time);
    try {
      seriesRef.current.setData(sanitized);
      if (chartRef.current) chartRef.current.timeScale().fitContent();
    } catch (_) { /* ignore */ }
  }, [data, ohlc]);

  /* Push individual ticks — area only. */
  React.useEffect(() => {
    if (!seriesRef.current || !liveTick || liveTick.time == null) return;
    if (seriesKindRef.current === 'candlestick') return;
    try { seriesRef.current.update(liveTick); } catch (_) { /* lib throws if time goes backwards — ignore */ }
  }, [liveTick]);

  /* Update color if it changes — area only (candle colors are fixed). */
  React.useEffect(() => {
    if (!seriesRef.current || seriesKindRef.current !== 'area') return;
    seriesRef.current.applyOptions({
      lineColor: color,
      topColor: hexToRgba(color, 0.28),
      bottomColor: hexToRgba(color, 0.0),
    });
  }, [color]);

  return <div ref={containerRef} style={{ width, height: `${height}px` }} />;
};

/* Group N adjacent line points into one OHLC candle. Open = first point,
   Close = last point, High/Low = max/min. Time = midpoint of the slice. */
function pointsToCandles(points, n) {
  /* First sanitise — drop bad rows, dedupe times. */
  const seen = new Set();
  const clean = [];
  for (const p of points) {
    if (!p || p.time == null || p.value == null) continue;
    if (seen.has(p.time)) continue;
    seen.add(p.time);
    clean.push(p);
  }
  clean.sort((a, b) => a.time - b.time);

  const candles = [];
  for (let i = 0; i < clean.length; i += n) {
    const slice = clean.slice(i, i + n);
    if (slice.length === 0) continue;
    const open = slice[0].value;
    const close = slice[slice.length - 1].value;
    let high = Math.max(open, close);
    let low = Math.min(open, close);
    for (const p of slice) {
      if (p.value > high) high = p.value;
      if (p.value < low) low = p.value;
    }
    /* Wick jitter — small extension above/below body so the candle has
       a real high/low spread even when the slice is a flat line.
       Deterministic per-candle via simple seeded hash so re-renders
       don't flicker. */
    const range = Math.max(high - low, Math.abs(open) * 0.0008);
    const seed = Math.abs(Math.floor(slice[0].time));
    const wickHi = ((seed * 9301 + 49297) % 233280) / 233280;
    const wickLo = ((seed * 9302 + 49297) % 233280) / 233280;
    high = high + range * 0.4 * wickHi;
    low = low - range * 0.4 * wickLo;
    /* Time = midpoint of the slice. */
    const time = slice[Math.floor(slice.length / 2)].time;
    candles.push({ time, open, high, low, close });
  }
  return candles;
}

/* Convert #RRGGBB to rgba(r,g,b,a). */
function hexToRgba(hex, a) {
  const m = /^#?([a-fA-F0-9]{6})$/.exec(hex);
  if (!m) return hex;
  const r = parseInt(m[1].slice(0, 2), 16);
  const g = parseInt(m[1].slice(2, 4), 16);
  const b = parseInt(m[1].slice(4, 6), 16);
  return `rgba(${r}, ${g}, ${b}, ${a})`;
}

Object.assign(window, { TVChart });
})();
