import {
  createChart,
  CandlestickSeries,
  BarSeries,
  LineSeries,
  AreaSeries,
  BaselineSeries,
  HistogramSeries,
  createSeriesMarkers,
  type IChartApi,
  type ISeriesApi,
  type SeriesType,
  type MouseEventParams,
  type Time,
  type IPriceLine,
  type ISeriesMarkersPluginApi,
  ColorType,
  LineStyle,
} from 'lightweight-charts-v5';
import type { OHLCVBar, ChartType, Theme, IndicatorConfig } from './types';
import { calculateIndicator, getIndicatorDef, type PlotData, type MarkerData } from './indicatorRegistry';
import { calcAutoFib } from './calculations';

interface ThemeTokens {
  bgColor:        string;
  textColor:      string;
  gridColor:      string;
  borderColor:    string;
  crosshairColor: string;
  upColor:        string;
  downColor:      string;
}

const DARK: ThemeTokens = {
  bgColor:        '#131722',
  textColor:      '#D1D4DC',
  gridColor:      '#1e2330',
  borderColor:    '#2A2E39',
  crosshairColor: '#9598A1',
  upColor:        '#26a69a',
  downColor:      '#ef5350',
};
const LIGHT: ThemeTokens = {
  bgColor:        '#FFFFFF',
  textColor:      '#131722',
  gridColor:      '#F0F3FA',
  borderColor:    '#E0E3EB',
  crosshairColor: '#758696',
  upColor:        '#26a69a',
  downColor:      '#ef5350',
};

interface IndicatorEntry {
  id:         string;
  type:       string;
  seriesList: ISeriesApi<SeriesType>[];
  paneIndex?: number;
  alertLines: IPriceLine[];
  config:     IndicatorConfig;
}

interface AlertEntry  { price: number; label: string; line: IPriceLine; }
interface FibLevel    { price: number; label: string; line: IPriceLine; }

function getTheme(theme: Theme): ThemeTokens {
  return theme === 'dark' ? DARK : LIGHT;
}

export class ChartManager {
  private chart: IChartApi;
  private container: HTMLElement;
  private mainSeries: ISeriesApi<SeriesType> | null = null;
  private volumeSeries: ISeriesApi<'Histogram'> | null = null;
  private indicators   = new Map<string, IndicatorEntry>();
  private alertEntries: AlertEntry[] = [];
  private fibLines:     FibLevel[]   = [];
  private drawingTool   = 'cursor';
  private drawingPoints: { time: Time; price: number }[] = [];
  private drawingLines:  IPriceLine[] = [];
  private _clickHandler: ((p: MouseEventParams<Time>) => void) | null = null;
  private rawBars: OHLCVBar[] = [];
  private currentTheme: Theme = 'dark';
  private mainMarkersPlugin: ISeriesMarkersPluginApi<Time> | null = null;
  private patternMarkers: Array<{ id: string; markers: MarkerData[] }> = [];
  private compareSeriesMap = new Map<string, ISeriesApi<SeriesType>>();

  constructor(container: HTMLElement, theme: Theme) {
    this.container = container;
    const t = getTheme(theme);
    this.chart = createChart(container, {
      layout: {
        background: { type: ColorType.Solid, color: t.bgColor },
        textColor:  t.textColor,
        fontSize:   12,
      },
      grid: {
        vertLines: { color: t.gridColor },
        horzLines: { color: t.gridColor },
      },
      crosshair: {
        vertLine: { color: t.crosshairColor, labelBackgroundColor: '#9598A1' },
        horzLine: { color: t.crosshairColor, labelBackgroundColor: '#9598A1' },
      },
      timeScale: {
        borderColor:     t.borderColor,
        timeVisible:     true,
        secondsVisible:  false,
        rightOffset:     8,
      },
      rightPriceScale: { borderColor: t.borderColor },
      handleScroll: { mouseWheel: true, pressedMouseMove: true, horzTouchDrag: true, vertTouchDrag: false },
      handleScale:  { axisPressedMouseMove: true, axisDoubleClickReset: true, mouseWheel: true, pinch: true },
    });
  }

  /** 내부 IChartApi 직접 접근 (커스텀 시리즈 추가 등) */
  getChart(): IChartApi { return this.chart; }

