import {
  ISeriesPrimitive,
  ISeriesPrimitivePaneView,
  ISeriesPrimitivePaneRenderer,
  SeriesAttachedParameter,
  Time,
  Logical,
  PrimitiveHoveredItem,
} from 'lightweight-charts';

export interface SpanData {
  id: number;
  start_time: number;
  end_time: number;
  label: string;
  color: string;
  max_high: number;
  min_low: number;
}

export interface SpanRectanglePrimitiveOptions {
  data: SpanData;
  isSelected?: boolean;
}

class SpanRectanglePaneView implements ISeriesPrimitivePaneView {
  private _data: SpanData;
  private _isSelected: boolean;
  private _series: SeriesAttachedParameter<Time> | null = null;

  constructor(data: SpanData, isSelected: boolean = false) {
    this._data = data;
    this._isSelected = isSelected;
  }

  update(data: SpanData, isSelected: boolean, series: SeriesAttachedParameter<Time> | null): void {
    this._data = data;
    this._isSelected = isSelected;
    this._series = series;
  }

  renderer() {
    return new SpanRectangleRenderer(this._data, this._isSelected, this._series);
  }

  zOrder(): 'bottom' | 'normal' | 'top' {
    return 'bottom'; // Render behind candlesticks
  }
}

class SpanRectangleRenderer implements ISeriesPrimitivePaneRenderer {
  private _data: SpanData;
  private _isSelected: boolean;
  private _series: SeriesAttachedParameter<Time> | null;

  constructor(data: SpanData, isSelected: boolean, series: SeriesAttachedParameter<Time> | null) {
    this._data = data;
    this._isSelected = isSelected;
    this._series = series;
  }

  draw(target: any): void {
    if (!this._series) return;

    target.useBitmapCoordinateSpace((scope: any) => {
      const ctx = scope.context;
      const scalingFactor = scope.horizontalPixelRatio;

      const timeScale = this._series!.chart.timeScale();
      const series = this._series!.series;

      // Convert time coordinates to pixel x coordinates
      const x1 = timeScale.timeToCoordinate(this._data.start_time as Time);
      const x2 = timeScale.timeToCoordinate(this._data.end_time as Time);

      // Convert price coordinates to pixel y coordinates
      const y1 = series.priceToCoordinate(this._data.max_high);
      const y2 = series.priceToCoordinate(this._data.min_low);

      // Check if coordinates are valid
      if (x1 === null || x2 === null || y1 === null || y2 === null) {
        return;
      }

      // Scale coordinates for HiDPI
      const scaledX1 = x1 * scalingFactor;
      const scaledX2 = x2 * scalingFactor;
      const scaledY1 = y1 * scalingFactor;
      const scaledY2 = y2 * scalingFactor;

      // Parse color and apply transparency
      const baseOpacity = this._isSelected ? 0.3 : 0.2;
      const fillColor = this._hexToRgba(this._data.color, baseOpacity);
      const borderOpacity = this._isSelected ? 0.8 : 0.5;
      const borderColor = this._hexToRgba(this._data.color, borderOpacity);
      const borderWidth = (this._isSelected ? 2 : 1) * scalingFactor;

      // Calculate rectangle dimensions
      const rectX = Math.min(scaledX1, scaledX2);
      const rectY = Math.min(scaledY1, scaledY2);
      const rectWidth = Math.abs(scaledX2 - scaledX1);
      const rectHeight = Math.abs(scaledY2 - scaledY1);

      // Draw filled rectangle
      ctx.fillStyle = fillColor;
      ctx.fillRect(rectX, rectY, rectWidth, rectHeight);

      // Draw border
      ctx.strokeStyle = borderColor;
      ctx.lineWidth = borderWidth;
      ctx.strokeRect(rectX, rectY, rectWidth, rectHeight);

      // Draw label tag above rectangle
      this._drawLabel(ctx, rectX, rectY, rectWidth, scalingFactor);
    });
  }

