'use client';

import { useEffect, useRef, useState, useImperativeHandle, forwardRef } from 'react';
import { createChart, IChartApi, ISeriesApi, CandlestickData, HistogramData, Time, SeriesMarker, MouseEventParams } from 'lightweight-charts';
import { useTheme } from 'next-themes';
import SpanAnnotationManager from './SpanAnnotationManager';
import { TrendLine } from '@/plugins/trend-line';
import { RectangleDrawingPrimitive, RectanglePoint } from '@/plugins/rectangle-drawing';
import type { PerCandlePrediction, PredictionSpan, ModelInfoResponse, PredictionSummary } from '@/types/predictions';
import type { Candle, Annotation, AnnotationType, SpanAnnotation, SpanLabelType } from '@/types';

// === Magic Number Constants ===
const ENDPOINT_CLICK_TOLERANCE_PX = 8;
const DEFAULT_CANDLE_INTERVAL_S = 60;
const MARKER_TIME_TOLERANCE_S = 60;
const LINE_WIDTH = 2;
const MARKER_SIZE_DEFAULT = 1;
const MARKER_SIZE_SELECTED = 2;
const PREDICTION_HISTOGRAM_ALPHA_DEFAULT = 0.15;
const PREDICTION_HISTOGRAM_ALPHA_DISAGREEMENT = 0.25;
const CONFIDENCE_PERCENTAGE_MULTIPLIER = 100;

// === Color Constants - Dark Theme ===
const DARK_BG_COLOR = '#000000';
const DARK_TEXT_COLOR = '#00ff41';
const DARK_GRID_COLOR = '#003311';
const DARK_BORDER_COLOR = '#003311';
const DARK_CROSSHAIR_COLOR = '#00ff41';

// === Color Constants - Light Theme ===
const LIGHT_BG_COLOR = '#ffffff';
const LIGHT_TEXT_COLOR = '#1a1a2e';
const LIGHT_GRID_COLOR = '#d1d5db';
const LIGHT_BORDER_COLOR = '#d1d5db';
const LIGHT_CROSSHAIR_COLOR = '#16a34a';

// === Color Constants - Candlestick ===
const CANDLESTICK_UP_COLOR = '#ffffff';
const CANDLESTICK_DOWN_COLOR = '#444444';
const CANDLESTICK_BORDER_UP_COLOR = '#444444';
const CANDLESTICK_BORDER_DOWN_COLOR = '#444444';
const CANDLESTICK_WICK_UP_COLOR = '#444444';
const CANDLESTICK_WICK_DOWN_COLOR = '#444444';

// === Color Constants - Prediction ===
const PREDICTION_HISTOGRAM_DEFAULT_COLOR = 'rgba(128, 128, 128, 0.15)';
const PREDICTION_DEFAULT_FALLBACK_COLOR = '#888888';
const PREDICTION_MISSED_BY_HUMAN_COLOR = '#eab308';
const PREDICTION_LABEL_MISMATCH_COLOR = '#f97316';

// === Predefined Label Colors ===
const PREDEFINED_LABEL_COLORS = [
  '#3b82f6', // blue
  '#ef4444', // red
  '#10b981', // green
  '#f59e0b', // amber
  '#8b5cf6', // violet
  '#ec4899', // pink
  '#06b6d4', // cyan
  '#f97316', // orange
];

// === Default Props ===
const EMPTY_STRING_SET = new Set<string>();

interface DrawingState {
  tool: 'line' | 'rectangle';
  firstPoint: { time: Time; price: number };
}

interface Point {
  time: Time;
  price: number;
}

// Candle, Annotation, AnnotationType, SpanAnnotation, SpanLabelType are imported from @/types above.

interface CandleChartProps {
  activeTool: string | null;
  onAnnotationChange?: () => void;
  selectedColor: string;
  selectedLabelId?: number | null;
  onLabelSelect?: (id: number) => void;
  activeChartId?: number | null;
  spanAnnotations?: SpanAnnotation[];
  spanLabelTypes?: SpanLabelType[];
  selectedSpanId?: number | null;
  onSpanAnnotationsChange?: () => void;
  onSelectedSpanChange?: (spanId: number | null) => void;
  // Prediction rendering props
  predictionVisible?: boolean;
  perCandlePredictions?: PerCandlePrediction[];
  predictionSpans?: PredictionSpan[];
  confidenceThreshold?: number;
  selectedLabels?: Set<string>;
  modelInfo?: ModelInfoResponse | null;
  predictionSummary?: PredictionSummary | null;
  showOnlyDisagreements?: boolean;
  onPredictionClick?: (span: PredictionSpan, disagreementType: string | null) => void;
  onPredictionDismiss?: (span: PredictionSpan, disagreementType: string | null) => void;
}

export interface CandleChartHandle {
  refreshData: () => Promise<void>;
  getVisibleCandles: () => Candle[];
}