  // ─── Data ───────────────────────────────────────────────────────────────
  setData(bars: OHLCVBar[], chartType: ChartType, theme: Theme): void {
    this.rawBars = bars;
    this.currentTheme = theme;
    const t = getTheme(theme);

    if (this.mainSeries)   { try { this.chart.removeSeries(this.mainSeries); } catch { /* ok */ } this.mainSeries = null; }
    if (this.volumeSeries) { try { this.chart.removeSeries(this.volumeSeries); } catch { /* ok */ } this.volumeSeries = null; }
    this.mainMarkersPlugin = null;

    this.mainSeries = this._createMainSeries(chartType, t);
    const mainData = bars.map(b => {
      if (chartType === 'line' || chartType === 'area' || chartType === 'baseline')
        return { time: b.time as Time, value: b.close };
      return { time: b.time as Time, open: b.open, high: b.high, low: b.low, close: b.close };
    });
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (this.mainSeries as ISeriesApi<any>).setData(mainData);

    // Volume sub-pane (pane index 1)
    this.volumeSeries = this.chart.addSeries(HistogramSeries, {
      priceFormat: { type: 'volume' },
      priceScaleId: 'volume',
    }, 1);

    const panes = this.chart.panes();
    if (panes[1]) panes[1].setHeight(60);

    this.volumeSeries.setData(bars.map(b => ({
      time:  b.time as Time,
      value: b.volume,
      color: b.close >= b.open ? `${t.upColor}88` : `${t.downColor}88`,
    })));

    this._reapplyAllPatternMarkers();
    this.chart.timeScale().fitContent();
  }

  private _createMainSeries(chartType: ChartType, t: ThemeTokens): ISeriesApi<SeriesType> {
    switch (chartType) {
      case 'candlestick':
        return this.chart.addSeries(CandlestickSeries, {
          upColor: t.upColor, downColor: t.downColor,
          borderUpColor: t.upColor, borderDownColor: t.downColor,
          wickUpColor: t.upColor, wickDownColor: t.downColor,
        });
      case 'bar':
        return this.chart.addSeries(BarSeries, { upColor: t.upColor, downColor: t.downColor });
      case 'line':
        return this.chart.addSeries(LineSeries, { color: '#2962FF', lineWidth: 2, crosshairMarkerVisible: true });
      case 'area':
        return this.chart.addSeries(AreaSeries, {
          lineColor: '#2962FF', topColor: '#2962FF44', bottomColor: '#2962FF00', lineWidth: 2,
        });
      case 'baseline':
        return this.chart.addSeries(BaselineSeries, {
          baseValue: { type: 'price', price: 0 },
          topLineColor: '#26a69a', topFillColor1: '#26a69a44', topFillColor2: '#26a69a00',
          bottomLineColor: '#ef5350', bottomFillColor1: '#ef535000', bottomFillColor2: '#ef535044',
        });
      default:
        return this.chart.addSeries(CandlestickSeries, {
          upColor: t.upColor, downColor: t.downColor,
          borderUpColor: t.upColor, borderDownColor: t.downColor,
          wickUpColor: t.upColor, wickDownColor: t.downColor,
        });
    }
  }

  // ─── Theme ──────────────────────────────────────────────────────────────
  setTheme(theme: Theme): void {
    const t = getTheme(theme);
    this.chart.applyOptions({
      layout:    { background: { type: ColorType.Solid, color: t.bgColor }, textColor: t.textColor },
      grid:      { vertLines: { color: t.gridColor }, horzLines: { color: t.gridColor } },
      crosshair: { vertLine: { color: t.crosshairColor, labelBackgroundColor: '#9598A1' }, horzLine: { color: t.crosshairColor, labelBackgroundColor: '#9598A1' } },
      timeScale: { borderColor: t.borderColor },
      rightPriceScale: { borderColor: t.borderColor },
    });
  }

