/**
 * WebSocket + /upbit/analyze 재계산 결과를 차트 시리즈에 직접 반영하기 위한 데이터 빌더
 * (차트 dispose 없이 setData만 사용)
 */
import type { Time } from 'lightweight-charts';
import type { CandleData, IndicatorData } from '../services/backtestApi';
import { chartTimeValueToUnixSeconds } from '../utils/chartCandleTime';

/** CandlestickChart buildCandleDataRaw / maxUnixFromCandles와 동일 규칙 */
const toUnix = (t: string | number | undefined): number => chartTimeValueToUnixSeconds(t) ?? 0;

/** 캔들 봉 시각(초) — time 외 date/timestamp 폴백 */
function candleOpenTimeUnix(c: CandleData): number {
  return toUnix(c.time ?? (c as any).date ?? (c as any).timestamp);
}

/** 표시 캔들 중 가장 늦은 봉의 Unix 초 (서브지표·재계산: 미래 행만 제외 — Set.has는 타임존/형식 불일치로 전부 탈락할 수 있음) */
export function maxUnixFromCandles(displayCandles: CandleData[]): number {
  let m = 0;
  for (const c of displayCandles) {
    const u = candleOpenTimeUnix(c);
    if (u > m) m = u;
  }
  return m;
}

/**
 * 서브차트 시리즈: 표시 캔들과 길이가 같으면 각 포인트 time을 해당 캔들 봉 시각으로 덮어씀.
 * (/upbit/analyze 지표 time 문자열이 캔들과 어긋나 일목 미래축에 선이 닿는 현상 방지)
 */
export function alignSubChartSeriesToDisplayCandles<T extends { time: Time }>(
  displayCandles: CandleData[],
  points: T[]
): T[] {
  if (points.length === 0 || displayCandles.length === 0) return points;
  const maxSec = maxUnixFromCandles(displayCandles);
  if (points.length === displayCandles.length) {
    return points.map((p, i) => ({
      ...p,
      time: candleOpenTimeUnix(displayCandles[i]) as Time,
    }));
  }
  if (maxSec <= 0) {
    return points.filter((p) => (p.time as number) > 0);
  }
  return points.filter((p) => {
    const tu = p.time as number;
    return tu > 0 && tu <= maxSec;
  });
}

function passesSubChartRowTime(tu: number, maxDisplayCandleSec: number): boolean {
  if (tu <= 0 || !isFinite(tu)) return false;
  if (maxDisplayCandleSec > 0 && tu > maxDisplayCandleSec) return false;
  return true;
}

export function buildMaLineDataFromApi(
  displayIndicators: IndicatorData[],
  ma: { id: string; period: number },
  maxDisplayCandleSec: number
): { time: Time; value: number }[] {
  const keyPeriod = `ma${ma.period}`;
  const out: { time: Time; value: number }[] = [];
  for (const ind of displayIndicators) {
    const mv = ind.maValues as Record<string, number> | undefined;
    const v =
      mv?.[keyPeriod] ??
      mv?.[ma.id] ??
      (ind as any)[`sma${ma.period}`] ??
      (ind as any)[`ma${ma.period}`];
    if (v == null || typeof v !== 'number' || !isFinite(v)) continue;
    const timeVal = ind.time ?? ind.timestamp ?? ind.date;
    const tu = toUnix(timeVal as string);
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    out.push({ time: tu as Time, value: v });
  }
  return out.sort((a, b) => (a.time as number) - (b.time as number));
}

export function buildBollingerFromIndicators(displayIndicators: IndicatorData[]): {
  upper: { time: Time; value: number }[];
  middle: { time: Time; value: number }[];
  lower: { time: Time; value: number }[];
  bandBg: Array<{ time: number; upper: number; lower: number }>;
} {
  const upper: { time: Time; value: number }[] = [];
  const middle: { time: Time; value: number }[] = [];
  const lower: { time: Time; value: number }[] = [];
  const bandBg: Array<{ time: number; upper: number; lower: number }> = [];

  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (d.bollingerUpper != null && isFinite(d.bollingerUpper)) {
      upper.push({ time: tu as Time, value: d.bollingerUpper });
    }
    if (d.bollingerMiddle != null && isFinite(d.bollingerMiddle)) {
      middle.push({ time: tu as Time, value: d.bollingerMiddle });
    }
    if (d.bollingerLower != null && isFinite(d.bollingerLower)) {
      lower.push({ time: tu as Time, value: d.bollingerLower });
    }
    if (
      d.bollingerUpper != null &&
      d.bollingerLower != null &&
      isFinite(d.bollingerUpper) &&
      isFinite(d.bollingerLower)
    ) {
      bandBg.push({ time: tu, upper: d.bollingerUpper, lower: d.bollingerLower });
    }
  }
  const sortFn = (a: { time: Time }, b: { time: Time }) => (a.time as number) - (b.time as number);
  upper.sort(sortFn);
  middle.sort(sortFn);
  lower.sort(sortFn);
  bandBg.sort((a, b) => a.time - b.time);
  return { upper, middle, lower, bandBg };
}

