/**
 * Frontend Indicator Calculator
 *
 * lightweight-charts-indicators-main 라이브러리를 사용하여
 * 백엔드 없이 프론트엔드에서 직접 보조지표를 계산합니다.
 */

import type { Bar } from 'oakscriptjs';
import {
  calculateRSI as calcRSI,
  calculateMACD as calcMACD,
  calculateStochastic as calcStoch,
  calculateDMI as calcDMI,
  calculateCCI as calcCCI,
  calculateOBV as calcOBV,
  calculateWilliamsR as calcWilliamsR,
  calculateTRIX as calcTRIX,
  calculateBB as calcBB,
  calculateVolumeOsc as calcVolumeOsc,
  calculateIchimoku as calcIchimoku,
  calculateMomentum as calcMomentum,
  calculateSMA as calcSMA,
} from 'lightweight-charts-indicators';

import type { CandleData, IndicatorData } from '../../services/backtestApi';

// ─────────────────────────────────────────────────────────────────────────────
// Types
// ─────────────────────────────────────────────────────────────────────────────

export interface FrontendIndicatorSettings {
  maPeriods?: number[];
  bollingerPeriod?: number;
  bollingerStdDev?: number;
  rsiPeriod?: number;
  macdFastPeriod?: number;
  macdSlowPeriod?: number;
  macdSignalPeriod?: number;
  stochasticKPeriod?: number;
  stochasticDPeriod?: number;
  stochasticSlowing?: number;
  adxPeriod?: number;
  dmiPeriod?: number;
  cciPeriod?: number;
  cciSignalPeriod?: number;
  obvPeriod?: number;
  obvSignalPeriod?: number;
  williamsRPeriod?: number;
  trixPeriod?: number;
  trixSignalPeriod?: number;
  volumeOscShortPeriod?: number;
  volumeOscLongPeriod?: number;
  disparityUltraShortPeriod?: number;
  disparityShortPeriod?: number;
  disparityMidPeriod?: number;
  disparityLongPeriod?: number;
  newPsychologicalPeriod?: number;
  investPsychologicalPeriod?: number;
    vrPeriod?: number;
  vr2Enabled?: boolean;
  vr2Period?: number;
  ichimokuSettings?: {
    conversionPeriod?: number;
    basePeriod?: number;
    laggingSpanPeriod?: number;
    displacement?: number;
  };
}

// ─────────────────────────────────────────────────────────────────────────────
// Conversion: CandleData → Bar (oakscriptjs format)
// ─────────────────────────────────────────────────────────────────────────────

function toBar(c: CandleData): Bar {
  return {
    time: c.time as any,
    open: c.open,
    high: c.high,
    low: c.low,
    close: c.close,
    volume: c.volume,
  };
}

// ─────────────────────────────────────────────────────────────────────────────
// Custom indicator helpers (not provided by the library)
// ─────────────────────────────────────────────────────────────────────────────

/** 단순 SMA 계산 (number[] 입력) */
function smaArr(src: number[], period: number): number[] {
  return src.map((_, i) => {
    if (i < period - 1) return NaN;
    let sum = 0;
    for (let j = i - period + 1; j <= i; j++) sum += src[j];
    return sum / period;
  });
}

/** 이격도 (Disparity Index): (close - SMA(n)) / SMA(n) * 100 */
function calcDisparity(closes: number[], period: number): number[] {
  const ma = smaArr(closes, period);
  return closes.map((c, i) => (isNaN(ma[i]) ? NaN : ((c - ma[i]) / ma[i]) * 100));
}

/** 신심리도 (New Psychological): 최근 N봉 중 종가가 이전 봉 종가보다 높은 비율 * 100 */
function calcNewPsychological(closes: number[], period: number): number[] {
  return closes.map((_, i) => {
    if (i < period) return NaN;
    let upCount = 0;
    for (let j = i - period + 1; j <= i; j++) {
      if (closes[j] > closes[j - 1]) upCount++;
    }
    return (upCount / period) * 100;
  });
}