  // ─── Indicators ─────────────────────────────────────────────────────────
  async addIndicator(config: IndicatorConfig): Promise<void> {
    if (this.indicators.has(config.id) || this.rawBars.length === 0) return;
    const def = getIndicatorDef(config.type);
    const seriesList: ISeriesApi<SeriesType>[] = [];

    try {
      const result = await calculateIndicator(config.type, this.rawBars, config.params);

      if (def?.returnsMarkers && result.markers && result.markers.length > 0) {
        this.patternMarkers.push({ id: config.id, markers: result.markers });
        this._reapplyAllPatternMarkers();
        this.indicators.set(config.id, { id: config.id, type: config.type, seriesList: [], alertLines: [], config });
        return;
      }

      const pane = def?.overlay ? 0 : this._getNextSubPane();
      const plotDefs = config.plots ?? def?.plots ?? [];

      for (const plotDef of plotDefs) {
        const plotData = result.plots[plotDef.id] as PlotData | undefined;
        if (!plotData || plotData.length === 0) continue;
        const filtered = plotData.filter(p => p.value !== null && !isNaN(p.value));
        if (filtered.length === 0) continue;

        let series: ISeriesApi<SeriesType>;
        if (plotDef.type === 'histogram') {
          series = this.chart.addSeries(HistogramSeries, { color: plotDef.color }, pane);
          const colorData = filtered.map((p, i, arr) => ({
            time:  p.time as Time,
            value: p.value,
            color: p.color ?? (i > 0 && p.value < arr[i - 1].value
              ? '#ef535088'
              : p.value < 0 ? '#ef535088' : `${plotDef.color}88`),
          }));
          series.setData(colorData);
        } else {
          series = this.chart.addSeries(LineSeries, {
            color:     plotDef.color,
            lineWidth: (plotDef.lineWidth ?? 1) as 1 | 2 | 3 | 4,
            priceLineVisible: false,
            lastValueVisible: true,
          }, pane);
          series.setData(filtered.map(p => ({ time: p.time as Time, value: p.value })));
        }

        const hlines = config.hlines ?? def?.hlines ?? [];
        for (const hl of hlines) {
          series.createPriceLine({ price: hl.price, color: hl.color, lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false });
        }
        seriesList.push(series);
      }

      this.indicators.set(config.id, { id: config.id, type: config.type, seriesList, paneIndex: pane, alertLines: [], config });
    } catch (e) {
      console.error(`[ChartV5 Indicator] ${config.type} error:`, e);
    }
  }

  removeIndicator(id: string): void {
    const entry = this.indicators.get(id);
    if (!entry) return;
    for (const s of entry.seriesList) { try { this.chart.removeSeries(s); } catch { /* ok */ } }
    this.patternMarkers = this.patternMarkers.filter(p => p.id !== id);
    this._reapplyAllPatternMarkers();
    this.indicators.delete(id);
  }

  private _reapplyAllPatternMarkers(): void {
    if (!this.mainSeries) return;
    const all = this.patternMarkers.flatMap(({ markers }) =>
      markers.map(m => ({
        time:     m.time as Time,
        position: m.position,
        shape:    m.shape,
        color:    m.color,
        text:     m.text,
      }))
    );
    if (!this.mainMarkersPlugin) {
      this.mainMarkersPlugin = createSeriesMarkers(this.mainSeries, all);
    } else {
      this.mainMarkersPlugin.setMarkers(all);
    }
  }

  private _getNextSubPane(): number {
    const used = new Set(Array.from(this.indicators.values()).map(e => e.paneIndex).filter(Boolean));
    let pane = 2;
    while (used.has(pane)) pane++;
    return pane;
  }

  // ─── 실시간 바 업데이트 ────────────────────────────────────────────────
  updateBar(bar: OHLCVBar): void {
    if (!this.mainSeries) return;
    const t = getTheme(this.currentTheme);

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (this.mainSeries as ISeriesApi<any>).update({
      time: bar.time as Time,
      open: bar.open, high: bar.high, low: bar.low, close: bar.close,
    });

    this.volumeSeries?.update({
      time:  bar.time as Time,
      value: bar.volume,
      color: bar.close >= bar.open ? `${t.upColor}88` : `${t.downColor}88`,
    });

    if (this.rawBars.length > 0) {
      const last = this.rawBars[this.rawBars.length - 1];
      if (last.time === bar.time) {
        this.rawBars[this.rawBars.length - 1] = bar;
      } else if (bar.time > last.time) {
        this.rawBars.push(bar);
      }
    }
  }

