/**
 * IndicatorFillPrimitive
 *
 * 보조지표 그래프에서 과매수(상한선 이상) / 과매도(하한선 이하) 구간에
 * 반투명 음영을 그려주는 lightweight-charts ISeriesPrimitive 구현.
 *
 * - 상한선(과매수) 색상의 25% 투명도로 상단 영역을 채운다.
 * - 하한선(과매도) 색상의 25% 투명도로 하단 영역을 채운다.
 * - 지표 선과 한계선 사이의 정확한 교차점을 보간하여 부드러운 채움을 표현한다.
 */

import type {
  ISeriesPrimitive,
  IPrimitivePaneView,
  SeriesAttachedParameter,
  Time,
  SeriesType,
  ISeriesApi,
} from 'lightweight-charts';
import type { CanvasRenderingTarget2D, MediaCoordinatesRenderingScope } from 'fancy-canvas';
import { getHLineLabel } from './indicatorRegistry';
import type { HLineDef } from './indicatorRegistry';

// ─────────────────────────────────────────────────────────────────────────────
//  내부 유틸
// ─────────────────────────────────────────────────────────────────────────────

/** HEX(6~9자리) + alpha 값 → CSS rgba() 문자열 */
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);
  return `rgba(${r},${g},${b},${alpha})`;
}

interface ScreenPt { x: number; y: number; }

interface FillZone {
  limitY:     number;
  color:      string;
  isAbove:    boolean;      // true=상한 초과, false=하한 미만
  isBand?:    boolean;      // true=배경 직사각형 밴드 (과열선~침체선 사이)
  bandLimitY?: number;      // isBand=true 일 때 반대쪽 경계 Y
}

// ─────────────────────────────────────────────────────────────────────────────
//  Canvas 그리기 유틸 (교차점 보간 포함)
// ─────────────────────────────────────────────────────────────────────────────

/**
 * 하나의 연속 구간에 대해 채움 폴리곤을 그린다.
 * pts[0]·pts[last]는 반드시 limitY 위에 있어야 한다 (교차점 또는 첫/마지막 점).
 */
function drawSegment(
  ctx: CanvasRenderingContext2D,
  pts: ScreenPt[],
  limitY: number,
): void {
  if (pts.length < 2) return;
  ctx.beginPath();
  ctx.moveTo(pts[0].x, limitY);
  for (const p of pts) ctx.lineTo(p.x, p.y);
  ctx.lineTo(pts[pts.length - 1].x, limitY);
  ctx.closePath();
  ctx.fill();
}

/**
 * isAbove=true: indicator > limitPrice(화면상 y < limitY) → 빨간 음영
 * isAbove=false: indicator < limitPrice(화면상 y > limitY) → 초록 음영
 */
function drawFillZone(
  ctx: CanvasRenderingContext2D,
  pts: ScreenPt[],
  limitY: number,
  color: string,
  isAbove: boolean,
): void {
  if (pts.length < 2) return;

  ctx.save();
  ctx.fillStyle = color;

  const inZone = (y: number) => isAbove ? y < limitY : y > limitY;

  let seg: ScreenPt[] = [];

  const flushSeg = () => {
    if (seg.length >= 1) {
      drawSegment(ctx, seg, limitY);
      seg = [];
    }
  };

  for (let i = 0; i < pts.length; i++) {
    const cur = pts[i];
    const curIn = inZone(cur.y);

    if (curIn) {
      if (seg.length === 0 && i > 0) {
        // 이전 점과의 교차점 보간
        const prev = pts[i - 1];
        const t = (limitY - prev.y) / (cur.y - prev.y);
        seg.push({ x: prev.x + t * (cur.x - prev.x), y: limitY });
      }
      seg.push({ x: cur.x, y: cur.y });
    } else if (seg.length > 0) {
      // 구간 종료 → 종료 교차점 보간
      const prev = pts[i - 1];
      const t = (limitY - prev.y) / (cur.y - prev.y);
      seg.push({ x: prev.x + t * (cur.x - prev.x), y: limitY });
      flushSeg();
    }
  }

  flushSeg(); // 마지막 구간

  ctx.restore();
}

// ─────────────────────────────────────────────────────────────────────────────
//  Renderer
// ─────────────────────────────────────────────────────────────────────────────

class FillRenderer {
  constructor(
    private readonly pts: ScreenPt[],
    private readonly zones: FillZone[],
  ) {}

  draw(target: CanvasRenderingTarget2D): void {
    target.useMediaCoordinateSpace((scope: MediaCoordinatesRenderingScope) => {
      const ctx = scope.context;
      for (const z of this.zones) {
        if (z.isBand && z.bandLimitY !== undefined && this.pts.length >= 2) {
          // 과열선~침체선 사이 배경 직사각형
          const minX = this.pts[0].x;
          const maxX = this.pts[this.pts.length - 1].x;
          const topY    = Math.min(z.limitY, z.bandLimitY);
          const bottomY = Math.max(z.limitY, z.bandLimitY);
          ctx.save();
          ctx.fillStyle = z.color;
          ctx.fillRect(minX, topY, maxX - minX, bottomY - topY);
          ctx.restore();
        } else {
          drawFillZone(ctx, this.pts, z.limitY, z.color, z.isAbove);
        }
      }
    });
  }
}