/** 일목 오버레이용 — API 행에서 전환/기준/선행만 추출 (선행은 displacement는 호출부에서 처리) */
export function buildIchimokuRowSeries(
  rows: IndicatorData[],
  maxDisplayCandleSec: number,
  opts: { senkouDisplacement: number }
): {
  tenkan: { time: number; value: number }[];
  kijun: { time: number; value: number }[];
  senkouA: { time: number; value: number }[];
  senkouB: { time: number; value: number }[];
  cloud: Array<{ time: number; senkouA: number; senkouB: number }>;
} {
  const { senkouDisplacement } = opts;
  const parsed = rows
    .map((d) => {
      const t = toUnix(d.time ?? d.timestamp);
      return {
        time: t,
        tenkan: d.ichimokuTenkan,
        kijun: d.ichimokuKijun,
        senkouA: d.ichimokuSenkouA,
        senkouB: d.ichimokuSenkouB,
      };
    })
    .filter((x) => x.time > 0);

  const ichimokuTimes = parsed.map((p) => p.time);
  const lastTime = ichimokuTimes.length > 0 ? ichimokuTimes[ichimokuTimes.length - 1] : 0;
  const intervalSec =
    ichimokuTimes.length >= 2 ? lastTime - ichimokuTimes[ichimokuTimes.length - 2] : 60;

  const tenkan: { time: number; value: number }[] = [];
  const kijun: { time: number; value: number }[] = [];
  const senkouA: { time: number; value: number }[] = [];
  const senkouB: { time: number; value: number }[] = [];
  const cloud: Array<{ time: number; senkouA: number; senkouB: number }> = [];

  const useDisp = senkouDisplacement > 0 && parsed.length >= 2;

  parsed.forEach((d, i) => {
    if (d.tenkan != null && isFinite(d.tenkan) && passesSubChartRowTime(d.time, maxDisplayCandleSec)) {
      tenkan.push({ time: d.time, value: d.tenkan });
    }
    if (d.kijun != null && isFinite(d.kijun) && passesSubChartRowTime(d.time, maxDisplayCandleSec)) {
      kijun.push({ time: d.time, value: d.kijun });
    }

    if (d.senkouA != null && d.senkouB != null && isFinite(d.senkouA) && isFinite(d.senkouB)) {
      let targetTime = d.time;
      if (useDisp) {
        targetTime =
          i + senkouDisplacement < parsed.length
            ? ichimokuTimes[i + senkouDisplacement]
            : lastTime + (i + senkouDisplacement - parsed.length + 1) * intervalSec;
      }
      senkouA.push({ time: targetTime, value: d.senkouA });
      senkouB.push({ time: targetTime, value: d.senkouB });
      cloud.push({ time: targetTime, senkouA: d.senkouA, senkouB: d.senkouB });
    }
  });

  return { tenkan, kijun, senkouA, senkouB, cloud };
}

export function buildVolumeHistogramData(
  displayCandles: CandleData[],
  colorSettings?: { volumeRising?: string; volumeFalling?: string }
): { time: Time; value: number; color: string }[] {
  const rising = colorSettings?.volumeRising || '#ef5350';
  const falling = colorSettings?.volumeFalling || '#2196f3';
  return displayCandles
    .filter((d) => d.volume != null)
    .map((d) => {
      const tu = toUnix(d.time);
      if (tu <= 0) return null;
      const isRising = d.close >= d.open;
      return {
        time: tu as Time,
        value: d.volume!,
        color: isRising ? rising : falling,
      };
    })
    .filter((x): x is NonNullable<typeof x> => x != null);
}