  async appendBar(bar: OHLCVBar): Promise<void> {
    if (!this.mainSeries) return;
    const t = getTheme(this.currentTheme);

    const last = this.rawBars[this.rawBars.length - 1];
    if (last && last.time === bar.time) {
      this.rawBars[this.rawBars.length - 1] = bar;
    } else {
      this.rawBars.push(bar);
    }

    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    (this.mainSeries as ISeriesApi<any>).update({
      time: bar.time as Time,
      open: bar.open, high: bar.high, low: bar.low, close: bar.close,
    });

    this.volumeSeries?.update({
      time:  bar.time as Time,
      value: bar.volume,
      color: bar.close >= bar.open ? `${t.upColor}88` : `${t.downColor}88`,
    });

    for (const [, entry] of Array.from(this.indicators.entries())) {
      if (!entry.config || entry.seriesList.length === 0) continue;
      if (entry.config.type) {
        try {
          const { plots } = await calculateIndicator(
            entry.config.type, this.rawBars, entry.config.params ?? {}
          );
          const plotValues = Object.values(plots);
          for (let i = 0; i < entry.seriesList.length && i < plotValues.length; i++) {
            const plotData = plotValues[i];
            if (!plotData?.length) continue;
            const lastPoint = plotData[plotData.length - 1];
            if (lastPoint) {
              // eslint-disable-next-line @typescript-eslint/no-explicit-any
              (entry.seriesList[i] as ISeriesApi<any>).update(lastPoint as any);
            }
          }
        } catch { /* 인디케이터 계산 실패 무시 */ }
      }
    }
  }

  async recalculateIndicators(newBars: OHLCVBar[]): Promise<void> {
    this.rawBars = newBars;

    const configs = Array.from(this.indicators.values()).map(e => e.config);

    for (const entry of Array.from(this.indicators.values())) {
      for (const s of entry.seriesList) { try { this.chart.removeSeries(s); } catch { /* ok */ } }
    }
    this.indicators.clear();
    this.patternMarkers = [];
    this._reapplyAllPatternMarkers();

    for (const cfg of configs) {
      await this.addIndicator(cfg);
    }
  }

  // ─── 커스텀 시리즈 (pre-calculated data) ────────────────────────────────
  addCustomSeriesGroup(
    id: string,
    seriesSpecs: Array<{
      data: Array<{ time: number; value: number; color?: string }>;
      seriesType: 'line' | 'histogram';
      color: string;
      lineWidth?: number;
    }>,
    hlines?: Array<{ price: number; color: string }>,
    pane?: number
  ): void {
    if (this.indicators.has(id)) return;
    const targetPane = pane ?? this._getNextSubPane();
    const seriesList: ISeriesApi<SeriesType>[] = [];

    for (const spec of seriesSpecs) {
      if (!spec.data || spec.data.length === 0) continue;
      const filtered = spec.data.filter(p => p.value !== null && !isNaN(p.value));
      if (filtered.length === 0) continue;

      let series: ISeriesApi<SeriesType>;
      if (spec.seriesType === 'histogram') {
        series = this.chart.addSeries(HistogramSeries, { color: spec.color }, targetPane);
        series.setData(filtered.map(p => ({
          time: p.time as Time,
          value: p.value,
          color: p.color ?? (p.value < 0 ? '#ef535088' : `${spec.color}88`),
        })));
      } else {
        series = this.chart.addSeries(LineSeries, {
          color: spec.color,
          lineWidth: (spec.lineWidth ?? 1) as 1 | 2 | 3 | 4,
          priceLineVisible: false,
          lastValueVisible: true,
        }, targetPane);
        series.setData(filtered.map(p => ({ time: p.time as Time, value: p.value })));
      }

      if (hlines) {
        for (const hl of hlines) {
          series.createPriceLine({ price: hl.price, color: hl.color, lineWidth: 1, lineStyle: LineStyle.Dashed, axisLabelVisible: false });
        }
      }
      seriesList.push(series);
    }

    this.indicators.set(id, {
      id, type: '__custom__', seriesList, paneIndex: targetPane, alertLines: [],
      config: { id, type: '__custom__', params: {} },
    });
  }

  // ─── Alerts ──────────────────────────────────────────────────────────────
  addAlert(price: number, label = ''): void {
    if (!this.mainSeries) return;
    const line = this.mainSeries.createPriceLine({
      price, color: '#FFB300', lineWidth: 1, lineStyle: LineStyle.Dashed,
      axisLabelVisible: true, title: label || `Alert ${price.toFixed(2)}`,
    });
    this.alertEntries.push({ price, label, line });
  }

  removeAlert(price: number): void {
    const idx = this.alertEntries.findIndex(a => Math.abs(a.price - price) < 0.0001);
    if (idx < 0) return;
    try { this.mainSeries?.removePriceLine(this.alertEntries[idx].line); } catch { /* ok */ }
    this.alertEntries.splice(idx, 1);
  }

