import { useEffect, useMemo, useRef } from 'react';
import {
  CandlestickSeries,
  ColorType,
  CrosshairMode,
  HistogramSeries,
  type IChartApi,
  type ISeriesApi,
  LineStyle,
  LineSeries,
  createChart,
  type UTCTimestamp,
  type CandlestickData,
  type HistogramData,
  type LineData,
  type WhitespaceData,
} from 'lightweight-charts';
import type { Candle, SeriesPoint } from '../../lib/api';
import { FibRetracementPrimitive, type FibAnchor, type FibRetracement } from './FibRetracementPrimitive';

type Props = {
  candles: Candle[];
  oracle?: SeriesPoint[];
  sma20?: SeriesPoint[];
  ema20?: SeriesPoint[];
  bb20?: { upper: SeriesPoint[]; lower: SeriesPoint[]; mid: SeriesPoint[] };
  showIndicators: boolean;
  bucketSeconds: number;
  seriesKey: string;
  fib?: FibRetracement | null;
  fibOpacity?: number;
  fibSelected?: boolean;
  priceAutoScale?: boolean;
  onReady?: (api: { chart: IChartApi; candles: ISeriesApi<'Candlestick', UTCTimestamp> }) => void;
  onChartClick?: (p: FibAnchor & { target: 'chart' | 'fib' }) => void;
  onChartCrosshairMove?: (p: FibAnchor) => void;
  onPointerEvent?: (p: {
    type: 'pointerdown' | 'pointermove' | 'pointerup' | 'pointercancel';
    logical: number;
    price: number;
    x: number;
    y: number;
    target: 'chart' | 'fib';
    event: PointerEvent;
  }) => { consume?: boolean; capturePointer?: boolean } | void;
};

type LinePoint = LineData | WhitespaceData;
type BuildSample = { t: number; v: number };

function toTime(t: number): UTCTimestamp {
  return t as UTCTimestamp;
}

function resolveBucketSeconds(bucketSeconds: number, candles: Candle[]): number {
  if (Number.isFinite(bucketSeconds) && bucketSeconds > 0) return bucketSeconds;
  if (candles.length >= 2) {
    const last = candles[candles.length - 1]?.time;
    const prev = candles[candles.length - 2]?.time;
    const delta = typeof last === 'number' && typeof prev === 'number' ? last - prev : 0;
    if (Number.isFinite(delta) && delta > 0) return delta;
  }
  return 60;
}

function samplePriceFromCandles(candles: Candle[]): number | null {
  for (let i = candles.length - 1; i >= 0; i -= 1) {
    const close = candles[i]?.close;
    if (typeof close !== 'number') continue;
    if (!Number.isFinite(close)) continue;
    if (close === 0) continue;
    return close;
  }
  return null;
}

function priceFormatForSample(price: number) {
  const abs = Math.abs(price);
  if (!Number.isFinite(abs) || abs === 0) return { type: 'price' as const, precision: 2, minMove: 0.01 };
  if (abs >= 1000) return { type: 'price' as const, precision: 0, minMove: 1 };
  if (abs >= 1) return { type: 'price' as const, precision: 2, minMove: 0.01 };
  const exponent = Math.floor(Math.log10(abs)); // negative for abs < 1
  const precision = Math.min(8, Math.max(4, -exponent + 3));
  return { type: 'price' as const, precision, minMove: Math.pow(10, -precision) };
}

function toCandleData(candles: Candle[]): CandlestickData[] {
  return candles.map((c) => ({
    time: toTime(c.time),
    open: c.open,
    high: c.high,
    low: c.low,
    close: c.close,
  }));
}

function toVolumeData(candles: Candle[]): HistogramData[] {
  return candles.map((c) => {
    return {
      time: toTime(c.time),
      value: c.volume ?? 0,
      color: 'rgba(148,163,184,0.22)',
    };
  });
}

function toLineSeries(points: SeriesPoint[] | undefined): LinePoint[] {
  if (!points?.length) return [];
  return points.map((p) => (p.value == null ? ({ time: toTime(p.time) } as WhitespaceData) : { time: toTime(p.time), value: p.value }));
}