/** 투자심리도 (Invest Psychological): 거래량 가중 신심리도 */
function calcInvestPsychological(closes: number[], volumes: number[], period: number): number[] {
  return closes.map((_, i) => {
    if (i < period) return NaN;
    let upVol = 0;
    let totalVol = 0;
    for (let j = i - period + 1; j <= i; j++) {
      totalVol += volumes[j];
      if (closes[j] > closes[j - 1]) upVol += volumes[j];
    }
    return totalVol === 0 ? NaN : (upVol / totalVol) * 100;
  });
}

/** BWI (Bollinger Width Index): (upper - lower) / middle * 100 */
function calcBWI(
  upper: number[],
  middle: number[],
  lower: number[]
): number[] {
  return upper.map((u, i) => {
    const m = middle[i];
    const l = lower[i];
    if (isNaN(u) || isNaN(m) || isNaN(l) || m === 0) return NaN;
    return ((u - l) / m) * 100;
  });
}

/** VR (Volume Ratio): up-volume / down-volume 비율 */
function calcVR(closes: number[], volumes: number[], period: number): number[] {
  return closes.map((_, i) => {
    if (i < period) return NaN;
    let upVol = 0;
    let downVol = 0;
    for (let j = i - period + 1; j <= i; j++) {
      if (closes[j] > closes[j - 1]) upVol += volumes[j];
      else if (closes[j] < closes[j - 1]) downVol += volumes[j];
    }
    if (downVol === 0) return NaN;
    return (upVol / downVol) * 100;
  });
}

// ─────────────────────────────────────────────────────────────────────────────
// Main: Calculate all indicators from candle data
// ─────────────────────────────────────────────────────────────────────────────

