/**
 * 볼린저밴드 상·하단 사이 영역 채움 (업비트 "백그라운드 그리기")
 */
import type {
  ISeriesPrimitive,
  IPrimitivePaneView,
  SeriesAttachedParameter,
  Time,
  SeriesType,
  ISeriesApi,
} from 'lightweight-charts';
import type { CanvasRenderingTarget2D, MediaCoordinatesRenderingScope } from 'fancy-canvas';

interface ScreenPt { x: number; y: number }

function hexToRgba(hex: string, alpha: number): string {
  const h = hex.startsWith('#') ? hex.slice(1) : hex;
  const r = parseInt(h.slice(0, 2), 16);
  const g = parseInt(h.slice(2, 4), 16);
  const b = parseInt(h.slice(4, 6), 16);
  const a = h.length >= 8 ? parseInt(h.slice(6, 8), 16) / 255 : alpha;
  return `rgba(${r},${g},${b},${a})`;
}

function resolveFillColor(color: string): string {
  if (color.length >= 9) {
    return hexToRgba(color.slice(0, 7), parseInt(color.slice(7, 9), 16) / 255);
  }
  return hexToRgba(color.slice(0, 7), 0.15);
}

class BandFillRenderer {
  constructor(
    private readonly upper: ScreenPt[],
    private readonly lower: ScreenPt[],
    private readonly fillColor: string,
  ) {}

  draw(target: CanvasRenderingTarget2D): void {
    if (this.upper.length < 2 || this.lower.length < 2) return;
    target.useMediaCoordinateSpace((scope: MediaCoordinatesRenderingScope) => {
      const ctx = scope.context;
      const n = this.upper.length;
      ctx.save();
      ctx.fillStyle = this.fillColor;
      ctx.beginPath();
      ctx.moveTo(this.upper[0].x, this.upper[0].y);
      for (let i = 1; i < n; i++) ctx.lineTo(this.upper[i].x, this.upper[i].y);
      for (let i = n - 1; i >= 0; i--) ctx.lineTo(this.lower[i].x, this.lower[i].y);
      ctx.closePath();
      ctx.fill();
      ctx.restore();
    });
  }
}

class BandFillPaneView implements IPrimitivePaneView {
  constructor(
    private readonly upper: ScreenPt[],
    private readonly lower: ScreenPt[],
    private readonly fillColor: string,
  ) {}

  renderer() { return new BandFillRenderer(this.upper, this.lower, this.fillColor); }
  zOrder(): 'bottom' { return 'bottom'; }
}

export interface BandBackgroundOptions {
  visible: boolean;
  color:   string;
}

/** basis 시리즈에 부착, upper/lower 데이터로 채움 */
export class BollingerBandFillPrimitive implements ISeriesPrimitive<Time> {
  private _ap: SeriesAttachedParameter<Time> | null = null;
  private _upper: ISeriesApi<SeriesType, Time>;
  private _lower: ISeriesApi<SeriesType, Time>;
  private _bg: BandBackgroundOptions;
  private _upperPts: ScreenPt[] = [];
  private _lowerPts: ScreenPt[] = [];

  constructor(
    upperSeries: ISeriesApi<SeriesType, Time>,
    lowerSeries: ISeriesApi<SeriesType, Time>,
    background: BandBackgroundOptions,
  ) {
    this._upper = upperSeries;
    this._lower = lowerSeries;
    this._bg = background;
  }

  attached(ap: SeriesAttachedParameter<Time>): void {
    this._ap = ap;
    this._recompute();
  }
  detached(): void { this._ap = null; }

  updateAllViews(): void { this._recompute(); }
  paneViews(): readonly IPrimitivePaneView[] {
    if (this._bg.visible === false || this._upperPts.length < 2) return [];
    return [new BandFillPaneView(this._upperPts, this._lowerPts, resolveFillColor(this._bg.color))];
  }

  updateBackground(bg: BandBackgroundOptions): void {
    this._bg = bg;
    this._ap?.requestUpdate();
  }

  requestRefresh(): void {
    this._recompute();
    this._ap?.requestUpdate();
  }

  private _recompute(): void {
    if (!this._ap || this._bg.visible === false) {
      this._upperPts = [];
      this._lowerPts = [];
      return;
    }

    const ts = this._ap.chart.timeScale();
    const upperRaw = this._upper.data() as readonly { time: Time; value?: number }[];
    const lowerRaw = this._lower.data() as readonly { time: Time; value?: number }[];
    const lowerByTime = new Map<Time, number>();
    for (const d of lowerRaw) {
      if (d.value !== undefined && d.value !== null && !Number.isNaN(d.value)) {
        lowerByTime.set(d.time, d.value);
      }
    }

    const upperPts: ScreenPt[] = [];
    const lowerPts: ScreenPt[] = [];

    for (const u of upperRaw) {
      const lv = lowerByTime.get(u.time);
      if (u.value === undefined || u.value === null || Number.isNaN(u.value)) continue;
      if (lv === undefined || Number.isNaN(lv)) continue;
      const x = ts.timeToCoordinate(u.time);
      const uy = this._upper.priceToCoordinate(u.value);
      const ly = this._lower.priceToCoordinate(lv);
      if (x === null || uy === null || ly === null) continue;
      upperPts.push({ x: x as number, y: uy as number });
      lowerPts.push({ x: x as number, y: ly as number });
    }

    this._upperPts = upperPts;
    this._lowerPts = lowerPts;
  }
}