export function buildMacdSeriesData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): {
  histogram: { time: Time; value: number; color: string }[];
  macdLine: { time: Time; value: number }[];
  signalLine: { time: Time; value: number }[];
} {
  const pos = '#26a69a';
  const neg = '#ef5350';
  const histogram: { time: Time; value: number; color: string }[] = [];
  const macdLine: { time: Time; value: number }[] = [];
  const signalLine: { time: Time; value: number }[] = [];

  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    const h = d.macdHistogram ?? (d as any).histogram;
    if (h != null && isFinite(h)) {
      histogram.push({ time: tu as Time, value: h, color: h >= 0 ? pos : neg });
    }
    if (d.macdLine != null && isFinite(d.macdLine)) {
      macdLine.push({ time: tu as Time, value: d.macdLine });
    }
    const sig = d.signalLine ?? (d as any).signal;
    if (sig != null && isFinite(sig)) {
      signalLine.push({ time: tu as Time, value: sig });
    }
  }
  const st = (a: { time: Time }, b: { time: Time }) => (a.time as number) - (b.time as number);
  histogram.sort(st);
  macdLine.sort(st);
  signalLine.sort(st);
  return { histogram, macdLine, signalLine };
}

export function buildRsiLineData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): { time: Time; value: number }[] {
  const out: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    if (d.rsi != null && isFinite(d.rsi)) {
      out.push({ time: tu as Time, value: d.rsi });
    }
  }
  return out.sort((a, b) => (a.time as number) - (b.time as number));
}

export function buildStochasticSeriesData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): {
  kLine: { time: Time; value: number }[];
  dLine: { time: Time; value: number }[];
} {
  const kLine: { time: Time; value: number }[] = [];
  const dLine: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    const k = d.stochasticK ?? (d as any).stochK;
    const dd = d.stochasticD ?? (d as any).stochD;
    if (k != null && isFinite(k)) kLine.push({ time: tu as Time, value: k });
    if (dd != null && isFinite(dd)) dLine.push({ time: tu as Time, value: dd });
  }
  const st = (a: { time: Time }, b: { time: Time }) => (a.time as number) - (b.time as number);
  kLine.sort(st);
  dLine.sort(st);
  return { kLine, dLine };
}

export function buildCciLineData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): { time: Time; value: number }[] {
  const out: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    const v = d.cci13 ?? (d as any).cci;
    if (v != null && isFinite(v)) out.push({ time: tu as Time, value: v });
  }
  return out.sort((a, b) => (a.time as number) - (b.time as number));
}

export function buildObvSeriesData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): {
  obv: { time: Time; value: number }[];
  signal: { time: Time; value: number }[];
} {
  const obv: { time: Time; value: number }[] = [];
  const signal: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    if (d.obv != null && isFinite(d.obv)) obv.push({ time: tu as Time, value: d.obv });
    if (d.obvSignal != null && isFinite(d.obvSignal)) signal.push({ time: tu as Time, value: d.obvSignal });
  }
  const st = (a: { time: Time }, b: { time: Time }) => (a.time as number) - (b.time as number);
  obv.sort(st);
  signal.sort(st);
  return { obv, signal };
}

export function buildAdxLineData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): { time: Time; value: number }[] {
  const out: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    if (d.adx != null && isFinite(d.adx)) out.push({ time: tu as Time, value: d.adx });
  }
  return out.sort((a, b) => (a.time as number) - (b.time as number));
}

export function buildDmiSeriesData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): {
  plusDi: { time: Time; value: number }[];
  minusDi: { time: Time; value: number }[];
} {
  const plusDi: { time: Time; value: number }[] = [];
  const minusDi: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    const p = (d as any).plusDI ?? (d as any).plusDi;
    const m = (d as any).minusDI ?? (d as any).minusDi;
    if (p != null && isFinite(p)) plusDi.push({ time: tu as Time, value: p });
    if (m != null && isFinite(m)) minusDi.push({ time: tu as Time, value: m });
  }
  const st = (a: { time: Time }, b: { time: Time }) => (a.time as number) - (b.time as number);
  plusDi.sort(st);
  minusDi.sort(st);
  return { plusDi, minusDi };
}

export function buildWilliamsRLineData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): { time: Time; value: number }[] {
  const out: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    if (d.williamsR != null && isFinite(d.williamsR)) out.push({ time: tu as Time, value: d.williamsR });
  }
  return out.sort((a, b) => (a.time as number) - (b.time as number));
}