const CandleChart = forwardRef<CandleChartHandle, CandleChartProps>(
  ({ 
    activeTool, 
    onAnnotationChange, 
    selectedColor, 
    selectedLabelId, 
    onLabelSelect, 
    activeChartId,
    spanAnnotations = [],
    spanLabelTypes = [],
    selectedSpanId = null,
    onSpanAnnotationsChange,
    onSelectedSpanChange,
    predictionVisible = false,
    perCandlePredictions = [],
    predictionSpans = [],
    confidenceThreshold = 0.5,
    selectedLabels = EMPTY_STRING_SET,
    modelInfo = null,
    predictionSummary = null,
    showOnlyDisagreements = false,
    onPredictionClick,
    onPredictionDismiss,
  }, ref) => {
    const chartContainerRef = useRef<HTMLDivElement>(null);
    const chartRef = useRef<IChartApi | null>(null);
    const seriesRef = useRef<ISeriesApi<'Candlestick'> | null>(null);
    const histogramSeriesRef = useRef<ISeriesApi<'Histogram'> | null>(null);
    const [candles, setCandles] = useState<Candle[]>([]);
    const [annotations, setAnnotations] = useState<Annotation[]>([]);
    const [annotationTypes, setAnnotationTypes] = useState<AnnotationType[]>([]);
    const [isEmpty, setIsEmpty] = useState(true);
    const { theme, resolvedTheme } = useTheme();
    const [mounted, setMounted] = useState(false);
    
    // Drawing state for line and rectangle tools
    const [drawingState, setDrawingState] = useState<DrawingState | null>(null);
    const previewPrimitiveRef = useRef<TrendLine | RectangleDrawingPrimitive | null>(null);
    const linePrimitivesRef = useRef<Map<number, TrendLine>>(new Map());
    const rectanglePrimitivesRef = useRef<Map<number, RectangleDrawingPrimitive>>(new Map());
    const [selectedRectangleId, setSelectedRectangleId] = useState<number | null>(null);
    
    // Line selection and dragging state
    const [selectedLineId, setSelectedLineId] = useState<number | null>(null);
    const [dragState, setDragState] = useState<{ lineId: number; endpoint: 'p1' | 'p2' } | null>(null);

    // Refs to avoid stale closures in chart click/mouse handlers
    const drawingStateRef = useRef<DrawingState | null>(drawingState);
    const selectedLineIdRef = useRef<number | null>(selectedLineId);
    const dragStateRef = useRef<{ lineId: number; endpoint: 'p1' | 'p2' } | null>(dragState);
    const annotationsRef = useRef<Annotation[]>(annotations);

    // Keep refs in sync with state
    useEffect(() => { drawingStateRef.current = drawingState; }, [drawingState]);
    useEffect(() => { selectedLineIdRef.current = selectedLineId; }, [selectedLineId]);
    useEffect(() => { dragStateRef.current = dragState; }, [dragState]);
    useEffect(() => { annotationsRef.current = annotations; }, [annotations]);

    // Track mounted state to avoid hydration mismatch
    useEffect(() => {
      setMounted(true);
    }, []);

    // Fetch candles from API
    const fetchCandles = async () => {
      try {
        const url = activeChartId ? `/api/candles?chartId=${activeChartId}` : '/api/candles';
        const response = await fetch(url);
        if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
        const data = await response.json();
        setCandles(data);
        setIsEmpty(data.length === 0);
        return data;
      } catch (error) {
        console.error('Failed to fetch candles:', error);
        return [];
      }
    };

    // Fetch annotations from API
    const fetchAnnotations = async () => {
      try {
        const url = activeChartId ? `/api/annotations?chartId=${activeChartId}` : '/api/annotations';
        const response = await fetch(url);
        if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
        const data = await response.json();
        setAnnotations(data);
        return data;
      } catch (error) {
        console.error('Failed to fetch annotations:', error);
        return [];
      }
    };

    // Fetch annotation types from API
    const fetchAnnotationTypes = async () => {
      try {
        const response = await fetch('/api/annotation-types');
        if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
        const data = await response.json();
        setAnnotationTypes(data.filter((t: AnnotationType) => t.is_active));
        return data;
      } catch (error) {
        console.error('Failed to fetch annotation types:', error);
        return [];
      }
    };

    // Expose methods to parent
    useImperativeHandle(ref, () => ({
      refreshData: async () => {
        await fetchCandles();
        await fetchAnnotations();
        await fetchAnnotationTypes();
      },
      getVisibleCandles: () => {
        if (!chartRef.current || candles.length === 0) return [];
        const timeScale = chartRef.current.timeScale();
        const visibleRange = timeScale.getVisibleLogicalRange();
        if (!visibleRange) return candles;
        const barsInfo = seriesRef.current?.barsInLogicalRange(visibleRange);
        if (!barsInfo || !barsInfo.barsBefore && !barsInfo.barsAfter) return candles;
        // Filter candles by visible time range
        const from = barsInfo.from as number;
        const to = barsInfo.to as number;
        if (typeof from !== 'number' || typeof to !== 'number') return candles;
        return candles.filter((c) => c.time >= from && c.time <= to);
      },
    }), [candles]);

    // Get theme-specific colors
    const getChartColors = () => {
      const isDark = resolvedTheme === 'dark';

      if (isDark) {
        return {
          layout: {
            background: { color: DARK_BG_COLOR },
            textColor: DARK_TEXT_COLOR,
          },
          grid: {
            vertLines: { color: DARK_GRID_COLOR },
            horzLines: { color: DARK_GRID_COLOR },
          },
          timeScale: {
            borderColor: DARK_BORDER_COLOR,
          },
          rightPriceScale: {
            borderColor: DARK_BORDER_COLOR,
          },
          crosshair: {
            color: DARK_CROSSHAIR_COLOR,
          },
        };
      } else {
        return {
          layout: {
            background: { color: LIGHT_BG_COLOR },
            textColor: LIGHT_TEXT_COLOR,
          },
          grid: {
            vertLines: { color: LIGHT_GRID_COLOR },
            horzLines: { color: LIGHT_GRID_COLOR },
          },
          timeScale: {
            borderColor: LIGHT_BORDER_COLOR,
          },
          rightPriceScale: {
            borderColor: LIGHT_BORDER_COLOR,
          },
          crosshair: {
            color: LIGHT_CROSSHAIR_COLOR,
          },
        };
      }
    };

    // Initialize chart
    useEffect(() => {
      if (!chartContainerRef.current || isEmpty || !mounted) return;

      const colors = getChartColors();

      const chart = createChart(chartContainerRef.current, {
        width: chartContainerRef.current.clientWidth,
        height: chartContainerRef.current.clientHeight,
        layout: colors.layout,
        grid: colors.grid,
        crosshair: {
          mode: 1,
        },
        timeScale: {
          timeVisible: true,
          secondsVisible: false,
          borderColor: colors.timeScale.borderColor,
        },
        rightPriceScale: {
          borderColor: colors.rightPriceScale.borderColor,
        },
      });

      const candlestickSeries = chart.addCandlestickSeries({
        upColor: CANDLESTICK_UP_COLOR,
        downColor: CANDLESTICK_DOWN_COLOR,
        borderVisible: true,
        wickUpColor: CANDLESTICK_WICK_UP_COLOR,
        wickDownColor: CANDLESTICK_WICK_DOWN_COLOR,
        borderUpColor: CANDLESTICK_BORDER_UP_COLOR,
        borderDownColor: CANDLESTICK_BORDER_DOWN_COLOR,
      });

      // Add histogram series for prediction overlay (rendered behind candles)
      const histogramSeries = chart.addHistogramSeries({
        color: PREDICTION_HISTOGRAM_DEFAULT_COLOR,
        priceFormat: { type: 'volume' },
        priceScaleId: 'prediction_overlay',
      });
      histogramSeries.priceScale().applyOptions({
        scaleMargins: { top: 0, bottom: 0 },
        visible: false,
      });

      chartRef.current = chart;
      seriesRef.current = candlestickSeries;
      histogramSeriesRef.current = histogramSeries;

      // Handle resize
      const resizeObserver = new ResizeObserver((entries) => {
        if (entries.length === 0 || !chartContainerRef.current) return;
        const { width, height } = entries[0].contentRect;
        chart.applyOptions({ width, height });
      });

      resizeObserver.observe(chartContainerRef.current);

      return () => {
        resizeObserver.disconnect();
        histogramSeriesRef.current = null;
        chart.remove();
      };
    }, [isEmpty, mounted]);

    // Apply theme changes without re-creating the chart
    useEffect(() => {
      if (!chartRef.current) return;
      const colors = getChartColors();
      chartRef.current.applyOptions({
        layout: colors.layout,
        grid: colors.grid,
        timeScale: { borderColor: colors.timeScale.borderColor },
        rightPriceScale: { borderColor: colors.rightPriceScale.borderColor },
      });
    }, [resolvedTheme]);

    // Load candle data into chart
    useEffect(() => {
      if (!seriesRef.current || candles.length === 0) return;

      const chartData: CandlestickData[] = candles
        .map((candle) => ({
          time: candle.time as Time,
          open: candle.open,
          high: candle.high,
          low: candle.low,
          close: candle.close,
        }))
        .sort((a, b) => (a.time as number) - (b.time as number));

      seriesRef.current.setData(chartData);
    }, [candles]);

    // Update markers from annotations
    useEffect(() => {
      if (!seriesRef.current || annotationTypes.length === 0) return;

      // Get marker type names
      const markerTypeNames = annotationTypes
        .filter((t) => t.category === 'marker')
        .map((t) => t.name);

      const markerAnnotations = annotations.filter((a) =>
        markerTypeNames.includes(a.label_type)
      );

      const markers = markerAnnotations
        .map((annotation) => {
          const annotationType = annotationTypes.find((t) => t.name === annotation.label_type);
          if (!annotationType) return null;

          const isSelected = annotation.id === selectedLabelId;

          // Determine marker shape and position based on icon
          const isUpArrow = annotationType.icon === 'arrowUp';

          return {
            time: annotation.timestamp as Time,
            position: isUpArrow ? ('belowBar' as const) : ('aboveBar' as const),
            color: isSelected
              ? annotationType.color + 'CC' // Add slight transparency when selected
              : annotationType.color,
            shape: isUpArrow ? ('arrowUp' as const) : ('arrowDown' as const),
            text: annotationType.display_name,
            size: isSelected ? MARKER_SIZE_SELECTED : MARKER_SIZE_DEFAULT,
          };
        })
        .filter((m) => m !== null)
        .sort((a, b) => (a!.time as number) - (b!.time as number));

      seriesRef.current.setMarkers(markers as any);
    }, [annotations, selectedLabelId, annotationTypes]);

    // Render prediction histogram overlay and span markers
    useEffect(() => {
      if (!histogramSeriesRef.current || !seriesRef.current || candles.length === 0) return;

      // If predictions are not visible or no data, clear the histogram and markers
      if (!predictionVisible || perCandlePredictions.length === 0) {
        histogramSeriesRef.current.setData([]);
        histogramSeriesRef.current.setMarkers([]);
        return;
      }

      // Build a label-to-color map from modelInfo
      // Generate colors for labels since backend no longer provides them
      const labelColorMap: Record<string, string> = {};
      if (modelInfo?.labels) {
        modelInfo.labels.forEach((label, index) => {
          labelColorMap[label] = PREDEFINED_LABEL_COLORS[index % PREDEFINED_LABEL_COLORS.length];
        });
      }

      // Build disagreement lookup map for highlighting
      // Map: predictionSpan start_time -> disagreement type
      const disagreementMap = new Map<number, string>();
      const disagreementSpanSet = new Set<string>(); // Set of "start-end" keys for filtering
      
      if (predictionSummary?.disagreements) {
        predictionSummary.disagreements.forEach((d) => {
          if (d.predictionSpan) {
            disagreementMap.set(d.predictionSpan.start_time, d.type);
            disagreementSpanSet.add(`${d.predictionSpan.start_time}-${d.predictionSpan.end_time}`);
          }
        });
      }

      // Helper to convert hex to rgba
      const hexToRgba = (hex: string, alpha: number): string => {
        hex = hex.replace('#', '');
        const r = parseInt(hex.substring(0, 2), 16);
        const g = parseInt(hex.substring(2, 4), 16);
        const b = parseInt(hex.substring(4, 6), 16);
        return `rgba(${r}, ${g}, ${b}, ${alpha})`;
      };

      // Build candle price lookup for histogram values
      const candleMap = new Map(candles.map((c) => [c.time, c]));

      // Detect candle interval from data (fall back to 60s if fewer than 2 candles)
      const candleInterval = candles.length >= 2 ? candles[1].time - candles[0].time : DEFAULT_CANDLE_INTERVAL_S;

      // Build a map from prediction time to its span for disagreement lookup
      const predictionTimeToSpan = new Map<number, PredictionSpan>();
      predictionSpans.forEach((span) => {
        // Associate all times in the span with the span object
        for (let t = span.start_time; t <= span.end_time; t += candleInterval) {
          predictionTimeToSpan.set(t, span);
        }
      });

      // Filter and map predictions to histogram data with disagreement highlighting
      const histogramData: (HistogramData & { color?: string })[] = perCandlePredictions
        .filter((p) => {
          // Filter by confidence threshold
          if (p.confidence < confidenceThreshold) return false;
          // Filter by selected labels
          if (!selectedLabels.has(p.label)) return false;
          // Skip "O" (no-pattern) labels
          if (p.label === 'O') return false;

          // If showOnlyDisagreements is enabled, only show predictions that are part of disagreements
          if (showOnlyDisagreements) {
            const span = predictionTimeToSpan.get(p.time);
            if (!span) return false;
            const spanKey = `${span.start_time}-${span.end_time}`;
            if (!disagreementSpanSet.has(spanKey)) return false;
          }

          return true;
        })
        .map((p) => {
          const candle = candleMap.get(p.time);
          // Use candle high as the histogram value so it overlays correctly
          const value = candle ? candle.high : 0;

          // Check if this prediction is part of a disagreement
          const span = predictionTimeToSpan.get(p.time);
          const disagreementType = span ? disagreementMap.get(span.start_time) : null;

          let baseColor = labelColorMap[p.label] || PREDICTION_DEFAULT_FALLBACK_COLOR;
          let alpha = PREDICTION_HISTOGRAM_ALPHA_DEFAULT;

          // Apply disagreement-specific colors and styling
          if (disagreementType === 'missed_by_human') {
            // Yellow highlight for predictions missed by humans
            baseColor = PREDICTION_MISSED_BY_HUMAN_COLOR;
            alpha = PREDICTION_HISTOGRAM_ALPHA_DISAGREEMENT;
          } else if (disagreementType === 'label_mismatch') {
            // Orange for label mismatches
            baseColor = PREDICTION_LABEL_MISMATCH_COLOR;
            alpha = PREDICTION_HISTOGRAM_ALPHA_DISAGREEMENT;
          }

          return {
            time: p.time as Time,
            value,
            color: hexToRgba(baseColor, alpha),
          };
        })
        .sort((a, b) => (a.time as number) - (b.time as number));

      histogramSeriesRef.current.setData(histogramData);

      // Add markers for prediction span labels at span start times
      const spanMarkers: SeriesMarker<Time>[] = predictionSpans
        .filter((span) => {
          if (span.avg_confidence < confidenceThreshold) return false;
          if (!selectedLabels.has(span.label)) return false;
          if (span.label === 'O') return false;

          // If showOnlyDisagreements is enabled, only show spans that are disagreements
          if (showOnlyDisagreements) {
            const spanKey = `${span.start_time}-${span.end_time}`;
            if (!disagreementSpanSet.has(spanKey)) return false;
          }

          return true;
        })
        .map((span) => {
          const disagreementType = disagreementMap.get(span.start_time);
          let baseColor = labelColorMap[span.label] || PREDICTION_DEFAULT_FALLBACK_COLOR;
          let labelText = span.label;

          // Apply disagreement-specific styling to markers
          if (disagreementType === 'missed_by_human') {
            baseColor = PREDICTION_MISSED_BY_HUMAN_COLOR;
            labelText = `⚠ ${span.label}`;
          } else if (disagreementType === 'label_mismatch') {
            baseColor = PREDICTION_LABEL_MISMATCH_COLOR;
            labelText = `⚠ ${span.label}`;
          }

          const confidencePct = Math.round(span.avg_confidence * CONFIDENCE_PERCENTAGE_MULTIPLIER);
          return {
            time: span.start_time as Time,
            position: 'belowBar' as const,
            color: baseColor,
            shape: 'square' as const,
            text: `${labelText} (${confidencePct}%)`,
            size: MARKER_SIZE_DEFAULT,
          };
        })
        .sort((a, b) => (a.time as number) - (b.time as number));

      histogramSeriesRef.current.setMarkers(spanMarkers);
    }, [predictionVisible, perCandlePredictions, predictionSpans, confidenceThreshold, selectedLabels, modelInfo, candles, predictionSummary, showOnlyDisagreements]);

    // Handle chart clicks for annotation
    useEffect(() => {
      if (!chartRef.current || !seriesRef.current) return;

      // Helper function to check if click is near a line endpoint
      const isNearEndpoint = (clickX: number, clickY: number, endpointTime: Time, endpointPrice: number): boolean => {
        const endpointX = chartRef.current!.timeScale().timeToCoordinate(endpointTime);
        const endpointY = seriesRef.current!.priceToCoordinate(endpointPrice);

        if (endpointX === null || endpointY === null) return false;

        const distance = Math.sqrt(
          Math.pow(clickX - endpointX, 2) + Math.pow(clickY - endpointY, 2)
        );

        return distance <= ENDPOINT_CLICK_TOLERANCE_PX;
      };

      const handleClick = async (param: MouseEventParams) => {
        if (!param.point) return;

        const timeCoordinate = param.point.x;
        const priceCoordinate = param.point.y;

        const time = chartRef.current!.timeScale().coordinateToTime(timeCoordinate);
        const price = seriesRef.current!.coordinateToPrice(priceCoordinate);

        if (time === null || price === null) return;

        // Check if clicking on a selected line's endpoint handle to start dragging
        if (selectedLineIdRef.current !== null && !activeTool) {
          const linePrimitive = linePrimitivesRef.current.get(selectedLineIdRef.current);
          if (linePrimitive) {
            const p1 = linePrimitive.getP1();
            const p2 = linePrimitive.getP2();
            
            if (isNearEndpoint(timeCoordinate, priceCoordinate, p1.time, p1.price)) {
              setDragState({ lineId: selectedLineIdRef.current!, endpoint: 'p1' });
              return;
            }
            
            if (isNearEndpoint(timeCoordinate, priceCoordinate, p2.time, p2.price)) {
              setDragState({ lineId: selectedLineIdRef.current!, endpoint: 'p2' });
              return;
            }
          }
        }

        // If currently dragging, complete the drag operation
        if (dragStateRef.current) {
          const linePrimitive = linePrimitivesRef.current.get(dragStateRef.current.lineId);
          if (linePrimitive) {
            const annotation = annotationsRef.current.find(a => a.id === dragStateRef.current!.lineId);
            if (annotation) {
              // Persist the updated geometry
              const p1 = linePrimitive.getP1();
              const p2 = linePrimitive.getP2();
              
              try {
                await fetch(`/api/annotations/${annotation.id}`, {
                  method: 'PATCH',
                  headers: { 'Content-Type': 'application/json' },
                  body: JSON.stringify({
                    geometry: {
                      startTime: typeof p1.time === 'string' ? Date.parse(p1.time) / 1000 : p1.time,
                      startPrice: p1.price,
                      endTime: typeof p2.time === 'string' ? Date.parse(p2.time) / 1000 : p2.time,
                      endPrice: p2.price,
                    },
                  }),
                });
                await fetchAnnotations();
                onAnnotationChange?.();
              } catch (error) {
                console.error('Failed to update line annotation:', error);
              }
            }
          }
          setDragState(null);
          return;
        }

        // Handle line and rectangle drawing
        if (activeTool === 'line' || activeTool === 'rectangle') {
          if (!drawingStateRef.current) {
            // First click: record first point and show preview
            const firstPoint = { time, price };
            setDrawingState({ tool: activeTool, firstPoint });

            // Create preview primitive
            if (seriesRef.current && chartRef.current) {
              const previewPoint = { time, price };
              
              if (activeTool === 'line') {
                const preview = new TrendLine(
                  chartRef.current,
                  seriesRef.current,
                  firstPoint as Point,
                  previewPoint as Point,
                  {
                    lineColor: selectedColor,
                    width: LINE_WIDTH,
                    showLabels: false,
                    isPreview: true,
                  }
                );
                seriesRef.current.attachPrimitive(preview);
                previewPrimitiveRef.current = preview;
              } else {
                const preview = new RectangleDrawingPrimitive({
                  p1: firstPoint as RectanglePoint,
                  p2: previewPoint as RectanglePoint,
                  color: selectedColor,
                  isPreview: true,
                });
                seriesRef.current.attachPrimitive(preview);
                previewPrimitiveRef.current = preview;
              }
            }
          } else {
            // Second click: save annotation and create permanent primitive
            const secondPoint = { time, price };
            
            try {
              const currentDrawing = drawingStateRef.current!;
              const timestamp = typeof currentDrawing.firstPoint.time === 'string'
                ? Date.parse(currentDrawing.firstPoint.time) / 1000
                : (currentDrawing.firstPoint.time as number);

              const response = await fetch('/api/annotations', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                  timestamp,
                  label_type: activeTool,
                  chart_id: activeChartId,
                  color: selectedColor,
                  geometry: {
                    startTime: typeof currentDrawing.firstPoint.time === 'string'
                      ? Date.parse(currentDrawing.firstPoint.time) / 1000
                      : currentDrawing.firstPoint.time,
                    startPrice: currentDrawing.firstPoint.price,
                    endTime: typeof secondPoint.time === 'string' 
                      ? Date.parse(secondPoint.time) / 1000 
                      : secondPoint.time,
                    endPrice: secondPoint.price,
                  },
                }),
              });

              if (response.ok) {
                // Detach preview
                if (previewPrimitiveRef.current && seriesRef.current) {
                  seriesRef.current.detachPrimitive(previewPrimitiveRef.current);
                  previewPrimitiveRef.current = null;
                }

                // Clear drawing state
                setDrawingState(null);

                // Refresh annotations to load the permanent primitive
                await fetchAnnotations();
                onAnnotationChange?.();
              }
            } catch (error) {
              console.error('Failed to create annotation:', error);
            }
          }
          return; // Don't process other tools
        }

        if (!activeTool) return;

        // Check if activeTool is a marker type
        const markerType = annotationTypes.find(
          (t) => t.category === 'marker' && t.name === activeTool
        );

        if (markerType) {
          const timestamp = typeof time === 'string' ? Date.parse(time) / 1000 : (time as number);

          // Find nearest candle
          const nearestCandle = candles.reduce((prev, curr) => {
            return Math.abs(curr.time - timestamp) < Math.abs(prev.time - timestamp) ? curr : prev;
          }, candles[0]);

          if (!nearestCandle) return;

          // Create annotation
          try {
            const response = await fetch('/api/annotations', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({
                timestamp: nearestCandle.time,
                label_type: activeTool,
                chart_id: activeChartId,
              }),
            });

            if (response.ok) {
              await fetchAnnotations();
              onAnnotationChange?.();
            }
          } catch (error) {
            console.error('Failed to create annotation:', error);
          }
        }

        // For delete tool, find and delete line, rectangle, or marker at clicked position
        if (activeTool === 'delete') {
          const timestamp = typeof time === 'string' ? Date.parse(time) / 1000 : (time as number);
          
          // First, check for line hit using primitives' hitTest
          const deleteLineHit = (() => {
            let result: { id: number; primitive: TrendLine } | null = null;
            linePrimitivesRef.current.forEach((primitive, id) => {
              const hit = primitive.hitTest(timeCoordinate, priceCoordinate);
              if (hit) {
                result = { id, primitive };
              }
            });
            return result as { id: number; primitive: TrendLine } | null;
          })();

          if (deleteLineHit) {
            // Delete the clicked line
            try {
              const response = await fetch(`/api/annotations/${deleteLineHit.id}`, {
                method: 'DELETE',
              });

              if (response.ok) {
                seriesRef.current!.detachPrimitive(deleteLineHit.primitive);
                linePrimitivesRef.current.delete(deleteLineHit.id);
                if (selectedLineIdRef.current === deleteLineHit.id) {
                  setSelectedLineId(null);
                }
                await fetchAnnotations();
                onAnnotationChange?.();
              }
            } catch (error) {
              console.error('Failed to delete line annotation:', error);
            }
            return; // Don't process rectangle/marker deletion
          }
          
          // Next, check for rectangle hit using primitives' hitTest
          const rectangleHit = (() => {
            let result: { id: number; primitive: RectangleDrawingPrimitive } | null = null;
            rectanglePrimitivesRef.current.forEach((primitive, id) => {
              const hit = primitive.hitTest(timeCoordinate, priceCoordinate);
              if (hit) {
                result = { id, primitive };
              }
            });
            return result as { id: number; primitive: RectangleDrawingPrimitive } | null;
          })();

          if (rectangleHit) {
            // Delete the clicked rectangle
            try {
              const response = await fetch(`/api/annotations/${rectangleHit.id}`, {
                method: 'DELETE',
              });

              if (response.ok) {
                seriesRef.current!.detachPrimitive(rectangleHit.primitive);
                rectanglePrimitivesRef.current.delete(rectangleHit.id);
                await fetchAnnotations();
                onAnnotationChange?.();
              }
            } catch (error) {
              console.error('Failed to delete rectangle annotation:', error);
            }
            return; // Don't process marker deletion
          }

          // If no rectangle hit, check for marker annotations
          const markerTypeNames = annotationTypes
            .filter((t) => t.category === 'marker')
            .map((t) => t.name);

          // Find annotation at this timestamp (within tolerance)
          const annotation = annotationsRef.current.find(
            (a) =>
              markerTypeNames.includes(a.label_type) &&
              Math.abs(a.timestamp - timestamp) < MARKER_TIME_TOLERANCE_S
          );

          if (annotation) {
            try {
              const response = await fetch(`/api/annotations/${annotation.id}`, {
                method: 'DELETE',
              });

              if (response.ok) {
                await fetchAnnotations();
                onAnnotationChange?.();
              }
            } catch (error) {
              console.error('Failed to delete annotation:', error);
            }
          }
        }

        // Handle line selection when no tool is active or delete tool is active
        if (!activeTool || activeTool === 'delete') {
          // Check if a line was clicked
          const lineHit = (() => {
            let result: { id: number; primitive: TrendLine } | null = null;
            linePrimitivesRef.current.forEach((primitive, id) => {
              const hit = primitive.hitTest(timeCoordinate, priceCoordinate);
              if (hit && activeTool !== 'delete') {
                result = { id, primitive };
              }
            });
            return result as { id: number; primitive: TrendLine } | null;
          })();

          if (lineHit && activeTool !== 'delete') {
            // Toggle selection
            const newSelectedId = selectedLineIdRef.current === lineHit.id ? null : lineHit.id;
            setSelectedLineId(newSelectedId);
            
            // Update all lines' selection state
            linePrimitivesRef.current.forEach((p, pid) => {
              p.setSelected(pid === newSelectedId);
            });
            seriesRef.current!.applyOptions({});
            return; // Don't process rectangle/marker selection
          }
        }

        // Handle rectangle selection when no tool is active or delete tool is active
        if (!activeTool || activeTool === 'delete') {
          // Check if a rectangle was clicked
          rectanglePrimitivesRef.current.forEach((primitive, id) => {
            const hit = primitive.hitTest(timeCoordinate, priceCoordinate);
            if (hit && activeTool !== 'delete') {
              // Toggle selection
              const newSelectedId = selectedRectangleId === id ? null : id;
              setSelectedRectangleId(newSelectedId);
              
              // Update all rectangles' selection state
              rectanglePrimitivesRef.current.forEach((p, pid) => {
                p.setSelected(pid === newSelectedId);
              });
              seriesRef.current!.applyOptions({});
            }
          });
        }

        // Handle clicks on prediction spans (for converting to annotations or dismissing)
        if (!activeTool && predictionVisible && predictionSpans.length > 0) {
          const timestamp = typeof time === 'string' ? Date.parse(time) / 1000 : (time as number);
          
          // Find if click is within any prediction span
          const clickedSpan = predictionSpans.find(
            (span) => timestamp >= span.start_time && timestamp <= span.end_time
          );

          if (clickedSpan) {
            // Check if this span is a disagreement
            const disagreementMap = new Map<number, string>();
            if (predictionSummary?.disagreements) {
              predictionSummary.disagreements.forEach((d) => {
                if (d.predictionSpan) {
                  disagreementMap.set(d.predictionSpan.start_time, d.type);
                }
              });
            }

            const disagreementType = disagreementMap.get(clickedSpan.start_time) || null;
            
            // Check if Alt or Ctrl key is pressed for dismiss action
            if ((param.sourceEvent?.altKey || param.sourceEvent?.ctrlKey) && onPredictionDismiss) {
              // Alt+Click or Ctrl+Click: Dismiss as "not a pattern"
              onPredictionDismiss(clickedSpan, disagreementType);
            } else if (onPredictionClick) {
              // Normal click: Convert to annotation (only for disagreements)
              if (disagreementType === 'missed_by_human' || disagreementType === 'label_mismatch') {
                onPredictionClick(clickedSpan, disagreementType);
              }
            }
          }
        }

        // Select/deselect label markers by clicking them
        const isMarkerTool = annotationTypes.find(
          (t) => t.category === 'marker' && t.name === activeTool
        );

        if (!activeTool || isMarkerTool) {
          const timestamp = typeof time === 'string' ? Date.parse(time) / 1000 : (time as number);
          const markerTypeNames = annotationTypes
            .filter((t) => t.category === 'marker')
            .map((t) => t.name);

          // Find annotation at this timestamp (within tolerance)
          const annotation = annotationsRef.current.find(
            (a) =>
              markerTypeNames.includes(a.label_type) &&
              Math.abs(a.timestamp - timestamp) < MARKER_TIME_TOLERANCE_S
          );

          if (annotation) {
            onLabelSelect?.(annotation.id === selectedLabelId ? -1 : annotation.id);
          }
        }
      };

      chartRef.current.subscribeClick(handleClick);

      return () => {
        chartRef.current?.unsubscribeClick(handleClick);
      };
    }, [activeTool, candles, annotationTypes, onAnnotationChange, predictionVisible, predictionSpans, predictionSummary, onPredictionClick, onPredictionDismiss, selectedColor]);

    // Handle crosshair move to update preview during drawing and dragging
    useEffect(() => {
      if (!chartRef.current || !seriesRef.current) return;
      if (!drawingState && !dragState) return;
      if (drawingState && !previewPrimitiveRef.current) return;

      let isUpdating = false;
      const handleCrosshairMove = (param: MouseEventParams) => {
        if (isUpdating) return;
        if (!param.point) return;

        const time = chartRef.current!.timeScale().coordinateToTime(param.point.x);
        const price = seriesRef.current!.coordinateToPrice(param.point.y);

        if (time === null || price === null) return;

        const currentPoint = { time, price };

        isUpdating = true;
        try {
          // Update preview primitive endpoint during drawing
          if (drawingState && previewPrimitiveRef.current) {
            if (previewPrimitiveRef.current instanceof TrendLine) {
              previewPrimitiveRef.current.updatePoints(
                drawingState.firstPoint as Point,
                currentPoint as Point
              );
            } else if (previewPrimitiveRef.current instanceof RectangleDrawingPrimitive) {
              previewPrimitiveRef.current.updatePoints(
                drawingState.firstPoint as RectanglePoint,
                currentPoint as RectanglePoint
              );
            }
            seriesRef.current!.applyOptions({});
          }

          // Update line endpoint during dragging
          if (dragState) {
            const linePrimitive = linePrimitivesRef.current.get(dragState.lineId);
            if (linePrimitive) {
              const p1 = linePrimitive.getP1();
              const p2 = linePrimitive.getP2();

              if (dragState.endpoint === 'p1') {
                linePrimitive.updatePoints(currentPoint as Point, p2);
              } else {
                linePrimitive.updatePoints(p1, currentPoint as Point);
              }
              seriesRef.current!.applyOptions({});
            }
          }
        } finally {
          isUpdating = false;
        }
      };

      chartRef.current.subscribeCrosshairMove(handleCrosshairMove);

      return () => {
        chartRef.current?.unsubscribeCrosshairMove(handleCrosshairMove);
      };
    }, [drawingState, dragState]);

    // Handle Escape key to cancel drawing
    useEffect(() => {
      const handleKeyDown = (e: KeyboardEvent) => {
        if (e.key === 'Escape' && drawingState && previewPrimitiveRef.current) {
          // Detach preview primitive
          if (seriesRef.current) {
            seriesRef.current.detachPrimitive(previewPrimitiveRef.current);
            previewPrimitiveRef.current = null;
          }
          // Clear drawing state
          setDrawingState(null);
        }
      };

      window.addEventListener('keydown', handleKeyDown);

      return () => {
        window.removeEventListener('keydown', handleKeyDown);
      };
    }, [drawingState, activeChartId]);

    // Manage TrendLine primitives for saved line annotations
    useEffect(() => {
      if (!chartRef.current || !seriesRef.current || annotations.length === 0) return;

      // Filter line annotations
      const lineAnnotations = annotations.filter((a) => a.label_type === 'line' && a.geometry);

      // Detach old primitives that no longer exist
      const currentIds = new Set(lineAnnotations.map((a) => a.id));
      linePrimitivesRef.current.forEach((primitive, id) => {
        if (!currentIds.has(id)) {
          seriesRef.current!.detachPrimitive(primitive);
          linePrimitivesRef.current.delete(id);
        }
      });

      // Create/update primitives for line annotations
      lineAnnotations.forEach((annotation) => {
        const geometry = annotation.geometry!;
        if (!geometry.startTime || !geometry.endTime) return;

        // Check if primitive already exists
        if (!linePrimitivesRef.current.has(annotation.id)) {
          // Create new TrendLine primitive
          const p1: Point = {
            time: geometry.startTime as Time,
            price: geometry.startPrice!,
          };
          const p2: Point = {
            time: geometry.endTime as Time,
            price: geometry.endPrice!,
          };

          const color = annotation.color || annotationTypes.find((t) => t.name === 'line')?.color || selectedColor;

          const trendLine = new TrendLine(
            chartRef.current!,
            seriesRef.current!,
            p1,
            p2,
            {
              lineColor: color,
              width: LINE_WIDTH,
              showLabels: false,
              annotationId: String(annotation.id),
            }
          );

          seriesRef.current!.attachPrimitive(trendLine);
          linePrimitivesRef.current.set(annotation.id, trendLine);
        }
      });
    }, [annotations, annotationTypes, selectedColor, activeChartId]);

    // Manage RectangleDrawingPrimitive for saved rectangle annotations
    useEffect(() => {
      if (!seriesRef.current || annotations.length === 0) return;

      // Filter rectangle annotations
      const rectangleAnnotations = annotations.filter((a) => a.label_type === 'rectangle' && a.geometry);

      // Detach old primitives that no longer exist
      const currentIds = new Set(rectangleAnnotations.map((a) => a.id));
      rectanglePrimitivesRef.current.forEach((primitive, id) => {
        if (!currentIds.has(id)) {
          seriesRef.current!.detachPrimitive(primitive);
          rectanglePrimitivesRef.current.delete(id);
        }
      });

      // Create/update primitives for rectangle annotations
      rectangleAnnotations.forEach((annotation) => {
        const geometry = annotation.geometry!;
        if (!geometry.startTime || !geometry.endTime) return;

        // Check if primitive already exists
        if (!rectanglePrimitivesRef.current.has(annotation.id)) {
          // Create new RectangleDrawingPrimitive
          const p1: RectanglePoint = {
            time: geometry.startTime as Time,
            price: geometry.startPrice!,
          };
          const p2: RectanglePoint = {
            time: geometry.endTime as Time,
            price: geometry.endPrice!,
          };

          const color = annotationTypes.find((t) => t.name === 'rectangle')?.color || selectedColor;

          const rectangle = new RectangleDrawingPrimitive({
            p1,
            p2,
            color,
            annotationId: String(annotation.id),
          });

          seriesRef.current!.attachPrimitive(rectangle);
          rectanglePrimitivesRef.current.set(annotation.id, rectangle);
        }
      });
    }, [annotations, annotationTypes, selectedColor, activeChartId]);

    // Fetch data on mount and when chart switches
    useEffect(() => {
      fetchCandles();
      fetchAnnotations();
      fetchAnnotationTypes();
    }, [activeChartId]);

    if (isEmpty) {
      return (
        <div className="flex-1 flex items-center justify-center bg-background">
          <div className="text-muted-foreground text-center">
            <p className="text-lg">Upload a CSV file to view the candlestick chart</p>
            <p className="text-sm mt-2">CSV format: time, open, high, low, close</p>
          </div>
        </div>
      );
    }

    return (
      <div className="w-full h-full relative">
        <div ref={chartContainerRef} className="absolute inset-0" />
        <SpanAnnotationManager
          chart={chartRef.current}
          series={seriesRef.current}
          activeTool={activeTool}
          candles={candles}
          spanAnnotations={spanAnnotations}
          spanLabelTypes={spanLabelTypes}
          selectedSpanId={selectedSpanId}
          onSpanAnnotationsChange={onSpanAnnotationsChange || (() => {})}
          onSelectedSpanChange={onSelectedSpanChange || (() => {})}
          activeChartId={activeChartId ?? null}
        />
      </div>
    );
  }
);

CandleChart.displayName = 'CandleChart';

export default CandleChart;
