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

export interface RectanglePoint {
  time: Time;
  price: number;
}

export interface RectangleDrawingOptions {
  p1: RectanglePoint;
  p2: RectanglePoint;
  color: string;
  isPreview?: boolean;
  annotationId?: string;
}

class RectangleDrawingPaneView implements ISeriesPrimitivePaneView {
  private _options: RectangleDrawingOptions;
  private _isSelected: boolean;
  private _series: SeriesAttachedParameter<Time> | null = null;

  constructor(options: RectangleDrawingOptions, isSelected: boolean = false) {
    this._options = options;
    this._isSelected = isSelected;
  }

  update(options: RectangleDrawingOptions, isSelected: boolean, series: SeriesAttachedParameter<Time> | null): void {
    this._options = options;
    this._isSelected = isSelected;
    this._series = series;
  }

  renderer() {
    return new RectangleDrawingRenderer(this._options, this._isSelected, this._series);
  }

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

class RectangleDrawingRenderer implements ISeriesPrimitivePaneRenderer {
  private _options: RectangleDrawingOptions;
  private _isSelected: boolean;
  private _series: SeriesAttachedParameter<Time> | null;

  constructor(options: RectangleDrawingOptions, isSelected: boolean, series: SeriesAttachedParameter<Time> | null) {
    this._options = options;
    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._options.p1.time);
      const x2 = timeScale.timeToCoordinate(this._options.p2.time);

      // Convert price coordinates to pixel y coordinates
      const y1 = series.priceToCoordinate(this._options.p1.price);
      const y2 = series.priceToCoordinate(this._options.p2.price);

      // 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;

      // 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);

      // Determine opacity based on preview and selection state
      let fillOpacity = 0.2;
      let borderOpacity = 0.5;
      let borderWidth = 1 * scalingFactor;

      if (this._options.isPreview) {
        fillOpacity = 0.1;
        borderOpacity = 0.3;
      } else if (this._isSelected) {
        fillOpacity = 0.3;
        borderOpacity = 0.8;
        borderWidth = 2 * scalingFactor;
      }

      const fillColor = this._hexToRgba(this._options.color, fillOpacity);
      const borderColor = this._hexToRgba(this._options.color, borderOpacity);

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

      // Draw border with optional dashed style for preview
      ctx.strokeStyle = borderColor;
      ctx.lineWidth = borderWidth;
      
      if (this._options.isPreview) {
        ctx.setLineDash([5 * scalingFactor, 5 * scalingFactor]);
      } else {
        ctx.setLineDash([]);
      }
      
      ctx.strokeRect(rectX, rectY, rectWidth, rectHeight);
      ctx.setLineDash([]);
    });
  }

  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 RectangleDrawingPrimitive implements ISeriesPrimitive<Time> {
  private _options: RectangleDrawingOptions;
  private _paneView: RectangleDrawingPaneView;
  private _series: SeriesAttachedParameter<Time> | null = null;
  private _isSelected: boolean = false;

  constructor(options: RectangleDrawingOptions) {
    this._options = options;
    this._paneView = new RectangleDrawingPaneView(options, false);
  }

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

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

  updateAllViews(): void {
    this._paneView.update(this._options, this._isSelected, this._series);
  }

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

  updatePoints(p1: RectanglePoint, p2: RectanglePoint): void {
    this._options.p1 = p1;
    this._options.p2 = p2;
    this.updateAllViews();
  }

  setSelected(isSelected: boolean): void {
    this._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;

    // Get time values for both corners
    const time1 = typeof this._options.p1.time === 'string' 
      ? Date.parse(this._options.p1.time) / 1000 
      : (this._options.p1.time as number);
    const time2 = typeof this._options.p2.time === 'string' 
      ? Date.parse(this._options.p2.time) / 1000 
      : (this._options.p2.time as number);

    const minTime = Math.min(time1, time2);
    const maxTime = Math.max(time1, time2);
    const minPrice = Math.min(this._options.p1.price, this._options.p2.price);
    const maxPrice = Math.max(this._options.p1.price, this._options.p2.price);

    // Check if click is within rectangle bounds
    const withinTimeRange = timeValue >= minTime && timeValue <= maxTime;
    const withinPriceRange = price >= minPrice && price <= maxPrice;

    if (withinTimeRange && withinPriceRange) {
      return {
        cursorStyle: 'pointer',
        externalId: this._options.annotationId || '',
        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);

    // Get time values for both corners
    const time1 = typeof this._options.p1.time === 'string' 
      ? Date.parse(this._options.p1.time) / 1000 
      : (this._options.p1.time as number);
    const time2 = typeof this._options.p2.time === 'string' 
      ? Date.parse(this._options.p2.time) / 1000 
      : (this._options.p2.time as number);

    const minTime = Math.min(time1, time2);
    const maxTime = Math.max(time1, time2);

    // Check if rectangle overlaps with visible time range
    const overlaps =
      (minTime >= startValue && minTime <= endValue) ||
      (maxTime >= startValue && maxTime <= endValue) ||
      (minTime <= startValue && maxTime >= endValue);

    if (!overlaps) return null;

    const minPrice = Math.min(this._options.p1.price, this._options.p2.price);
    const maxPrice = Math.max(this._options.p1.price, this._options.p2.price);

    // Return price range of the rectangle
    return {
      priceRange: {
        minValue: minPrice,
        maxValue: maxPrice,
      },
    };
  }
}