  private _drawLabel(ctx: CanvasRenderingContext2D, rectX: number, rectY: number, rectWidth: number, scalingFactor: number): void {
    const label = this._data.label.replace(/_/g, ' ').toUpperCase();
    const fontSize = 11 * scalingFactor;
    const padding = 4 * scalingFactor;

    ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
    const textMetrics = ctx.measureText(label);
    const textWidth = textMetrics.width;
    const textHeight = fontSize;

    // Position label at top-left of rectangle, slightly offset
    const labelX = rectX + 4 * scalingFactor;
    const labelY = rectY - 2 * scalingFactor;

    // Draw background
    ctx.fillStyle = this._data.color;
    ctx.fillRect(
      labelX - padding,
      labelY - textHeight - padding,
      textWidth + padding * 2,
      textHeight + padding * 2
    );

    // Draw text
    ctx.fillStyle = '#ffffff';
    ctx.textBaseline = 'top';
    ctx.fillText(label, labelX, labelY - textHeight);
  }

  private _hexToRgba(hex: string, alpha: number): string {
    // Remove # if present
    hex = hex.replace('#', '');

    // Parse hex to RGB
    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})`;
  }
}

export class SpanRectanglePrimitive implements ISeriesPrimitive<Time> {
  private _options: SpanRectanglePrimitiveOptions;
  private _paneView: SpanRectanglePaneView;
  private _series: SeriesAttachedParameter<Time> | null = null;

  constructor(options: SpanRectanglePrimitiveOptions) {
    this._options = options;
    this._paneView = new SpanRectanglePaneView(options.data, options.isSelected ?? false);
  }

  attached(param: SeriesAttachedParameter<Time>): void {
    this._series = param;
    this.updateAllViews();
  }

  detached(): void {
    this._series = null;
  }

  updateAllViews(): void {
    this._paneView.update(this._options.data, this._options.isSelected ?? false, this._series);
  }

  paneViews() {
    return [this._paneView];
  }

  updateData(data: SpanData): void {
    this._options.data = data;
    this.updateAllViews();
  }

  setSelected(isSelected: boolean): void {
    this._options.isSelected = isSelected;
    this.updateAllViews();
  }

  hitTest(x: number, y: number): PrimitiveHoveredItem | null {
    if (!this._series) return null;

    const chart = this._series.chart;
    const timeScale = chart.timeScale();

    // Convert pixel x to time coordinate
    const time = timeScale.coordinateToTime(x);
    if (!time) return null;

    const timeValue = typeof time === 'string' ? Date.parse(time) / 1000 : (time as number);

    // Convert pixel y to price coordinate
    const price = this._series.series.coordinateToPrice(y);
    if (!price) return null;

    // Check if click is within span bounds
    const withinTimeRange = timeValue >= this._options.data.start_time && timeValue <= this._options.data.end_time;
    const withinPriceRange = price >= this._options.data.min_low && price <= this._options.data.max_high;

    if (withinTimeRange && withinPriceRange) {
      return {
        cursorStyle: 'pointer',
        externalId: this._options.data.id.toString(),
        zOrder: 'bottom',
      };
    }

    return null;
  }

  autoscaleInfo(startTimePoint: Logical, endTimePoint: Logical) {
    if (!this._series) return null;

    const timeScale = this._series.chart.timeScale();
    const startTime = timeScale.coordinateToTime(startTimePoint as any);
    const endTime = timeScale.coordinateToTime(endTimePoint as any);

    if (!startTime || !endTime) return null;

    const startValue = typeof startTime === 'string' ? Date.parse(startTime) / 1000 : (startTime as number);
    const endValue = typeof endTime === 'string' ? Date.parse(endTime) / 1000 : (endTime as number);

    // Check if span overlaps with visible time range
    const overlaps =
      (this._options.data.start_time >= startValue && this._options.data.start_time <= endValue) ||
      (this._options.data.end_time >= startValue && this._options.data.end_time <= endValue) ||
      (this._options.data.start_time <= startValue && this._options.data.end_time >= endValue);

    if (!overlaps) return null;

    // Return price range of the span
    return {
      priceRange: {
        minValue: this._options.data.min_low,
        maxValue: this._options.data.max_high,
      },
    };
  }
}