export default function TradingChart({
  candles,
  oracle,
  sma20,
  ema20,
  bb20,
  showIndicators,
  bucketSeconds,
  seriesKey,
  fib,
  fibOpacity = 1,
  fibSelected = false,
  priceAutoScale = true,
  onReady,
  onChartClick,
  onChartCrosshairMove,
  onPointerEvent,
}: Props) {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const chartRef = useRef<IChartApi | null>(null);
  const fibPrimitiveRef = useRef<FibRetracementPrimitive | null>(null);
  const fibRef = useRef<FibRetracement | null>(fib ?? null);
  const fibOpacityRef = useRef<number>(fibOpacity);
  const priceAutoScaleRef = useRef<boolean>(priceAutoScale);
  const prevPriceAutoScaleRef = useRef<boolean>(priceAutoScale);
  const onReadyRef = useRef<Props['onReady']>(onReady);
  const onChartClickRef = useRef<Props['onChartClick']>(onChartClick);
  const onChartCrosshairMoveRef = useRef<Props['onChartCrosshairMove']>(onChartCrosshairMove);
  const onPointerEventRef = useRef<Props['onPointerEvent']>(onPointerEvent);
  const capturedOverlayPointerRef = useRef<number | null>(null);
  const buildSamplesRef = useRef<Map<number, BuildSample[]>>(new Map());
  const buildKeyRef = useRef<string | null>(null);
  const lastBuildCandleStartRef = useRef<number | null>(null);
  const seriesRef = useRef<{
    candles?: ISeriesApi<'Candlestick'>;
    volume?: ISeriesApi<'Histogram'>;
    build?: ISeriesApi<'Line'>;
    oracle?: ISeriesApi<'Line'>;
    sma20?: ISeriesApi<'Line'>;
    ema20?: ISeriesApi<'Line'>;
    bbUpper?: ISeriesApi<'Line'>;
    bbLower?: ISeriesApi<'Line'>;
    bbMid?: ISeriesApi<'Line'>;
  }>({});

  const candleData = useMemo(() => toCandleData(candles), [candles]);
  const volumeData = useMemo(() => toVolumeData(candles), [candles]);
  const oracleData = useMemo(() => toLineSeries(oracle), [oracle]);
  const smaData = useMemo(() => toLineSeries(sma20), [sma20]);
  const emaData = useMemo(() => toLineSeries(ema20), [ema20]);
  const bbUpper = useMemo(() => toLineSeries(bb20?.upper), [bb20?.upper]);
  const bbLower = useMemo(() => toLineSeries(bb20?.lower), [bb20?.lower]);
  const bbMid = useMemo(() => toLineSeries(bb20?.mid), [bb20?.mid]);
  const priceFormat = useMemo(() => {
    const sample = samplePriceFromCandles(candles);
    return sample == null ? { type: 'price' as const, precision: 2, minMove: 0.01 } : priceFormatForSample(sample);
  }, [candles]);

  useEffect(() => {
    onReadyRef.current = onReady;
  }, [onReady]);

  useEffect(() => {
    onChartClickRef.current = onChartClick;
  }, [onChartClick]);

  useEffect(() => {
    onChartCrosshairMoveRef.current = onChartCrosshairMove;
  }, [onChartCrosshairMove]);

  useEffect(() => {
    onPointerEventRef.current = onPointerEvent;
  }, [onPointerEvent]);

  useEffect(() => {
    fibRef.current = fib ?? null;
  }, [fib]);

  useEffect(() => {
    fibOpacityRef.current = fibOpacity;
    fibPrimitiveRef.current?.setOpacity(fibOpacity);
  }, [fibOpacity]);

  useEffect(() => {
    priceAutoScaleRef.current = priceAutoScale;
  }, [priceAutoScale]);

  useEffect(() => {
    if (!containerRef.current) return;
    if (chartRef.current) return;

    const chart = createChart(containerRef.current, {
      layout: {
        background: { type: ColorType.Solid, color: 'rgba(0,0,0,0)' },
        textColor: '#e6e9ef',
        fontFamily:
          'system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial, sans-serif',
      },
      grid: {
        vertLines: { color: 'rgba(255,255,255,0.06)' },
        horzLines: { color: 'rgba(255,255,255,0.06)' },
      },
      crosshair: {
        mode: CrosshairMode.Normal,
        vertLine: { color: 'rgba(255,255,255,0.18)', style: LineStyle.Dashed },
        horzLine: { color: 'rgba(255,255,255,0.18)', style: LineStyle.Dashed },
      },
      rightPriceScale: { borderColor: 'rgba(255,255,255,0.08)' },
      timeScale: { borderColor: 'rgba(255,255,255,0.08)', timeVisible: true, secondsVisible: false },
      handleScale: { mouseWheel: true, pinch: true },
      handleScroll: { mouseWheel: true, pressedMouseMove: true, horzTouchDrag: true, vertTouchDrag: true },
    });
    chartRef.current = chart;

    const candleSeries = chart.addSeries(CandlestickSeries, {
      upColor: '#22c55e',
      downColor: '#ef4444',
      borderVisible: false,
      wickUpColor: '#22c55e',
      wickDownColor: '#ef4444',
      priceFormat,
    });

    const fibPrimitive = new FibRetracementPrimitive();
    candleSeries.attachPrimitive(fibPrimitive);
    fibPrimitiveRef.current = fibPrimitive;
    fibPrimitive.setFib(fib ?? null);
    fibPrimitive.setOpacity(fibOpacityRef.current);

    const volumeSeries = chart.addSeries(HistogramSeries, {
      priceFormat: { type: 'volume' },
      priceScaleId: '',
      color: 'rgba(255,255,255,0.15)',
    });
    volumeSeries.priceScale().applyOptions({
      scaleMargins: { top: 0.88, bottom: 0 },
    });

    const buildSeries = chart.addSeries(LineSeries, {
      color: '#60a5fa',
      lineWidth: 2,
      priceFormat,
      priceScaleId: 'build',
      lastValueVisible: false,
      priceLineVisible: false,
      crosshairMarkerVisible: false,
    });
    buildSeries.priceScale().applyOptions({
      scaleMargins: { top: 0.72, bottom: 0.12 },
      visible: false,
      borderVisible: false,
    });

    const oracleSeries = chart.addSeries(LineSeries, {
      color: 'rgba(251,146,60,0.9)',
      lineWidth: 1,
      lineStyle: LineStyle.Dotted,
      priceFormat,
    });

    const smaSeries = chart.addSeries(LineSeries, { color: 'rgba(248,113,113,0.9)', lineWidth: 1, priceFormat });
    const emaSeries = chart.addSeries(LineSeries, { color: 'rgba(52,211,153,0.9)', lineWidth: 1, priceFormat });
    const bbUpperSeries = chart.addSeries(LineSeries, { color: 'rgba(250,204,21,0.6)', lineWidth: 1, priceFormat });
    const bbLowerSeries = chart.addSeries(LineSeries, { color: 'rgba(163,163,163,0.6)', lineWidth: 1, priceFormat });
    const bbMidSeries = chart.addSeries(LineSeries, {
      color: 'rgba(250,204,21,0.35)',
      lineWidth: 1,
      lineStyle: LineStyle.Dashed,
      priceFormat,
    });

    seriesRef.current = {
      candles: candleSeries,
      volume: volumeSeries,
      build: buildSeries,
      oracle: oracleSeries,
      sma20: smaSeries,
      ema20: emaSeries,
      bbUpper: bbUpperSeries,
      bbLower: bbLowerSeries,
      bbMid: bbMidSeries,
    };

    onReadyRef.current?.({ chart, candles: candleSeries as ISeriesApi<'Candlestick', UTCTimestamp> });

    const onClick = (param: any) => {
      if (!param?.point) return;
      const logical = param.logical ?? chart.timeScale().coordinateToLogical(param.point.x);
      if (logical == null) return;
      const price = candleSeries.coordinateToPrice(param.point.y);
      if (price == null) return;

      const currentFib = fibRef.current;
      let target: 'chart' | 'fib' = 'chart';
      if (currentFib) {
        const x1 = chart.timeScale().logicalToCoordinate(currentFib.a.logical as any);
        const x2 = chart.timeScale().logicalToCoordinate(currentFib.b.logical as any);
        if (x1 != null && x2 != null) {
          const tol = 6;
          const left = Math.min(x1, x2) - tol;
          const right = Math.max(x1, x2) + tol;
          const p0 = currentFib.a.price;
          const delta = currentFib.b.price - p0;
          const yRatioMin = 0;
          const yRatioMax = 4.236;
          const pMin = Math.min(p0 + delta * yRatioMin, p0 + delta * yRatioMax);
          const pMax = Math.max(p0 + delta * yRatioMin, p0 + delta * yRatioMax);
          const y1 = candleSeries.priceToCoordinate(pMin);
          const y2 = candleSeries.priceToCoordinate(pMax);
          if (y1 != null && y2 != null) {
            const top = Math.min(y1, y2) - tol;
            const bottom = Math.max(y1, y2) + tol;
            if (param.point.x >= left && param.point.x <= right && param.point.y >= top && param.point.y <= bottom) {
              target = 'fib';
            }
          }
        }
      }

      onChartClickRef.current?.({ logical: Number(logical), price: Number(price), target });
    };
    chart.subscribeClick(onClick);

    const onCrosshairMove = (param: any) => {
      if (!param?.point) return;
      const logical = param.logical ?? chart.timeScale().coordinateToLogical(param.point.x);
      if (logical == null) return;
      const price = candleSeries.coordinateToPrice(param.point.y);
      if (price == null) return;
      onChartCrosshairMoveRef.current?.({ logical: Number(logical), price: Number(price) });
    };
    chart.subscribeCrosshairMove(onCrosshairMove);

    const container = containerRef.current;
    const toAnchorFromPoint = (x: number, y: number): FibAnchor | null => {
      const logical = chart.timeScale().coordinateToLogical(x);
      if (logical == null) return null;
      const price = candleSeries.coordinateToPrice(y);
      if (price == null) return null;
      return { logical: Number(logical), price: Number(price) };
    };

    const isOverFib = (x: number, y: number, currentFib: FibRetracement): boolean => {
      const x1 = chart.timeScale().logicalToCoordinate(currentFib.a.logical as any);
      const x2 = chart.timeScale().logicalToCoordinate(currentFib.b.logical as any);
      if (x1 == null || x2 == null) return false;

      const tol = 6;
      const left = Math.min(x1, x2) - tol;
      const right = Math.max(x1, x2) + tol;

      const p0 = currentFib.a.price;
      const delta = currentFib.b.price - p0;
      const yRatioMin = 0;
      const yRatioMax = 4.236;
      const pMin = Math.min(p0 + delta * yRatioMin, p0 + delta * yRatioMax);
      const pMax = Math.max(p0 + delta * yRatioMin, p0 + delta * yRatioMax);
      const y1 = candleSeries.priceToCoordinate(pMin);
      const y2 = candleSeries.priceToCoordinate(pMax);
      if (y1 == null || y2 == null) return false;

      const top = Math.min(y1, y2) - tol;
      const bottom = Math.max(y1, y2) + tol;
      return x >= left && x <= right && y >= top && y <= bottom;
    };

    const onOverlayPointer = (e: PointerEvent) => {
      if (!containerRef.current) return;
      const rect = containerRef.current.getBoundingClientRect();
      const x = e.clientX - rect.left;
      const y = e.clientY - rect.top;
      const anchor = toAnchorFromPoint(x, y);
      if (!anchor) return;

      const currentFib = fibRef.current;
      const target: 'chart' | 'fib' = currentFib && isOverFib(x, y, currentFib) ? 'fib' : 'chart';
      const type =
        e.type === 'pointerdown'
          ? ('pointerdown' as const)
          : e.type === 'pointermove'
            ? ('pointermove' as const)
            : e.type === 'pointerup'
              ? ('pointerup' as const)
              : ('pointercancel' as const);

      const decision = onPointerEventRef.current?.({
        type,
        logical: anchor.logical,
        price: anchor.price,
        x,
        y,
        target,
        event: e,
      });

      if (decision?.capturePointer) {
        capturedOverlayPointerRef.current = e.pointerId;
        try {
          containerRef.current.setPointerCapture(e.pointerId);
        } catch {
          // ignore
        }
      }

      if (decision?.consume) {
        e.preventDefault();
        e.stopImmediatePropagation();
      }

      if (type === 'pointerup' || type === 'pointercancel') {
        if (capturedOverlayPointerRef.current === e.pointerId) {
          capturedOverlayPointerRef.current = null;
          try {
            containerRef.current.releasePointerCapture(e.pointerId);
          } catch {
            // ignore
          }
        }
      }
    };
    container?.addEventListener('pointerdown', onOverlayPointer, { capture: true });
    container?.addEventListener('pointermove', onOverlayPointer, { capture: true });
    container?.addEventListener('pointerup', onOverlayPointer, { capture: true });
    container?.addEventListener('pointercancel', onOverlayPointer, { capture: true });

    const onWheel = (e: WheelEvent) => {
      if (!e.ctrlKey) return;
      if (!containerRef.current) return;
      e.preventDefault();
      e.stopPropagation();
      if (priceAutoScaleRef.current) return;

      const rect = containerRef.current.getBoundingClientRect();
      const y = e.clientY - rect.top;
      const pivotPrice = candleSeries.coordinateToPrice(y);
      if (pivotPrice == null) return;

      const ps = candleSeries.priceScale();
      const range = ps.getVisibleRange();
      let low: number;
      let high: number;
      if (range) {
        low = Math.min(range.from, range.to);
        high = Math.max(range.from, range.to);
      } else {
        const top = candleSeries.coordinateToPrice(0);
        const bottom = candleSeries.coordinateToPrice(rect.height);
        if (top == null || bottom == null) return;
        low = Math.min(top, bottom);
        high = Math.max(top, bottom);
      }
      const span = high - low;
      if (!(span > 0)) return;

      const zoomFactor = Math.exp(e.deltaY * 0.002);
      if (!Number.isFinite(zoomFactor) || zoomFactor <= 0) return;
      const t = Math.min(1, Math.max(0, (pivotPrice - low) / span));
      const minSpan = span * 1e-6;
      const maxSpan = span * 1e6;
      const nextSpan = Math.min(maxSpan, Math.max(minSpan, span * zoomFactor));
      const nextLow = pivotPrice - t * nextSpan;
      const nextHigh = nextLow + nextSpan;

      ps.setAutoScale(false);
      ps.setVisibleRange({ from: nextLow, to: nextHigh });
    };
    container?.addEventListener('wheel', onWheel, { passive: false, capture: true });

    let pan: { pointerId: number; startPrice: number; startRange: { from: number; to: number } } | null = null;
    const onPointerDown = (e: PointerEvent) => {
      if (e.button !== 0) return;
      if (!e.ctrlKey) return;
      if (priceAutoScaleRef.current) return;
      if (!containerRef.current) return;

      const rect = containerRef.current.getBoundingClientRect();
      const y = e.clientY - rect.top;
      const startPrice = candleSeries.coordinateToPrice(y);
      if (startPrice == null) return;

      const ps = candleSeries.priceScale();
      const range = ps.getVisibleRange();
      let from: number;
      let to: number;
      if (range) {
        from = range.from;
        to = range.to;
      } else {
        const top = candleSeries.coordinateToPrice(0);
        const bottom = candleSeries.coordinateToPrice(rect.height);
        if (top == null || bottom == null) return;
        from = Math.min(top, bottom);
        to = Math.max(top, bottom);
      }
      if (!(to > from)) return;

      pan = { pointerId: e.pointerId, startPrice, startRange: { from, to } };
      try {
        containerRef.current.setPointerCapture(e.pointerId);
      } catch {
        // ignore
      }
      e.preventDefault();
      e.stopPropagation();
    };

    const onPointerMove = (e: PointerEvent) => {
      if (!pan) return;
      if (pan.pointerId !== e.pointerId) return;
      if (!containerRef.current) return;
      if (!e.ctrlKey || priceAutoScaleRef.current) {
        pan = null;
        return;
      }

      const rect = containerRef.current.getBoundingClientRect();
      const y = e.clientY - rect.top;
      const currentPrice = candleSeries.coordinateToPrice(y);
      if (currentPrice == null) return;
      const delta = pan.startPrice - currentPrice;

      const ps = candleSeries.priceScale();
      ps.setAutoScale(false);
      ps.setVisibleRange({ from: pan.startRange.from + delta, to: pan.startRange.to + delta });

      e.preventDefault();
      e.stopPropagation();
    };

    const stopPan = (e: PointerEvent) => {
      if (!pan) return;
      if (pan.pointerId !== e.pointerId) return;
      pan = null;
      try {
        containerRef.current?.releasePointerCapture(e.pointerId);
      } catch {
        // ignore
      }
    };

    container?.addEventListener('pointerdown', onPointerDown, { capture: true });
    container?.addEventListener('pointermove', onPointerMove, { capture: true });
    container?.addEventListener('pointerup', stopPan, { capture: true });
    container?.addEventListener('pointercancel', stopPan, { capture: true });

    const ro = new ResizeObserver(() => {
      if (!containerRef.current) return;
      const { width, height } = containerRef.current.getBoundingClientRect();
      chart.applyOptions({ width: Math.floor(width), height: Math.floor(height) });
    });
    ro.observe(containerRef.current);

    return () => {
      chart.unsubscribeClick(onClick);
      chart.unsubscribeCrosshairMove(onCrosshairMove);
      container?.removeEventListener('pointerdown', onOverlayPointer, { capture: true });
      container?.removeEventListener('pointermove', onOverlayPointer, { capture: true });
      container?.removeEventListener('pointerup', onOverlayPointer, { capture: true });
      container?.removeEventListener('pointercancel', onOverlayPointer, { capture: true });
      container?.removeEventListener('wheel', onWheel, { capture: true });
      container?.removeEventListener('pointerdown', onPointerDown, { capture: true });
      container?.removeEventListener('pointermove', onPointerMove, { capture: true });
      container?.removeEventListener('pointerup', stopPan, { capture: true });
      container?.removeEventListener('pointercancel', stopPan, { capture: true });
      ro.disconnect();
      if (fibPrimitiveRef.current) {
        candleSeries.detachPrimitive(fibPrimitiveRef.current);
        fibPrimitiveRef.current = null;
      }
      chart.remove();
      chartRef.current = null;
      seriesRef.current = {};
    };
  }, []);

  useEffect(() => {
    const s = seriesRef.current;
    if (!s.candles || !s.volume || !s.build) return;
    s.candles.setData(candleData);
    s.volume.setData(volumeData);
    s.oracle?.setData(oracleData);
    s.sma20?.setData(smaData);
    s.ema20?.setData(emaData);
    s.bbUpper?.setData(bbUpper);
    s.bbLower?.setData(bbLower);
    s.bbMid?.setData(bbMid);

    if (buildKeyRef.current !== seriesKey) {
      buildSamplesRef.current.clear();
      buildKeyRef.current = seriesKey;
      lastBuildCandleStartRef.current = null;
    }

    const bs = resolveBucketSeconds(bucketSeconds, candles);
    const eps = 1e-3;
    const maxPointsPerCandle = 600;
    const minStep = Math.max(0.5, bs / maxPointsPerCandle);
    const map = buildSamplesRef.current;
    const visibleStarts = new Set(candles.map((c) => c.time));
    for (const start of Array.from(map.keys())) {
      if (!visibleStarts.has(start)) map.delete(start);
    }

    const last = candles[candles.length - 1];
    if (last) {
      const prevStart = lastBuildCandleStartRef.current;
      if (prevStart != null && prevStart !== last.time) {
        const prevCandle = candles.find((c) => c.time === prevStart);
        if (prevCandle) {
          const endT = prevStart + bs - eps;
          const finalDelta = prevCandle.close - prevCandle.open;
          const list = map.get(prevStart) ?? [];
          const lastT = list.length ? list[list.length - 1]!.t : -Infinity;
          if (endT > lastT + eps) {
            list.push({ t: endT, v: finalDelta });
          } else if (list.length) {
            list[list.length - 1] = { t: lastT, v: finalDelta };
          }
          map.set(prevStart, list);
        }
      }

      const start = last.time;
      const endT = start + bs - eps;
      const delta = last.close - last.open;
      const nowT = Date.now() / 1000;
      const tClamped = Math.min(endT, Math.max(start + eps, nowT));
      const list = map.get(start) ?? [];
      if (list.length) {
        const lastPt = list[list.length - 1]!;
        if (tClamped - lastPt.t < minStep) {
          list[list.length - 1] = { t: tClamped, v: delta };
        } else {
          const t = Math.min(endT, Math.max(lastPt.t + eps, tClamped));
          if (t > lastPt.t) list.push({ t, v: delta });
        }
      } else {
        list.push({ t: tClamped, v: delta });
      }
      map.set(start, list);
      lastBuildCandleStartRef.current = start;
    }

    const buildRaw: LinePoint[] = [];
    for (const c of candles) {
      const list = map.get(c.time);
      if (!list?.length) continue;

      const startT = c.time + eps;
      const endT = c.time + bs - eps;
      if (!(endT > startT)) continue;

      buildRaw.push({ time: toTime(c.time) } as WhitespaceData);
      buildRaw.push({ time: toTime(startT), value: 0 } as LineData);

      let lastT = startT;
      for (const p of list) {
        let t = p.t;
        if (t <= lastT + eps) t = lastT + eps;
        if (t >= endT) break;
        buildRaw.push({ time: toTime(t), value: p.v } as LineData);
        lastT = t;
      }

      const finalDelta = c.close - c.open;
      if (endT > lastT + eps) {
        buildRaw.push({ time: toTime(endT), value: finalDelta } as LineData);
      } else if (buildRaw.length) {
        const prev = buildRaw[buildRaw.length - 1];
        if ('value' in prev) {
          buildRaw[buildRaw.length - 1] = { ...prev, value: finalDelta } as LineData;
        }
      }
    }

    const buildColored: LinePoint[] = [];
    let lastLineIdx: number | null = null;
    let lastLineVal: number | null = null;
    for (const p of buildRaw) {
      if (!('value' in p)) {
        buildColored.push(p);
        lastLineIdx = null;
        lastLineVal = null;
        continue;
      }

      if (lastLineIdx != null && lastLineVal != null) {
        const delta = p.value - lastLineVal;
        const color = delta > 0 ? '#22c55e' : delta < 0 ? '#ef4444' : '#60a5fa';
        const prev = buildColored[lastLineIdx] as LineData;
        buildColored[lastLineIdx] = { ...prev, color };
      }

      buildColored.push({ time: p.time, value: p.value } as LineData);
      lastLineIdx = buildColored.length - 1;
      lastLineVal = p.value;
    }

    s.build.setData(buildColored);

    s.sma20?.applyOptions({ visible: showIndicators });
    s.ema20?.applyOptions({ visible: showIndicators });
    s.bbUpper?.applyOptions({ visible: showIndicators });
    s.bbLower?.applyOptions({ visible: showIndicators });
    s.bbMid?.applyOptions({ visible: showIndicators });
  }, [candleData, volumeData, oracleData, smaData, emaData, bbUpper, bbLower, bbMid, showIndicators, candles, bucketSeconds, seriesKey]);

  useEffect(() => {
    const s = seriesRef.current;
    if (!s.candles) return;
    s.candles.applyOptions({ priceFormat });
    s.build?.applyOptions({ priceFormat });
    s.oracle?.applyOptions({ priceFormat });
    s.sma20?.applyOptions({ priceFormat });
    s.ema20?.applyOptions({ priceFormat });
    s.bbUpper?.applyOptions({ priceFormat });
    s.bbLower?.applyOptions({ priceFormat });
    s.bbMid?.applyOptions({ priceFormat });
  }, [priceFormat]);

  useEffect(() => {
    fibPrimitiveRef.current?.setFib(fib ?? null);
  }, [fib]);

  useEffect(() => {
    fibPrimitiveRef.current?.setSelected(Boolean(fibSelected));
  }, [fibSelected]);

  useEffect(() => {
    const candlesSeries = seriesRef.current.candles;
    if (!candlesSeries) return;
    const ps = candlesSeries.priceScale();
    const prev = prevPriceAutoScaleRef.current;
    prevPriceAutoScaleRef.current = priceAutoScale;

    if (priceAutoScale) {
      ps.setAutoScale(true);
      return;
    }

    ps.setAutoScale(false);
    if (!prev) return;
    if (!containerRef.current) return;

    const rect = containerRef.current.getBoundingClientRect();
    const top = candlesSeries.coordinateToPrice(0);
    const bottom = candlesSeries.coordinateToPrice(rect.height);
    if (top == null || bottom == null) return;
    const from = Math.min(top, bottom);
    const to = Math.max(top, bottom);
    if (!(to > from)) return;
    ps.setVisibleRange({ from, to });
  }, [priceAutoScale]);

  return <div className="tradingChart" ref={containerRef} />;
}