// ─────────────────────────────────────────────────────────────────────────────
//  PaneView
// ─────────────────────────────────────────────────────────────────────────────

class FillPaneView implements IPrimitivePaneView {
  constructor(
    private readonly pts: ScreenPt[],
    private readonly zones: FillZone[],
  ) {}

  renderer() { return new FillRenderer(this.pts, this.zones); }
  zOrder(): 'bottom' { return 'bottom'; }
}

// ─────────────────────────────────────────────────────────────────────────────
//  Public Primitive
// ─────────────────────────────────────────────────────────────────────────────

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

export class IndicatorFillPrimitive implements ISeriesPrimitive<Time> {
  private _ap:         SeriesAttachedParameter<Time> | null = null;
  private _hlines:     HLineDef[] = [];
  private _background: HlinesBackgroundOptions | undefined;
  private _pts:        ScreenPt[] = [];
  private _zones:      FillZone[] = [];

  constructor(hlines: HLineDef[], background?: HlinesBackgroundOptions) {
    this._hlines     = hlines;
    this._background = background;
  }

  // ISeriesPrimitive 콜백
  attached(ap: SeriesAttachedParameter<Time>): void { this._ap = ap; }
  detached(): void { this._ap = null; }

  updateAllViews(): void { this._recompute(); }
  paneViews(): readonly IPrimitivePaneView[] {
    return [new FillPaneView(this._pts, this._zones)];
  }

  /** 외부에서 hlines 가 변경되면 호출 */
  updateHLines(hlines: HLineDef[], background?: HlinesBackgroundOptions): void {
    this._hlines     = hlines;
    this._background = background;
    this._ap?.requestUpdate();
  }

  // ─── 좌표 재계산 ───────────────────────────────────────────────────────────
  private _recompute(): void {
    if (!this._ap) { this._pts = []; this._zones = []; return; }

    const series = this._ap.series as ISeriesApi<SeriesType, Time>;
    const ts     = this._ap.chart.timeScale();

    // 시리즈 데이터 → 화면 좌표
    const raw = series.data() as readonly { time: Time; value?: number }[];
    this._pts = raw
      .map(d => {
        if (d.value === undefined || d.value === null || isNaN(d.value)) return null;
        const x = ts.timeToCoordinate(d.time);
        const y = series.priceToCoordinate(d.value);
        if (x === null || y === null) return null;
        return { x: x as number, y: y as number };
      })
      .filter((p): p is ScreenPt => p !== null);

    // 음영 구간 결정 (과매수/과매도 레이블이 있는 hline 만 사용)
    const visible = this._hlines.filter(h => h.visible !== false);
    const prices  = visible.map(h => h.price);

    this._zones = [];

    // ── Hlines Background: 과열선~침체선 사이 배경 밴드 ────────────────────
    const bg = this._background;
    if (bg?.visible !== false) {
      const upperHl = visible.find(h => (h.label ?? getHLineLabel(h.price, prices)) === '과열선');
      const lowerHl = visible.find(h => (h.label ?? getHLineLabel(h.price, prices)) === '침체선');
      if (upperHl && lowerHl) {
        const upperY = series.priceToCoordinate(upperHl.price);
        const lowerY = series.priceToCoordinate(lowerHl.price);
        if (upperY !== null && lowerY !== null) {
          // 배경을 채우기 위해 항상 존재하는 "모든 구간" 영역으로 처리
          // 과열선 아래(침체선까지)를 채우는 방식: 두 개의 존 대신 캔버스 rect 로 직접 그림
          const bgColor = bg?.color
            ? hexToRgba(bg.color.slice(0, 7), parseInt(bg.color.slice(7, 9) || 'ff', 16) / 255 * 0.4)
            : 'rgba(120,123,134,0.08)';
          // 배경 존은 limitY=upperY, isAbove=false 로 표현하면 침체선 아래만 채움.
          // 대신 "배경 전용 존"은 전체 visible 구간을 채우도록 두 경계를 모두 넣는다.
          this._zones.push({
            limitY:  upperY as number,
            color:   bgColor,
            isAbove: false,
            isBand:  true,
            bandLimitY: lowerY as number,
          } as FillZone & { isBand: true; bandLimitY: number });
        }
      }
    }

    // ── 과매수/과매도 초과 구간 음영 ────────────────────────────────────────
    for (const hl of visible) {
      const label = hl.label ?? getHLineLabel(hl.price, prices);
      if (label !== '과열선' && label !== '침체선') continue;

      const limitY = series.priceToCoordinate(hl.price);
      if (limitY === null) continue;

      this._zones.push({
        limitY:  limitY as number,
        color:   hexToRgba(hl.color.slice(0, 7), 0.25),
        isAbove: label === '과열선',
      });
    }
  }
}