  getAlerts(): Array<{ price: number; label: string }> {
    return this.alertEntries.map(({ price, label }) => ({ price, label }));
  }

  // ─── Auto Fibonacci ──────────────────────────────────────────────────────
  drawAutoFib(lookback = 50): void {
    if (!this.mainSeries || this.rawBars.length === 0) return;
    this._clearFibLines();
    const { levels } = calcAutoFib(this.rawBars, lookback);
    const COLORS: Record<string, string> = {
      '0%':     '#EF5350', '23.6%': '#FF9800', '38.2%': '#FFC107',
      '50%':    '#FFFFFF', '61.8%': '#4CAF50', '78.6%': '#2196F3',
      '100%':   '#9C27B0', '127.2%': '#607D8B', '161.8%': '#607D8B',
    };
    for (const l of levels) {
      const line = this.mainSeries.createPriceLine({
        price: l.price, color: COLORS[l.label] ?? '#607D8B',
        lineWidth: l.label === '0%' || l.label === '100%' ? 2 : 1,
        lineStyle: l.label === '50%' ? LineStyle.Dashed : LineStyle.Dotted,
        axisLabelVisible: true, title: `Fib ${l.label}`,
      });
      this.fibLines.push({ price: l.price, label: l.label, line });
    }
  }

  clearFib(): void { this._clearFibLines(); }
  private _clearFibLines(): void {
    for (const f of this.fibLines) { try { this.mainSeries?.removePriceLine(f.line); } catch { /* ok */ } }
    this.fibLines = [];
  }

  // ─── Crosshair ────────────────────────────────────────────────────────────
  subscribeCrosshair(cb: (p: MouseEventParams<Time>) => void): () => void {
    this.chart.subscribeCrosshairMove(cb);
    return () => this.chart.unsubscribeCrosshairMove(cb);
  }

  // ─── Layout ───────────────────────────────────────────────────────────────
  fitContent(): void { this.chart.timeScale().fitContent(); }
  setLogScale(enabled: boolean): void {
    this.mainSeries?.applyOptions({ priceScale: { mode: enabled ? 1 : 0 } } as Parameters<ISeriesApi<SeriesType>['applyOptions']>[0]);
  }
  resize(w: number, h: number): void { this.chart.resize(w, h); }

  // ─── Compare overlay ──────────────────────────────────────────────────────
  addCompareSeries(symbol: string, bars: OHLCVBar[], color: string): void {
    if (this.compareSeriesMap.has(symbol)) return;
    const s = this.chart.addSeries(LineSeries, { color, lineWidth: 2, priceLineVisible: false });
    const base = bars[0]?.close ?? 1;
    s.setData(bars.map(b => ({ time: b.time as Time, value: (b.close / base - 1) * 100 })));
    this.compareSeriesMap.set(symbol, s);
  }

  removeCompareSeries(symbol: string): void {
    const s = this.compareSeriesMap.get(symbol);
    if (s) { try { this.chart.removeSeries(s); } catch { /* ok */ } this.compareSeriesMap.delete(symbol); }
  }

  // ─── Coordinate conversion ───────────────────────────────────────────────
  timeToX(time: number): number | null {
    const coord = this.chart.timeScale().timeToCoordinate(time as Time);
    return coord === null ? null : coord;
  }

  priceToY(price: number): number | null {
    const coord = this.mainSeries?.priceToCoordinate(price);
    return coord === null || coord === undefined ? null : coord;
  }

  xToTime(x: number): number | null {
    const t = this.chart.timeScale().coordinateToTime(x);
    return t === null ? null : (t as number);
  }

  yToPrice(y: number): number | null {
    const p = this.mainSeries?.coordinateToPrice(y);
    return p === null || p === undefined ? null : p;
  }

  subscribeViewport(cb: () => void): () => void {
    this.chart.timeScale().subscribeVisibleTimeRangeChange(cb);
    this.chart.timeScale().subscribeVisibleLogicalRangeChange(cb);
    return () => {
      try { this.chart.timeScale().unsubscribeVisibleTimeRangeChange(cb); } catch { /* ok */ }
      try { this.chart.timeScale().unsubscribeVisibleLogicalRangeChange(cb); } catch { /* ok */ }
    };
  }

  // ─── Destroy ─────────────────────────────────────────────────────────────
  destroy(): void {
    if (this._clickHandler) this.chart.unsubscribeClick(this._clickHandler);
    this.chart.remove();
  }
}