export function calculateAllIndicators(
  candles: CandleData[],
  settings: FrontendIndicatorSettings = {}
): IndicatorData[] {
  if (!candles.length) return [];

  const bars = candles.map(toBar);

  const {
    maPeriods = [5, 10, 20, 60, 120],
    bollingerPeriod = 20,
    bollingerStdDev = 2,
    rsiPeriod = 9,
    macdFastPeriod = 12,
    macdSlowPeriod = 26,
    macdSignalPeriod = 9,
    stochasticKPeriod = 12,
    stochasticDPeriod = 5,
    stochasticSlowing = 5,
    adxPeriod = 14,
    dmiPeriod = 14,
    cciPeriod = 13,
    cciSignalPeriod = 10,
    obvSignalPeriod = 10,
    williamsRPeriod = 14,
    trixPeriod = 12,
    trixSignalPeriod = 9,
    volumeOscShortPeriod = 5,
    volumeOscLongPeriod = 10,
    disparityUltraShortPeriod = 5,
    disparityShortPeriod = 20,
    disparityMidPeriod = 60,
    disparityLongPeriod = 120,
    newPsychologicalPeriod = 12,
    investPsychologicalPeriod = 24,
    vrPeriod = 10,
    vr2Enabled = false,
    vr2Period = 10,
    ichimokuSettings = {},
  } = settings;

  const closes = candles.map((c) => c.close);
  const volumes = candles.map((c) => c.volume);

  // ── 라이브러리 지표 계산 ──────────────────────────────────────────────────

  const rsiResult = calcRSI(bars, { length: rsiPeriod });
  const rsiValues = rsiResult.plots['plot0'];

  const macdResult = calcMACD(bars, {
    fastLength: macdFastPeriod,
    slowLength: macdSlowPeriod,
    signalLength: macdSignalPeriod,
  });
  const macdHistValues = macdResult.plots['plot0']; // histogram
  const macdLineValues = macdResult.plots['plot1']; // MACD line
  const macdSignalValues = macdResult.plots['plot2']; // Signal line

  const stochResult = calcStoch(bars, {
    periodK: stochasticKPeriod,
    smoothK: stochasticSlowing,
    periodD: stochasticDPeriod,
  });
  const stochKValues = stochResult.plots['plot0'];
  const stochDValues = stochResult.plots['plot1'];

  // DMI returns ADX(plot0), +DI(plot1), -DI(plot2)
  const dmiResult = calcDMI(bars, {
    adxSmoothing: adxPeriod,
    diLength: dmiPeriod,
  });
  const adxValues = dmiResult.plots['plot0'];
  const plusDIValues = dmiResult.plots['plot1'];
  const minusDIValues = dmiResult.plots['plot2'];

  const cciResult = calcCCI(bars, { length: cciPeriod });
  const cciValues = cciResult.plots['plot0'];
  const cciRawArr = cciValues.map((d) => (d ? (isNaN(d.value) ? NaN : d.value) : NaN));
  const cciSignalArr = smaArr(cciRawArr, cciSignalPeriod);

  const obvResult = calcOBV(bars);
  const obvValues = obvResult.plots['plot0'];
  const obvRawArr = obvValues.map((d) => (d ? (isNaN(d.value) ? NaN : d.value) : NaN));
  const obvSignalArr = smaArr(obvRawArr, obvSignalPeriod);

  const wrResult = calcWilliamsR(bars, { length: williamsRPeriod });
  const wrValues = wrResult.plots['plot0'];

  const trixResult = calcTRIX(bars, { length: trixPeriod });
  const trixValues = trixResult.plots['plot0'];
  /** 라이브러리가 ROC에 1e4를 곱해 반환 — 차트·게이지는 이 스케일과 동일 */
  const trixRawArr = trixValues.map((d) => (d ? (isNaN(d.value) ? NaN : d.value) : NaN));
  const trixSignalArr = smaArr(trixRawArr, trixSignalPeriod);

  const bbResult = calcBB(bars, {
    length: bollingerPeriod,
    mult: bollingerStdDev,
    maType: 'SMA',
  });
  const bbBasis = bbResult.plots['plot0']; // middle
  const bbUpper = bbResult.plots['plot1']; // upper
  const bbLower = bbResult.plots['plot2']; // lower

  const volOscResult = calcVolumeOsc(bars, {
    shortLength: volumeOscShortPeriod,
    longLength: volumeOscLongPeriod,
  });
  const volOscValues = volOscResult.plots['plot0'];

  const momentumResult = calcMomentum(bars, { length: 10 });
  const momentumValues = momentumResult.plots['plot0'];

  const ichimokuResult = calcIchimoku(bars, {
    conversionPeriods: ichimokuSettings.conversionPeriod ?? 9,
    basePeriods: ichimokuSettings.basePeriod ?? 26,
    laggingSpan2Periods: ichimokuSettings.laggingSpanPeriod ?? 52,
    displacement: ichimokuSettings.displacement ?? 26,
  });
  const tenkanValues = ichimokuResult.plots['plot0']; // conversion
  const kijunValues = ichimokuResult.plots['plot1'];  // base
  const chikouValues = ichimokuResult.plots['plot2']; // lagging
  const senkouAValues = ichimokuResult.plots['plot3']; // leading A
  const senkouBValues = ichimokuResult.plots['plot4']; // leading B

  // ── MA 계산 ──────────────────────────────────────────────────────────────
  // maPeriods별 SMA 계산
  const maResultsMap: Record<string, number[]> = {};
  for (const period of maPeriods) {
    const smaResult = calcSMA(bars, { len: period });
    maResultsMap[`ma${period}`] = smaResult.plots['plot0'].map((d) =>
      d ? (isNaN(d.value) ? NaN : d.value) : NaN
    );
  }

  // ── 커스텀 지표 계산 ──────────────────────────────────────────────────────

  const bbUpperArr = bbUpper.map((d) => (d ? d.value : NaN));
  const bbMiddleArr = bbBasis.map((d) => (d ? d.value : NaN));
  const bbLowerArr = bbLower.map((d) => (d ? d.value : NaN));
  const bwiArr = calcBWI(bbUpperArr, bbMiddleArr, bbLowerArr);

  const vrArr = calcVR(closes, volumes, vrPeriod);
  const vr2Arr = vr2Enabled ? calcVR(closes, volumes, vr2Period) : [];

  const disparityUSArr = calcDisparity(closes, disparityUltraShortPeriod);
  const disparityShortArr = calcDisparity(closes, disparityShortPeriod);
  const disparityMidArr = calcDisparity(closes, disparityMidPeriod);
  const disparityLongArr = calcDisparity(closes, disparityLongPeriod);

  const newPsyArr = calcNewPsychological(closes, newPsychologicalPeriod);
  const investPsyArr = calcInvestPsychological(closes, volumes, investPsychologicalPeriod);

  // ── 결과 조합 ─────────────────────────────────────────────────────────────

  return candles.map((candle, i) => {
    // MA 동적 값 맵 생성
    const maValues: Record<string, number> = {};
    for (const [key, arr] of Object.entries(maResultsMap)) {
      const v = arr[i];
      if (!isNaN(v)) maValues[key] = v;
    }

    const get = (arr: { value: number }[] | undefined, idx: number): number | undefined => {
      if (!arr) return undefined;
      const v = arr[idx]?.value;
      return v !== undefined && !isNaN(v) ? v : undefined;
    };

    const getNum = (arr: number[], idx: number): number | undefined => {
      const v = arr[idx];
      return !isNaN(v) ? v : undefined;
    };

    const row: IndicatorData = {
      time: candle.time,
      timestamp: candle.time,
      open: candle.open,
      high: candle.high,
      low: candle.low,
      close: candle.close,
      volume: candle.volume,

      // RSI
      rsi: get(rsiValues, i),

      // MACD
      macdLine: get(macdLineValues, i),
      signalLine: get(macdSignalValues, i),
      macdHistogram: get(macdHistValues, i),
      histogram: get(macdHistValues, i),
      macd: get(macdHistValues, i),
      signal: get(macdSignalValues, i),

      // Stochastic
      stochK: get(stochKValues, i),
      stochD: get(stochDValues, i),

      // ADX / DMI
      adx: get(adxValues, i),
      plusDI: get(plusDIValues, i),
      minusDI: get(minusDIValues, i),

      // CCI
      cci: get(cciValues, i),

      // OBV
      obv: getNum(obvRawArr, i),
      obvSignal: getNum(obvSignalArr, i),

      // Williams %R
      williamsR: get(wrValues, i),

      // TRIX
      trix: getNum(trixRawArr, i),
      trixSignal: getNum(trixSignalArr, i),

      // Bollinger Bands
      bollingerUpper: getNum(bbUpperArr, i),
      bollingerMiddle: getNum(bbMiddleArr, i),
      bollingerLower: getNum(bbLowerArr, i),

      // Volume Oscillator
      volumeOsc: get(volOscValues, i),

      // Momentum
      momentum: get(momentumValues, i),

      // Ichimoku
      ichimokuTenkan: get(tenkanValues, i),
      ichimokuKijun: get(kijunValues, i),
      ichimokuChikou: get(chikouValues, i),
      ichimokuSenkouA: get(senkouAValues, i),
      ichimokuSenkouB: get(senkouBValues, i),

      // BWI
      bwi: getNum(bwiArr, i),

      // VR
      vr: getNum(vrArr, i),
      vr2: vr2Enabled ? getNum(vr2Arr, i) : undefined,

      // 이격도
      disparity: getNum(disparityUSArr, i),

      // 심리도
      newPsychological: getNum(newPsyArr, i),
      newPsy: getNum(newPsyArr, i),
      investPsychological: getNum(investPsyArr, i),
      investPsy: getNum(investPsyArr, i),

      // MA 동적 값
      maValues,
    };

    // CCI signal alias
    row.cciSignal = getNum(cciSignalArr, i);

    // 개별 이격도
    row.disparityUltraShort = getNum(disparityUSArr, i);
    row.disparityShort = getNum(disparityShortArr, i);
    row.disparityMid = getNum(disparityMidArr, i);
    row.disparityLong = getNum(disparityLongArr, i);

    // SMA 별칭 (호환성)
    for (const period of maPeriods) {
      const key = `ma${period}`;
      const val = maValues[key];
      if (val !== undefined) {
        row[`sma${period}`] = val;
        row[key] = val;
      }
    }

    return row;
  });
}