export function buildTrixSeriesData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): {
  trix: { time: Time; value: number }[];
  signal: { time: Time; value: number }[];
} {
  const trix: { time: Time; value: number }[] = [];
  const signal: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    if (d.trix != null && isFinite(d.trix)) trix.push({ time: tu as Time, value: d.trix });
    if (d.trixSignal != null && isFinite(d.trixSignal)) signal.push({ time: tu as Time, value: d.trixSignal });
  }
  const st = (a: { time: Time }, b: { time: Time }) => (a.time as number) - (b.time as number);
  trix.sort(st);
  signal.sort(st);
  return { trix, signal };
}

export function buildBwiLineData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): { time: Time; value: number }[] {
  const out: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    if (d.bwi != null && isFinite(d.bwi)) out.push({ time: tu as Time, value: d.bwi });
  }
  return out.sort((a, b) => (a.time as number) - (b.time as number));
}

export function buildVolumeOscLineData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): { time: Time; value: number }[] {
  const out: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    if (d.volumeOsc != null && isFinite(d.volumeOsc)) out.push({ time: tu as Time, value: d.volumeOsc });
  }
  return out.sort((a, b) => (a.time as number) - (b.time as number));
}

export function buildVrSeriesData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): {
  vr: { time: Time; value: number }[];
  vr2: { time: Time; value: number }[];
} {
  const vr: { time: Time; value: number }[] = [];
  const vr2: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    if (d.vr != null && isFinite(d.vr)) vr.push({ time: tu as Time, value: d.vr });
    if (d.vr2 != null && isFinite(d.vr2)) vr2.push({ time: tu as Time, value: d.vr2 });
  }
  const st = (a: { time: Time }, b: { time: Time }) => (a.time as number) - (b.time as number);
  vr.sort(st);
  vr2.sort(st);
  return { vr, vr2 };
}

/** 서브차트 이격도와 동일: 원본(95~105 부근)에서 95를 빼서 표시 */
const DISPARITY_CHART_OFFSET = 95;

export function buildDisparityTransformedSeries(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): {
  ultraShort: { time: Time; value: number }[];
  short: { time: Time; value: number }[];
  mid: { time: Time; value: number }[];
  long: { time: Time; value: number }[];
} {
  const ultraShort: { time: Time; value: number }[] = [];
  const short: { time: Time; value: number }[] = [];
  const mid: { time: Time; value: number }[] = [];
  const long: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    const u = (d as any).disparityUltraShort;
    const s = (d as any).disparityShort;
    const m = (d as any).disparityMid;
    const l = (d as any).disparityLong;
    if (u != null && isFinite(u)) ultraShort.push({ time: tu as Time, value: u - DISPARITY_CHART_OFFSET });
    if (s != null && isFinite(s)) short.push({ time: tu as Time, value: s - DISPARITY_CHART_OFFSET });
    if (m != null && isFinite(m)) mid.push({ time: tu as Time, value: m - DISPARITY_CHART_OFFSET });
    if (l != null && isFinite(l)) long.push({ time: tu as Time, value: l - DISPARITY_CHART_OFFSET });
  }
  const st = (a: { time: Time }, b: { time: Time }) => (a.time as number) - (b.time as number);
  ultraShort.sort(st);
  short.sort(st);
  mid.sort(st);
  long.sort(st);
  return { ultraShort, short, mid, long };
}

export function buildNewPsychologicalLineData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): { time: Time; value: number }[] {
  const out: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    const v = d.newPsychological ?? (d as any).newPsy;
    if (v != null && isFinite(v)) out.push({ time: tu as Time, value: v });
  }
  return out.sort((a, b) => (a.time as number) - (b.time as number));
}

export function buildInvestPsychologicalLineData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): { time: Time; value: number }[] {
  const out: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    const v = d.investPsychological ?? (d as any).investPsy;
    if (v != null && isFinite(v)) out.push({ time: tu as Time, value: v });
  }
  return out.sort((a, b) => (a.time as number) - (b.time as number));
}

export function buildCciSignalLineData(
  displayIndicators: IndicatorData[],
  maxDisplayCandleSec = 0
): { time: Time; value: number }[] {
  const out: { time: Time; value: number }[] = [];
  for (const d of displayIndicators) {
    const tu = toUnix(d.time ?? d.timestamp);
    if (tu <= 0) continue;
    if (!passesSubChartRowTime(tu, maxDisplayCandleSec)) continue;
    const v = (d as any).cciSignal;
    if (v != null && isFinite(v)) out.push({ time: tu as Time, value: v });
  }
  return out.sort((a, b) => (a.time as number) - (b.time as number));
}
