import React, { useEffect, useRef, forwardRef, useImperativeHandle, useState, useCallback, useMemo } from 'react';

import {
  createChart,
  ColorType,
  IChartApi,
  ISeriesApi,
  Time,
  LineStyle,
  LineWidth,
  CrosshairMode
} from 'lightweight-charts';
import { Box, Typography } from '@mui/material';
import { useAppTheme } from '../contexts/ThemeContext';
import { useIndicatorSettings } from '../contexts/IndicatorSettingsContext';
import { useChartColors } from '../contexts/ChartColorContext';

export type IndicatorType = 'volume' | 'macd' | 'rsi' | 'stochastic' | 'momentum' | 'cci' | 'williamsR' | 'dmi' | 'adx' | 'obv' | 'trix' | 'newPsychological' | 'investPsychological' | 'disparity' | 'bwi' | 'volumeOsc' | 'vr';

export interface IndicatorChartHandle {
  setCrosshairPosition: (time: Time, price?: number) => void;
  clearCrosshair: () => void;
  setVisibleRange: (from: number, to: number) => void;
  setVisibleLogicalRange: (from: number, to: number) => void;
  getVisibleLogicalRange: () => { from: number; to: number } | null;
  resize: () => void;
  fitContent: () => void;
  resetYAxis: () => void;  // ✅ Y축 리셋 메서드 추가
}

interface IndicatorDataPoint {
  time: string;
  open?: number;
  high?: number;
  low?: number;
  close?: number;
  volume?: number;
  volumeColor?: string;
  macdLine?: number;
  signalLine?: number;
  macdHistogram?: number;
  rsi?: number;
  stochasticK?: number;
  stochasticD?: number;
  momentum?: number;
  cci?: number;
  williamsR?: number;
  adx?: number;
  plusDI?: number;
  minusDI?: number;
  obv?: number;
  trix?: number;
  trixSignal?: number;
  newPsy?: number;
  investPsy?: number;
  disparity?: number;
  disparityUltraShort?: number;
  disparityShort?: number;
  disparityMid?: number;
  disparityLong?: number;
  bwi?: number;
  volumeOsc?: number;
  vr?: number;
  vr2?: number;
  [key: string]: any;
}

interface IndicatorChartProps {
  type: IndicatorType;
  data: IndicatorDataPoint[];
  height?: number;
  title?: string;
  hideTitle?: boolean; // 타이틀 숨김 여부
  onCrosshairMove?: (time: Time | null) => void;
  onVisibleRangeChange?: (from: number, to: number) => void;
  externalHoverTime?: Time | null;
  hideNativeCrosshair?: boolean;
  hideTimeScale?: boolean; // X축 타임스케일 숨김 여부 (기본값: false)
  initialVisibleRange?: { from: number; to: number }; // ✅ 초기 visible range (차트 생성 시에만 사용)
  sharedTimeRange?: { from: number; to: number } | null; // ✅ 외부에서 제어하는 시간 범위 (실시간 동기화)
  isMobileMode?: boolean; // ✅ 모바일 모드 플래그 (Y축 너비 최소화)
  colorVersion?: number; // ✅ 색상 버전 (변경 시 강제 업데이트용)
  settingsVersion?: number; // ✅ 설정 버전 (선유형, 선굵기 등 변경 시 강제 업데이트용)
  ichimokuEnabled?: boolean; // ✅ 일목균형표 선행스팬 활성화 여부 (timeScale 동기화용)
}

/**
 * lightweight-charts 기반 보조지표 차트 컴포넌트
 * Volume, MACD, RSI, Stochastic 등 다양한 보조지표 지원
 */
const IndicatorChart = forwardRef<IndicatorChartHandle, IndicatorChartProps>(
  ({ type, data, height = 150, title, hideTitle = false, onCrosshairMove, onVisibleRangeChange, externalHoverTime, hideNativeCrosshair = false, hideTimeScale = false, initialVisibleRange, sharedTimeRange, isMobileMode = false, colorVersion: propColorVersion, settingsVersion: propSettingsVersion, ichimokuEnabled = false }, ref) => {
    const chartContainerRef = useRef<HTMLDivElement>(null);
    const chartRef = useRef<IChartApi | null>(null);
    const seriesRefs = useRef<Map<string, ISeriesApi<any>>>(new Map());
    const isSettingRangeRef = useRef(false);
    const isSettingExternalCrosshairRef = useRef(false);
    const lastVisibleRangeRef = useRef<{ from: number; to: number } | null>(null);
    const lastExternalHoverTimeRef = useRef<Time | null>(null);
    const retryTimeoutRef = useRef<NodeJS.Timeout | null>(null);
    const savedVisibleRangeBeforeHiddenRef = useRef<{ from: number; to: number; logicalFrom: number; logicalTo: number } | null>(null);
    /** 극단 줌(수 봉만) 저장·복원 시 캔들/지표가 판처럼 보이는 문제 방지 */
    const MIN_LOGICAL_BARS_TO_PERSIST = 16;
    const isDisposedRef = useRef<boolean>(false);
    const retryCountRef = useRef<number>(0);
    const typeRef = useRef(type);
    typeRef.current = type;

    // ✅ 새로운 Refs: DevTools 토글 대응
    const resizeObserverRef = useRef<ResizeObserver | null>(null);
    const devicePixelRatioRef = useRef<number>(window.devicePixelRatio || 1);
    const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
    const isChartCreatingRef = useRef<boolean>(false);  // StrictMode 중복 생성 방지
    const containerSizeRef = useRef<{ width: number; height: number }>({ width: 0, height: 0 });

    const onCrosshairMoveRef = useRef(onCrosshairMove);
    const onVisibleRangeChangeRef = useRef(onVisibleRangeChange);
    const dataRef = useRef(data);
    dataRef.current = data; // ✅ 렌더링 시마다 즉시 갱신 (useEffect보다 빠르고 확실함)

    useEffect(() => {
      onCrosshairMoveRef.current = onCrosshairMove;
      onVisibleRangeChangeRef.current = onVisibleRangeChange;
    }, [onCrosshairMove, onVisibleRangeChange]);

    // dataRef 업데이트 useEffect 제거 (위에서 직접 할당)

    const { chartColors, themeMode } = useAppTheme();
    const { indicatorSettings } = useIndicatorSettings();
    const { colors: colorSettings, colorVersion } = useChartColors();
    
    // ✅ 디버그: 컴포넌트 렌더링 확인 (BUILD_VERSION_2026_02_03_v4)
    console.log(`🎬 [IndicatorChart:${type}] 렌더링됨 - colorVersion: ${propColorVersion}, settingsVersion: ${propSettingsVersion} [BUILD_V6]`);
    
    // ✅ 디버그: version 변경 감지
    useEffect(() => {
      console.log(`🔔 [IndicatorChart:${type}] 버전 변경 감지 - colorVersion: ${propColorVersion}, settingsVersion: ${propSettingsVersion} [BUILD_V6]`);
    }, [propColorVersion, propSettingsVersion, type]);

    // ✅ 이전 설정값을 저장하여 실제 변경 여부 확인용
    const prevRelevantSettingsRef = useRef<string>('');
    const prevRelevantColorsRef = useRef<string>('');
    const isSeriesRecreatingRef = useRef<boolean>(false); // 시리즈 재생성 중 플래그
    
    // ✅ 데이터 업데이트용 이전 설정값 저장 (시리즈 재생성과 별도)
    const prevDataUpdateSettingsRef = useRef<string>('');
    const prevDataUpdateColorsRef = useRef<string>('');
    const prevDataLengthRef = useRef<number>(0);
    const prevDataFirstTimeRef = useRef<string>('');
    const prevDataLastTimeRef = useRef<string>('');
    const prevDataSettingsVersionRef = useRef<number | undefined>(undefined);
    const isDataUpdatingRef = useRef<boolean>(false); // 데이터 업데이트 중 플래그

    // ✅ 크로스헤어 설정 함수 (재사용 가능, 재시도 포함)
    const applyCrosshairPosition = useCallback((hoverTime: Time, retryCount: number = 0) => {
      // 기존 재시도 타이머 취소
      if (retryTimeoutRef.current) {
        clearTimeout(retryTimeoutRef.current);
        retryTimeoutRef.current = null;
      }

      if (!chartRef.current || !hoverTime || dataRef.current.length === 0) {
        return false;
      }

      // 시리즈가 준비되지 않았으면 재시도
      if (seriesRefs.current.size === 0) {
        if (retryCount < 5) {
          const delay = Math.min(50 * Math.pow(2, retryCount), 500); // exponential backoff: 50, 100, 200, 400, 500ms
          console.debug(`[IndicatorChart:${type}] 시리즈 대기 중 - ${delay}ms 후 재시도 (${retryCount + 1}/5)`);
          retryTimeoutRef.current = setTimeout(() => {
            applyCrosshairPosition(hoverTime, retryCount + 1);
          }, delay);
        }
        return false;
      }

      try {
        const timeInSeconds = (typeof hoverTime === 'number' ? hoverTime : Math.floor(new Date(hoverTime as string).getTime() / 1000)) as Time;

        // ✅ 데이터 범위 확인 (데이터 범위 밖이면 조용히 무시)
        if (dataRef.current.length === 0) {
          return false;
        }

        const firstDataTime = Math.floor(new Date((dataRef.current[0] as any).time || (dataRef.current[0] as any).date || (dataRef.current[0] as any).timestamp).getTime() / 1000);
        const lastDataTime = Math.floor(new Date((dataRef.current[dataRef.current.length - 1] as any).time || (dataRef.current[dataRef.current.length - 1] as any).date || (dataRef.current[dataRef.current.length - 1] as any).timestamp).getTime() / 1000);

        // ✅ 요청된 시간이 데이터 범위 밖이면 조용히 무시 (에러 로그 없이)
        if ((timeInSeconds as number) < firstDataTime || (timeInSeconds as number) > lastDataTime) {
          return false;
        }

        // 가장 가까운 데이터 포인트 찾기
        let closestDataPoint: any = null;
        let minTimeDiff = Infinity;

        for (const d of dataRef.current) {
          const timeField = (d as any).time || (d as any).date || (d as any).timestamp;
          if (!timeField) continue;

          const dataTime = Math.floor(new Date(timeField).getTime() / 1000);
          const timeDiff = Math.abs(dataTime - (timeInSeconds as number));

          if (timeDiff < 60 && timeDiff < minTimeDiff) {
            minTimeDiff = timeDiff;
            closestDataPoint = d;
          }
        }

        if (!closestDataPoint) {
          return false;
        }

        // 시리즈 가져오기
        const allSeries = Array.from(seriesRefs.current.values());
        const firstSeries = allSeries[0];

        if (!firstSeries) {
          if (retryCount < 5) {
            const delay = Math.min(50 * Math.pow(2, retryCount), 500);
            console.debug(`[IndicatorChart:${type}] 시리즈 없음 - ${delay}ms 후 재시도 (${retryCount + 1}/5)`);
            retryTimeoutRef.current = setTimeout(() => {
              applyCrosshairPosition(hoverTime, retryCount + 1);
            }, delay);
          }
          return false;
        }

        // 값 찾기
        let value: number | undefined;
        if (type === 'volume' && closestDataPoint.volume != null) {
          value = closestDataPoint.volume;
        } else if (type === 'macd' && closestDataPoint.macdLine != null) {
          value = closestDataPoint.macdLine;
        } else if (type === 'rsi' && closestDataPoint.rsi != null) {
          value = closestDataPoint.rsi;
        } else if (type === 'stochastic' && closestDataPoint.stochasticK != null) {
          value = closestDataPoint.stochasticK;
        } else if (type === 'cci') {
          const cciValue = closestDataPoint.cci ?? closestDataPoint.cci13 ?? closestDataPoint.cci14;
          if (cciValue != null) value = cciValue;
        } else if (type === 'adx' && closestDataPoint.adx != null) {
          value = closestDataPoint.adx;
        } else if (type === 'dmi' && closestDataPoint.plusDI != null) {
          value = closestDataPoint.plusDI;
        } else if (type === 'obv' && closestDataPoint.obv != null) {
          value = closestDataPoint.obv;
        } else if (type === 'trix' && closestDataPoint.trix != null) {
          value = closestDataPoint.trix;
        } else if (type === 'williamsR' && closestDataPoint.williamsR != null) {
          value = closestDataPoint.williamsR;
        } else if (type === 'newPsychological' && closestDataPoint.newPsy != null) {
          value = closestDataPoint.newPsy;
        } else if (type === 'investPsychological' && closestDataPoint.investPsy != null) {
          value = closestDataPoint.investPsy;
        } else if (type === 'disparity' && closestDataPoint.disparity != null) {
          value = closestDataPoint.disparity;
        } else if (type === 'bwi' && closestDataPoint.bwi != null) {
          value = closestDataPoint.bwi;
        } else if (type === 'volumeOsc' && closestDataPoint.volumeOsc != null) {
          value = closestDataPoint.volumeOsc;
        } else if (type === 'vr' && closestDataPoint.vr != null) {
          value = closestDataPoint.vr;
        }

        if (value === undefined || value === null || !isFinite(value)) {
          return false;
        }

        // 크로스헤어 설정 시도
        chartRef.current.setCrosshairPosition(value, timeInSeconds, firstSeries);
        retryCountRef.current = 0; // 성공하면 재시도 카운트 리셋
        return true;
      } catch (error) {
        // 에러 발생 시 재시도
        if (retryCount < 5) {
          const delay = Math.min(50 * Math.pow(2, retryCount), 500);
          // console.debug는 기본적으로 출력되지 않으므로 부담 없음
          console.debug(`[IndicatorChart:${type}] setCrosshairPosition 실패 - ${delay}ms 후 재시도 (${retryCount + 1}/5)`);
          retryTimeoutRef.current = setTimeout(() => {
            applyCrosshairPosition(hoverTime, retryCount + 1);
          }, delay);
          return false;
        } else {
          // 최대 재시도 초과 시 간결한 메시지만 출력 (에러 객체 제외)
          // 크로스헤어 설정 실패는 치명적이지 않으므로 조용히 무시
          // console.debug로 변경하여 기본적으로 출력되지 않도록 함
          console.debug(`[IndicatorChart:${type}] 크로스헤어 설정 재시도 초과 (차트 데이터 업데이트 중일 수 있음)`);
          return false;
        }
      }
    }, [type]);

    useImperativeHandle(ref, () => ({
      setCrosshairPosition: (time: Time, price?: number) => {
        if (chartRef.current && data.length > 0) {
          try {
            const firstSeries = Array.from(seriesRefs.current.values())[0];
            // ✅ price가 유효한 숫자인지 검증
            if (firstSeries && price !== undefined && price !== null && !isNaN(price) && isFinite(price)) {
              try {
                chartRef.current.setCrosshairPosition(price, time, firstSeries);
              } catch (innerError) {
                // ✅ lightweight-charts 내부 에러 무시 (차트 범위 벗어남 등)
                // volume/obv는 값이 크므로 스케일 문제로 에러가 자주 발생 - 무시
              }
            }
          } catch (e) {
            // 에러 무시
          }
        }
      },
      clearCrosshair: () => {
        if (chartRef.current) {
          chartRef.current.clearCrosshairPosition();
        }
      },
      setVisibleRange: (from: number, to: number) => {
        if (chartRef.current) {
          try {
            if (isSettingRangeRef.current) {
              // 이미 설정 중이면 스킵
              return;
            }
            isSettingRangeRef.current = true;

            // ⚠️ lastVisibleRangeRef 저장 금지 - 부모(CandlestickTestPage)가 관리하는 initialVisibleRange만 사용

            // ✅ 단순하게 visible range만 설정 (깜빡임 방지)
            chartRef.current.timeScale().setVisibleRange({
              from: from as Time,
              to: to as Time,
            });
          } catch (e) {
            // 에러 무시
          } finally {
            setTimeout(() => {
              isSettingRangeRef.current = false;
            }, 10);
          }
        }
      },
      setVisibleLogicalRange: (from: number, to: number) => {
        if (chartRef.current) {
          try {
            if (isSettingRangeRef.current) {
              // 이미 설정 중이면 스킵
              return;
            }
            isSettingRangeRef.current = true;

            // ✅ logical range 설정 (스케일 유지하면서 스크롤)
            chartRef.current.timeScale().setVisibleLogicalRange({
              from,
              to,
            });
          } catch (e) {
            // 에러 무시
          } finally {
            setTimeout(() => {
              isSettingRangeRef.current = false;
            }, 10);
          }
        }
      },
      resize: () => {
        if (chartRef.current && chartContainerRef.current) {
          chartRef.current.applyOptions({
            width: chartContainerRef.current.clientWidth,
            height: height,
          });
        }
      },
      fitContent: () => {
        if (chartRef.current) {
          try {
            chartRef.current.timeScale().fitContent();
            // ✅ fitContent 후 grid, priceScale, timeScale 옵션 재적용 (수평선 완전 제거)
            chartRef.current.applyOptions({
              grid: {
                vertLines: { 
                  visible: true, 
                  color: chartColors.gridColor,
                },
                horzLines: { 
                  visible: false,
                },
              },
              rightPriceScale: {
                borderVisible: false,  // Y축 경계선 숨김
                ticksVisible: false,   // 눈금 숨김
              },
              timeScale: {
                borderVisible: false,  // X축 경계선 숨김
              },
            });
          } catch (e) {
            console.error('fitContent 오류:', e);
          }
        }
      },
      getVisibleLogicalRange: () => {
        if (chartRef.current) {
          try {
            const range = chartRef.current.timeScale().getVisibleLogicalRange();
            if (range) {
              return {
                from: range.from,
                to: range.to,
              };
            }
          } catch (e) {
            // 에러 무시
          }
        }
        return null;
      },
      resetYAxis: () => {
        if (chartRef.current) {
          try {
            // 1. 오른쪽 Y축 스케일 리셋
            const priceScale = chartRef.current.priceScale('right');
            priceScale.applyOptions({
              autoScale: true,
            });

            // 2. 모든 시리즈의 autoscaleInfoProvider 제거
            seriesRefs.current.forEach((series) => {
              series.applyOptions({
                autoscaleInfoProvider: undefined,
              });
            });

            // 3. 타임스케일 fitContent 호출하여 전체 데이터 기준으로 재조정
            chartRef.current.timeScale().fitContent();

            // ✅ Y축 리셋 후 grid, priceScale, timeScale 옵션 재적용 (수평선 완전 제거)
            chartRef.current.applyOptions({
              grid: {
                vertLines: { 
                  visible: true, 
                  color: chartColors.gridColor,
                },
                horzLines: { 
                  visible: false,
                },
              },
              rightPriceScale: {
                borderVisible: false,  // Y축 경계선 숨김
                ticksVisible: false,   // 눈금 숨김
              },
              timeScale: {
                borderVisible: false,  // X축 경계선 숨김
              },
            });

            console.log(`[IndicatorChart:${type}] Y축 리셋 완료`);
          } catch (e) {
            console.error(`[IndicatorChart:${type}] Y축 리셋 오류:`, e);
          }
        }
      },
    }));

    // 차트 초기화
    useEffect(() => {
      if (!chartContainerRef.current) return;

      // ✅ useEffect 재실행 시 dispose 플래그 리셋
      isDisposedRef.current = false;

      // ✅ 컨테이너 크기 및 DPR 초기화
      const containerWidth = chartContainerRef.current.clientWidth;
      const containerHeight = chartContainerRef.current.clientHeight;
      const currentDPR = window.devicePixelRatio || 1;
      
      containerSizeRef.current = { width: containerWidth, height: containerHeight };
      devicePixelRatioRef.current = currentDPR;

      console.log(`🎨 [IndicatorChart:${type}] 차트 생성`, { width: containerWidth, height: containerHeight, dpr: currentDPR });

      const chart = createChart(chartContainerRef.current, {
        autoSize: true,  // ✅ 자동 크기 조정 활성화
        layout: {
          background: { type: ColorType.Solid, color: chartColors.background },
          textColor: chartColors.textColor,
          attributionLogo: false,
        },
        watermark: {
          visible: false,  // ✅ 워터마크 완전 비활성화
        },
        width: chartContainerRef.current.clientWidth,
        height: height,
        localization: {
          locale: 'en-US',  // 기본 로케일을 영어로 고정하여 tickMarkFormatter가 일관되게 작동하도록 함
          // ★ Y축 라벨 포맷터: 모든 차트에서 동일한 길이의 문자열 반환하여 Y축 너비 통일
          priceFormatter: (price: number) => {
            let formatted: string;
            // 절대값이 1000000 이상이면 M 단위로 표시
            if (Math.abs(price) >= 1000000) {
              formatted = (price / 1000000).toFixed(1) + 'M';
            }
            // 절대값이 1000 이상이면 K 단위로 표시
            else if (Math.abs(price) >= 1000) {
              formatted = (price / 1000).toFixed(1) + 'K';
            }
            // 절대값이 100 이상이면 정수로 표시
            else if (Math.abs(price) >= 100) {
              formatted = price.toFixed(0);
            }
            // 그 외에는 소수점 2자리로 표시
            else {
              formatted = price.toFixed(2);
            }
            // ★ 고정 길이 (8자리)로 패딩하여 모든 Y축 라벨의 너비를 동일하게
            return formatted.padStart(8, ' ');
          },
          // ✅ 크로스헤어 레이블의 시간 포맷터 (X축과 동일한 형식으로 통일)
          timeFormatter: (time: number) => {
            const date = new Date(time * 1000);
            const month = String(date.getMonth() + 1).padStart(2, '0');
            const day = String(date.getDate()).padStart(2, '0');
            const hours = String(date.getHours()).padStart(2, '0');
            const minutes = String(date.getMinutes()).padStart(2, '0');
            return `${month}/${day} ${hours}:${minutes}`;
          },
        },
        grid: {
          vertLines: { 
            visible: true, 
            color: chartColors.gridColor,
          },
          horzLines: { 
            visible: false,  // ✅ 수평 그리드 라인 완전히 숨김
            color: 'transparent',  // ✅ 혹시 모를 경우를 대비해 투명 색상 설정
          },
        },
        crosshair: {
          mode: hideNativeCrosshair ? CrosshairMode.Hidden : CrosshairMode.Normal,
          vertLine: {
            visible: true,
            width: 1,
            color: chartColors.crosshairColor,
            style: LineStyle.Solid,
            labelVisible: true,
            labelBackgroundColor: chartColors.crosshairLabelBg,
          },
          horzLine: {
            visible: true,  // 크로스헤어 수평선은 표시 (사용자 인터랙션용)
            width: 1,
            color: chartColors.crosshairColor,
            style: LineStyle.Solid,
            labelVisible: true,
            labelBackgroundColor: chartColors.crosshairLabelBg,
          },
        },
        timeScale: {
          visible: !hideTimeScale,  // ✅ hideTimeScale이 true면 숨김 (가장 마지막 보조지표만 표시)
          borderVisible: false,  // ✅ X축 경계선 숨김 (수평선 생성 방지)
          borderColor: 'transparent',
          timeVisible: true,
          secondsVisible: false,
          rightOffset: ichimokuEnabled ? 30 : 5,  // ✅ 일목균형표 활성화 시 30, 비활성화 시 5 (CandlestickChart와 완전히 일치)
          barSpacing: 8,    // ✅ CandlestickChart와 일치
          minBarSpacing: 5, // ✅ CandlestickChart와 완전히 일치
          fixLeftEdge: ichimokuEnabled ? true : false,  // ✅ 일목균형표 활성화 시 좌측 기준 정렬 (CandlestickChart와 완전히 일치)
          fixRightEdge: false,  // ✅ CandlestickChart와 완전히 일치 (우측 가장자리 스크롤 가능)
          lockVisibleTimeRangeOnResize: true,  // ✅ CandlestickChart와 완전히 일치 (리사이즈 시 범위 유지)
          rightBarStaysOnScroll: ichimokuEnabled ? false : false,  // ✅ CandlestickChart와 완전히 일치
          shiftVisibleRangeOnNewBar: false,  // ✅ CandlestickChart와 완전히 일치 (새 캔들 추가 시 범위 유지)
          allowShiftVisibleRangeOnWhitespaceReplacement: false,  // ✅ CandlestickChart와 완전히 일치 (모든 자동 조정 차단)
          tickMarkFormatter: (time: number) => {
            const date = new Date(time * 1000);
            const month = String(date.getMonth() + 1).padStart(2, '0');
            const day = String(date.getDate()).padStart(2, '0');
            const hours = String(date.getHours()).padStart(2, '0');
            const minutes = String(date.getMinutes()).padStart(2, '0');
            return `${month}/${day} ${hours}:${minutes}`;
          },
        },
        leftPriceScale: {
          visible: false,  // ✅ 왼쪽 Y축 완전 숨김
          minimumWidth: 0,  // ✅ 왼쪽 여백 완전 제거
          mode: 0,  // ✅ PriceScaleMode.Normal
          borderVisible: false,  // ✅ 경계선도 숨김
          ticksVisible: false,  // ✅ 눈금 숨김
        },
        rightPriceScale: {
          visible: true,  // ✅ 오른쪽 Y축 표시 (업비트 스타일)
          borderVisible: false,  // ✅ Y축 경계선 숨김 (수평선 생성 방지)
          borderColor: 'transparent',  // ✅ 경계선 색상도 투명으로
          minimumWidth: isMobileMode ? 40 : 100,  // ✅ 70 → 100으로 증가 (X축 라벨 "02/10 09:00" 전체가 표시되도록)
          entireTextOnly: false,  // 라벨이 잘려도 괜찮음
          autoScale: true,  // ✅ autoscaleInfoProvider와 함께 사용 (현재 보이는 범위 기준)
          scaleMargins: {
            top: 0.1,
            bottom: 0.1,
          },
          ticksVisible: false,  // ✅ Y축 눈금 완전 제거 (수평선처럼 보이는 것 방지)
          alignLabels: false,  // ✅ 라벨 정렬 비활성화
          mode: 0,  // ✅ PriceScaleMode.Normal (자동 로그 스케일 방지)
        },
        // ✅ 차트 스크롤 동작: 모바일 터치 최적화
        handleScroll: {
          mouseWheel: false,      // 마우스 휠 스크롤 비활성화
          pressedMouseMove: false, // 마우스 드래그 스크롤 비활성화
          horzTouchDrag: isMobileMode ? true : true,    // 터치 가로 드래그 활성화 (좌우 이동)
          vertTouchDrag: isMobileMode ? true : false,   // 모바일: 터치 세로 드래그 활성화 (확대/축소)
        },
        // ✅ 차트 줌 동작: 모바일 터치 줌 활성화
        handleScale: {
          mouseWheel: isMobileMode ? true : true,        // 마우스 휠 줌 활성화
          pinch: isMobileMode ? true : false,            // 모바일: 핀치 줌 활성화
          axisPressedMouseMove: {
            time: isMobileMode ? true : false,           // 모바일: X축 드래그로 줌 활성화
            price: isMobileMode ? true : false,          // 모바일: Y축 드래그로 줌 활성화
          },
          axisDoubleClickReset: false, // ✅ 축 더블클릭 리셋 완전 비활성화
        },
      });

      chartRef.current = chart;

      // ✅ 차트 생성 직후 grid, priceScale, timeScale 옵션 강제 재적용 (수평선 완전 제거)
      const gridOptions = {
        grid: {
          vertLines: { 
            visible: true, 
            color: chartColors.gridColor,
          },
          horzLines: { 
            visible: false,
            color: 'transparent',  // ✅ 혹시 모를 경우를 대비해 투명 색상 설정
          },
        },
        rightPriceScale: {
          borderVisible: false,  // Y축 경계선 숨김
          ticksVisible: false,   // 눈금 숨김
        },
        timeScale: {
          borderVisible: false,  // X축 경계선 숨김
        },
      };
      console.log(`[IndicatorChart:${type}] 🚫 수평선 제거 옵션 적용:`, gridOptions);
      chart.applyOptions(gridOptions);

      // 보조지표 타입별 시리즈 생성
      createSeriesForType(chart, type);

      // ✅ 시리즈 생성 직후에도 grid, priceScale, timeScale 옵션 재적용 (수평선 완전 제거)
      console.log(`[IndicatorChart:${type}] 🚫 시리즈 생성 후 수평선 제거 옵션 재적용`);
      chart.applyOptions({
        grid: {
          vertLines: { 
            visible: true, 
            color: chartColors.gridColor,
          },
          horzLines: { 
            visible: false,
          },
        },
        rightPriceScale: {
          borderVisible: false,  // Y축 경계선 숨김
          ticksVisible: false,   // 눈금 숨김
        },
        timeScale: {
          borderVisible: false,  // X축 경계선 숨김
        },
      });

      // 크로스헤어 이벤트
      const handleCrosshairMove = (param: any) => {
        // ✅ 외부 크로스헤어 설정 중에는 이벤트를 발생시키지 않음 (무한 루프 방지)
        if (isSettingExternalCrosshairRef.current) return;

        if (param.time && onCrosshairMoveRef.current) {
          onCrosshairMoveRef.current(param.time);
        } else if (!param.time && onCrosshairMoveRef.current) {
          onCrosshairMoveRef.current(null);
        }
      };

      chart.subscribeCrosshairMove(handleCrosshairMove);

      // 가시 범위 변경 이벤트
      const handleVisibleRangeChange = () => {
        if (isSettingRangeRef.current) return;

        const logicalRange = chart.timeScale().getVisibleLogicalRange();
        if (logicalRange && onVisibleRangeChangeRef.current) {
          const barsInfo = chart.timeScale().getVisibleRange();
          if (barsInfo) {
            onVisibleRangeChangeRef.current(barsInfo.from as number, barsInfo.to as number);
          }
        }
      };

      chart.timeScale().subscribeVisibleLogicalRangeChange(handleVisibleRangeChange);

      // ✅ 보조지표 차트는 휠 이벤트를 처리하지 않음 (캔들차트에서만 처리하여 동기화)
      // 캔들차트의 줌 변경이 onVisibleRangeChange를 통해 자동으로 전파됨

      // 리사이즈 핸들러 (window resize와 container resize 모두 감지)
      const handleResize = () => {
        if (isDisposedRef.current || !chartContainerRef.current || !chartRef.current) return;
        
        try {
          chartRef.current.applyOptions({
            width: chartContainerRef.current.clientWidth,
            // ✅ 리사이즈 시에도 grid, priceScale, timeScale 옵션 재적용 (수평선 완전 제거)
            grid: {
              vertLines: { 
                visible: true, 
                color: chartColors.gridColor,
              },
              horzLines: { 
                visible: false,
              },
            },
            rightPriceScale: {
              borderVisible: false,  // Y축 경계선 숨김
              ticksVisible: false,   // 눈금 숨김
            },
            timeScale: {
              borderVisible: false,  // X축 경계선 숨김
            },
          });
          
          // ✅ 탭 복귀 시 저장된 범위 복원 (단, 한 번만 복원)
          if (savedVisibleRangeBeforeHiddenRef.current) {
            const savedRange = savedVisibleRangeBeforeHiddenRef.current;
            savedVisibleRangeBeforeHiddenRef.current = null; // ⚡ 즉시 초기화하여 중복 복원 방지
            
            setTimeout(() => {
              if (isDisposedRef.current || !chartRef.current) return; // ✅ 차트 파괴 여부 재확인
              
              try {
                const span = savedRange.logicalTo - savedRange.logicalFrom;
                const n = dataRef.current?.length ?? 0;
                if (span < MIN_LOGICAL_BARS_TO_PERSIST && n > MIN_LOGICAL_BARS_TO_PERSIST) {
                  chartRef.current.timeScale().setVisibleLogicalRange({
                    from: 0 as any,
                    to: (n - 1) as any,
                  });
                  console.log(`🔄 [IndicatorChart:${type}] Resize 시 극단 줌 → 전체 논리봉 복원`, { n, span });
                } else {
                  chartRef.current.timeScale().setVisibleRange({
                    from: savedRange.from as Time,
                    to: savedRange.to as Time,
                  });
                  console.log(`🔄 [IndicatorChart:${type}] Resize 시 범위 복원`, savedRange);
                }
              } catch (e) {
                console.error(`[IndicatorChart:${type}] Resize 시 범위 복원 오류:`, e);
              }
            }, 50);
          }
        } catch (e) {
          console.error(`[IndicatorChart:${type}] handleResize 오류:`, e);
        }
      };

      // ✅ 탭 전환 시 차트 범위 보존 (Page Visibility API)
      const handleVisibilityChange = () => {
        if (isDisposedRef.current || !chartRef.current) return;
        
        if (document.hidden) {
          try {
            const visibleRange = chartRef.current.timeScale().getVisibleRange();
            const visibleLogicalRange = chartRef.current.timeScale().getVisibleLogicalRange();
            const span =
              visibleLogicalRange && Number.isFinite(visibleLogicalRange.from) && Number.isFinite(visibleLogicalRange.to)
                ? Math.max(0, visibleLogicalRange.to - visibleLogicalRange.from)
                : 0;
            if (visibleRange && visibleLogicalRange && span >= MIN_LOGICAL_BARS_TO_PERSIST) {
              savedVisibleRangeBeforeHiddenRef.current = {
                from: visibleRange.from as number,
                to: visibleRange.to as number,
                logicalFrom: visibleLogicalRange.from,
                logicalTo: visibleLogicalRange.to,
              };
              console.log(`💾 [IndicatorChart:${type}] 탭 숨김 - 차트 범위 저장`, savedVisibleRangeBeforeHiddenRef.current);
            } else {
              savedVisibleRangeBeforeHiddenRef.current = null;
              if (visibleLogicalRange) {
                console.log(
                  `⏸️ [IndicatorChart:${type}] 탭 숨김 - 범위 저장 스킵 (논리봉 ${span.toFixed(1)}개 < ${MIN_LOGICAL_BARS_TO_PERSIST})`
                );
              }
            }
          } catch (e) {
            console.error(`[IndicatorChart:${type}] 탭 숨김 시 범위 저장 오류:`, e);
          }
        } else {
          if (savedVisibleRangeBeforeHiddenRef.current) {
            const savedRange = savedVisibleRangeBeforeHiddenRef.current;
            savedVisibleRangeBeforeHiddenRef.current = null;
            
            setTimeout(() => {
              if (isDisposedRef.current || !chartRef.current) return;
              
              try {
                const span = savedRange.logicalTo - savedRange.logicalFrom;
                const n = dataRef.current?.length ?? 0;
                if (span < MIN_LOGICAL_BARS_TO_PERSIST && n > MIN_LOGICAL_BARS_TO_PERSIST) {
                  chartRef.current.timeScale().setVisibleLogicalRange({
                    from: 0 as any,
                    to: (n - 1) as any,
                  });
                  console.log(`🔄 [IndicatorChart:${type}] 탭 복귀 - 극단 줌 → 전체 논리봉 복원`, { n, span });
                } else {
                  chartRef.current.timeScale().setVisibleRange({
                    from: savedRange.from as Time,
                    to: savedRange.to as Time,
                  });
                  console.log(`🔄 [IndicatorChart:${type}] 탭 복귀 - 차트 범위 복원`, savedRange);
                }
              } catch (e) {
                console.error(`[IndicatorChart:${type}] 탭 복귀 시 범위 복원 오류:`, e);
              }
            }, 150);
          }
        }
      };

      window.addEventListener('resize', handleResize);
      document.addEventListener('visibilitychange', handleVisibilityChange);

      // ✅ ResizeObserver로 컨테이너 크기 변화 감지 + DevTools 대응
      let resizeObserver: ResizeObserver | null = null;
      if (chartContainerRef.current) {
        resizeObserver = new ResizeObserver((entries) => {
          // ✅ debounce 타이머 클리어
          if (debounceTimerRef.current) {
            clearTimeout(debounceTimerRef.current);
          }

          // ✅ 300ms debounce 적용
          debounceTimerRef.current = setTimeout(() => {
            if (isDisposedRef.current || !chartContainerRef.current) return;

            const entry = entries[0];
            if (!entry) return;

            const newWidth = entry.contentRect.width;
            const newHeight = entry.contentRect.height;
            const currentDPR = window.devicePixelRatio || 1;

            // ✅ 컨테이너 크기가 0이면 차트 재생성 금지
            if (newWidth <= 0 || newHeight <= 0) {
              console.warn(`[IndicatorChart:${type}] 컨테이너 크기가 0: ${newWidth}x${newHeight}`);
              return;
            }

            const widthChanged = Math.abs(containerSizeRef.current.width - newWidth) > 1;
            const heightChanged = Math.abs(containerSizeRef.current.height - newHeight) > 1;
            const dprChanged = Math.abs(devicePixelRatioRef.current - currentDPR) > 0.001;

            // ✅ DPR 변경 또는 크기 변경 시 차트 재생성
            if (dprChanged || widthChanged || heightChanged) {
              console.log(`🔄 [IndicatorChart:${type}] DevTools 감지 - 차트 재생성`, {
                oldSize: containerSizeRef.current,
                newSize: { width: newWidth, height: newHeight },
                oldDPR: devicePixelRatioRef.current,
                newDPR: currentDPR,
                dprChanged,
                widthChanged,
                heightChanged
              });

              // 크기 및 DPR 업데이트
              containerSizeRef.current = { width: newWidth, height: newHeight };
              devicePixelRatioRef.current = currentDPR;

              // ✅ 차트 크기 조정 (lightweight-charts는 autoSize로 자동 처리)
              if (chartRef.current) {
                try {
                  chartRef.current.applyOptions({
                    width: newWidth,
                    height: height,
                  });
                  console.log(`✅ [IndicatorChart:${type}] 차트 크기 조정 완료: ${newWidth}x${height}`);
                } catch (e) {
                  console.error(`[IndicatorChart:${type}] 크기 조정 오류:`, e);
                }
              }
            }
          }, 200); // 200ms debounce
        });
        resizeObserver.observe(chartContainerRef.current);
        resizeObserverRef.current = resizeObserver;
      }

      // ✅ 차트 준비 완료 - 마지막 호버 시간이 있으면 재적용
      setTimeout(() => {
        console.log(`✅ [IndicatorChart:${type}] 차트 준비 완료`);

        if (lastExternalHoverTimeRef.current) {
          const hoverTime = lastExternalHoverTimeRef.current;
          const timeStr = typeof hoverTime === 'number' ? new Date(hoverTime * 1000).toISOString().slice(11, 19) : new Date(hoverTime as string).toISOString().slice(11, 19);
          console.log(`🔄 [IndicatorChart:${type}] 차트 준비 후 크로스헤어 재적용: ${timeStr}`);
          applyCrosshairPosition(hoverTime, 0);
        }
      }, 300);

      return () => {
        // ✅ cleanup 시작 - 모든 핸들러 즉시 무시
        isDisposedRef.current = true;
        
        // ✅ Debounce 타이머 정리
        if (debounceTimerRef.current) {
          clearTimeout(debounceTimerRef.current);
          debounceTimerRef.current = null;
        }
        
        // Cleanup: 이벤트 리스너와 observer 먼저 제거
        if (retryTimeoutRef.current) {
          clearTimeout(retryTimeoutRef.current);
          retryTimeoutRef.current = null;
        }
        savedVisibleRangeBeforeHiddenRef.current = null;
        
        // ✅ ResizeObserver 정리 (ref 사용)
        if (resizeObserverRef.current) {
          resizeObserverRef.current.disconnect();
          resizeObserverRef.current = null;
        }
        
        window.removeEventListener('resize', handleResize);
        document.removeEventListener('visibilitychange', handleVisibilityChange);
        
        // 차트 구독 해제 및 제거
        try {
          chart.unsubscribeCrosshairMove(handleCrosshairMove);
          chart.timeScale().unsubscribeVisibleLogicalRangeChange(handleVisibleRangeChange);
          chart.remove();
        } catch (e) {
          console.error(`[IndicatorChart:${type}] cleanup 오류:`, e);
        }
        seriesRefs.current.clear();
        chartRef.current = null;
      };
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [chartColors, type, hideNativeCrosshair]); // ✅ 차트는 테마/타입 변경시만 재생성

    // ✅ hideTimeScale 변경 시 timeScale 가시성 업데이트
    useEffect(() => {
      if (chartRef.current) {
        chartRef.current.applyOptions({
          timeScale: {
            visible: !hideTimeScale,
          },
        });
      }
    }, [hideTimeScale]);

    // ✅ 일목 선행스팬 on/off 시 캔들차트와 timeScale 옵션 동기화 (재생성 없이 — 시리즈/데이터 유지)
    useEffect(() => {
      if (!chartRef.current) return;
      try {
        chartRef.current.applyOptions({
          timeScale: {
            rightOffset: ichimokuEnabled ? 30 : 5,
            fixLeftEdge: ichimokuEnabled,
          },
        });
      } catch (e) {
        console.warn(`[IndicatorChart:${type}] timeScale 일목 동기화 실패:`, e);
      }
    }, [ichimokuEnabled, type]);

    // height prop 변경 시 차트 높이 업데이트 (재생성 없이)
    useEffect(() => {
      if (chartRef.current && height) {
        chartRef.current.applyOptions({ height });
      }
    }, [height]);

    // ✅ colorSettings와 indicatorSettings의 실제 값을 문자열로 변환하여 추적
    // (객체 참조가 아닌 실제 값 변경을 감지하기 위함)
    // useMemo로 감싸서 성능 최적화 (매 렌더링마다 JSON.stringify 방지)
    // colorVersion을 포함하여 강제 변경 감지
    const colorSettingsStr = useMemo(() => {
      const str = `v${colorVersion}:${JSON.stringify(colorSettings)}`;
      console.log(`🔍 [IndicatorChart:${type}] colorSettingsStr 생성: v${colorVersion}`);
      return str;
    }, [colorSettings, colorVersion, type]);
    const indicatorSettingsStr = useMemo(() => JSON.stringify(indicatorSettings), [indicatorSettings]);
    
    // ✅ indicatorSettings 또는 colorSettings 변경 시 시리즈와 기준선만 재생성 (화면 위치 유지)
    useEffect(() => {
      console.log(`🔄 [IndicatorChart:${type}] useEffect 트리거됨 - colorVersion: ${colorVersion}`);
      
      if (!chartRef.current || !data || data.length === 0) {
        console.log(`⏭️ [IndicatorChart:${type}] useEffect 스킵 - 차트 또는 데이터 없음`);
        return;
      }

      // ✅ 이미 재생성 중이면 skip (동시 실행 방지)
      if (isSeriesRecreatingRef.current) {
        console.log(`[IndicatorChart:${type}] 이미 시리즈 재생성 중 - 스킵`);
        return;
      }

      // ✅ 각 타입별로 관련 설정 키 패턴 정의
      const getRelevantKeyPatterns = (indicatorType: IndicatorType): string[] => {
        switch (indicatorType) {
          case 'volume':
            return ['volume'];
          case 'macd':
            return ['macd', 'signal', 'histogram', 'slowEma'];
          case 'rsi':
            return ['rsi'];
          case 'stochastic':
            return ['stochastic'];
          case 'cci':
            return ['cci'];
          case 'williamsR':
            return ['williamsR'];
          case 'adx':
            return ['adx', 'plusDi', 'minusDi'];
          case 'dmi':
            return ['dmi', 'plusDi', 'minusDi'];
          case 'trix':
            return ['trix'];
          case 'obv':
            return ['obv'];
          case 'momentum':
            return ['momentum'];
          case 'volumeOsc':
            return ['volumeOsc'];
          case 'vr':
            return ['vr', 'vr2'];  // ✅ vr2 추가
          case 'bwi':
            return ['bwi'];
          case 'disparity':
            return ['disparity'];
          case 'newPsychological':
            return ['newPsychological'];
          case 'investPsychological':
            return ['investPsychological'];
          default:
            return [indicatorType];
        }
      };

      const patterns = getRelevantKeyPatterns(type);
      
      // ✅ 현재 타입과 관련된 설정만 추출 (정확한 매칭)
      // ✅ Period 관련 설정도 포함 (백엔드에서 새 데이터를 받으면 차트 재생성 필요)
      const relevantIndicatorKeys = Object.keys(indicatorSettings).filter(key => 
        patterns.some(pattern => key.startsWith(pattern))
      );
      const relevantColorKeys = Object.keys(colorSettings).filter(key => 
        patterns.some(pattern => key.startsWith(pattern))
      );

      // 관련 설정값들을 문자열로 직렬화
      const currentIndicatorSettings = relevantIndicatorKeys
        .map(key => `${key}:${(indicatorSettings as any)[key]}`)
        .sort()
        .join('|');
      const currentColorSettings = relevantColorKeys
        .map(key => `${key}:${(colorSettings as any)[key]}`)
        .sort()
        .join('|');

      // ✅ ref가 비어있으면 (첫 렌더링 또는 DB 로드 완료 시) 초기화만 하고 차트 재생성 안 함
      if (prevRelevantSettingsRef.current === '' || prevRelevantColorsRef.current === '') {
        prevRelevantSettingsRef.current = currentIndicatorSettings;
        prevRelevantColorsRef.current = currentColorSettings;
        console.log(`[IndicatorChart:${type}] 초기화 완료 - 차트 재생성 생략`);
        return;
      }

      // ✅ 이전 값과 비교하여 실제로 변경되었을 때만 처리
      const settingsChanged = prevRelevantSettingsRef.current !== currentIndicatorSettings;
      const colorsChanged = prevRelevantColorsRef.current !== currentColorSettings;
      
      if (!settingsChanged && !colorsChanged) {
        console.log(`[IndicatorChart:${type}] 관련 설정 및 색상 변경 없음 - 스킵`);
        return;
      }

      // ✅ **색상만 변경된 경우** - 시리즈 재생성 없이 색상만 업데이트
      if (!settingsChanged && colorsChanged) {
        console.log(`🎨 [IndicatorChart:${type}] 색상만 변경됨 - 시리즈 색상 업데이트 (재생성 없음)`);
        console.log(`[IndicatorChart:${type}] 이전 색상: ${prevRelevantColorsRef.current}`);
        console.log(`[IndicatorChart:${type}] 현재 색상: ${currentColorSettings}`);
        
        prevRelevantColorsRef.current = currentColorSettings;
        
        // ✅ 기존 시리즈에 색상만 적용
        updateSeriesColors(type);
        return;
      }

      // ✅ **설정값이 변경된 경우** - 시리즈 재생성
      console.log(`🔧 [IndicatorChart:${type}] 설정 변경 감지 - 시리즈 및 기준선 재생성 (화면 위치 유지)`);
      console.log(`[IndicatorChart:${type}] 이전 설정: ${prevRelevantSettingsRef.current}`);
      console.log(`[IndicatorChart:${type}] 현재 설정: ${currentIndicatorSettings}`);
      console.log(`[IndicatorChart:${type}] 이전 색상: ${prevRelevantColorsRef.current}`);
      console.log(`[IndicatorChart:${type}] 현재 색상: ${currentColorSettings}`);
      
      // VR 타입일 경우 VR2 설정 상세 로그
      if (type === 'vr') {
        console.log(`🔍 [IndicatorChart:VR] VR2 설정 상세:`, {
          vr2Enabled: indicatorSettings.vr2Enabled,
          vr2Period: indicatorSettings.vr2Period,
          vrPeriod: indicatorSettings.vrPeriod
        });
      }
      
      // ✅ 시리즈 재생성 시작 - 플래그 설정
      isSeriesRecreatingRef.current = true;
      
      // 현재 값을 저장
      prevRelevantSettingsRef.current = currentIndicatorSettings;
      prevRelevantColorsRef.current = currentColorSettings;

      // 현재 visible range 저장
      const currentTimeScale = chartRef.current.timeScale();
      const currentVisibleRange = currentTimeScale.getVisibleRange();
      
      // 기존 시리즈 제거 및 새로운 시리즈와 기준선 생성
      createSeriesForType(chartRef.current, type);
      
      console.log(`[IndicatorChart:${type}] 시리즈 재생성 완료`);
      
      // ✅ ref 초기화 (데이터 업데이트 useEffect에서 "첫 렌더링" 처리되도록)
      prevDataUpdateSettingsRef.current = '';
      prevDataUpdateColorsRef.current = '';
      
      // ✅ 시리즈가 생성된 직후 즉시 데이터 적용 (지연 최소화)
      // requestAnimationFrame을 사용하여 다음 프레임에 데이터 적용 (시리즈 생성 완료 보장)
      requestAnimationFrame(() => {
        console.log(`[IndicatorChart:${type}] 시리즈 준비 완료 - 데이터 즉시 적용 시작`);
        applyChartData(true); // 강제 적용
        
        // ✅ 데이터 적용이 완전히 끝날 때까지 충분히 대기한 후 플래그 해제
        setTimeout(() => {
          // ✅ 플래그 해제 전에 prevDataUpdate refs 초기화 (시리즈 재생성 중 도착한 새 데이터를 감지하도록)
          prevDataUpdateSettingsRef.current = '';
          prevDataUpdateColorsRef.current = '';
          
          isSeriesRecreatingRef.current = false;
          console.log(`[IndicatorChart:${type}] 시리즈 재생성 플래그 해제 + prevDataUpdate refs 초기화`);
          
          // ✅ 플래그 해제 직후 최신 데이터로 다시 적용 (시리즈 재생성 중 새 데이터가 도착했을 경우 대응)
          setTimeout(() => {
            console.log(`[IndicatorChart:${type}] 플래그 해제 후 최신 데이터 재적용 확인`);
            applyChartData(false); // 최신 데이터로 재적용 (이제 변경 감지됨)
          }, 50);
        }, 300); // applyChartData 내부의 모든 비동기 작업 완료 대기 (200ms + 여유, 400ms에서 단축)
      });

      // visible range 복원 (화면 위치 유지)
      // ⚠️ applyChartData 완료 후에 실행되도록 충분한 지연 필요 (10ms + 400ms = 410ms 후)
      if (currentVisibleRange) {
        setTimeout(() => {
          if (chartRef.current) {
            try {
              chartRef.current.timeScale().setVisibleRange(currentVisibleRange);
              console.log(`[IndicatorChart:${type}] 화면 위치 복원 완료`);
            } catch (error) {
              console.warn(`[IndicatorChart:${type}] 화면 위치 복원 실패:`, error);
            }
          }
        }, 450); // 시리즈 생성 + 데이터 적용 완료 대기 (10ms + 400ms + 여유)
      }

      // 데이터는 applyChartData(true) 직접 호출로 강제 적용됨
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [colorSettingsStr, indicatorSettingsStr, type, propColorVersion, propSettingsVersion]); // ✅ 실제 값 변경을 감지하도록 문자열화된 값 사용 + propColorVersion + propSettingsVersion

    // ✅ 시리즈 색상만 업데이트하는 함수 (재생성 없이)
    const updateSeriesColors = (indicatorType: IndicatorType) => {
      if (!chartRef.current || seriesRefs.current.size === 0) {
        console.warn(`[IndicatorChart:${indicatorType}] 색상 업데이트 실패 - 차트 또는 시리즈 없음`);
        return;
      }

      // ✅ 타입 단언 - 모든 속성에 접근 가능하도록
      const colors = colorSettings as any;
      const settings = indicatorSettings as any;
      const themeColors = chartColors as any;

      // 선유형 변환 함수
      const getLineStyle = (styleStr: string) => {
        if (styleStr === 'dotted') return LineStyle.Dotted;
        if (styleStr === 'dashed') return LineStyle.Dashed;
        return LineStyle.Solid;
      };

      try {
        switch (indicatorType) {
          case 'volume':
            // Volume은 데이터에 색상이 포함되어 있어서 applyChartData로 업데이트 필요
            console.log(`🎨 [IndicatorChart:volume] 색상 업데이트 - 데이터 재적용 필요`);
            applyChartData(true);
            break;

          case 'macd':
            const macdLine = seriesRefs.current.get('macdLine');
            const signalLine = seriesRefs.current.get('signalLine');
            if (macdLine) {
              macdLine.applyOptions({
                color: colors.macdLineColor || themeColors.macdLine,
                lineStyle: getLineStyle(settings.macdLineStyle || 'solid'),
              });
            }
            if (signalLine) {
              signalLine.applyOptions({
                color: colors.macdSignalColor || themeColors.macdSignal,
                lineStyle: getLineStyle(settings.macdSignalStyle || 'solid'),
              });
            }
            // Histogram은 데이터에 색상이 포함되어 있어서 applyChartData로 업데이트
            applyChartData(true);
            break;

          case 'stochastic':
            const kLine = seriesRefs.current.get('stochasticK');
            const dLine = seriesRefs.current.get('stochasticD');
            if (kLine) {
              kLine.applyOptions({
                color: colors.stochasticKColor || themeColors.stochasticK,
                lineStyle: getLineStyle(settings.stochasticKStyle || 'solid'),
              });
            }
            if (dLine) {
              dLine.applyOptions({
                color: colors.stochasticDColor || themeColors.stochasticD,
                lineStyle: getLineStyle(settings.stochasticDStyle || 'solid'),
              });
            }
            break;

          case 'cci':
            const cciSeries = seriesRefs.current.get('cci');
            if (cciSeries) {
              cciSeries.applyOptions({
                color: colors.cciColor || themeColors.cci,
                lineStyle: getLineStyle(settings.cciLineStyle || 'solid'),
              });
            }
            break;

          case 'obv':
            const obvSeries = seriesRefs.current.get('obv');
            if (obvSeries) {
              obvSeries.applyOptions({
                color: colors.obvColor || themeColors.obv,
                lineStyle: getLineStyle(settings.obvLineStyle || 'solid'),
              });
            }
            break;

          case 'vr':
            const vrSeries = seriesRefs.current.get('vr');
            const vr2Series = seriesRefs.current.get('vr2');
            if (vrSeries) {
              vrSeries.applyOptions({
                color: colors.vrColor || themeColors.vr,
                lineStyle: getLineStyle(settings.vrLineStyle || 'solid'),
              });
            }
            if (vr2Series && settings.vr2Enabled) {
              vr2Series.applyOptions({
                color: colors.vr2Color || themeColors.vr2,
                lineStyle: getLineStyle(settings.vr2LineStyle || 'solid'),
              });
            }
            break;

          case 'dmi':
            const plusDiSeries = seriesRefs.current.get('plusDI');
            const minusDiSeries = seriesRefs.current.get('minusDI');
            if (plusDiSeries) {
              plusDiSeries.applyOptions({
                color: colors.plusDiColor || themeColors.plusDI,
              });
            }
            if (minusDiSeries) {
              minusDiSeries.applyOptions({
                color: colors.minusDiColor || themeColors.minusDI,
              });
            }
            break;

          case 'adx':
            const adxSeries = seriesRefs.current.get('adx');
            const adxPlusDiSeries = seriesRefs.current.get('plusDI');
            const adxMinusDiSeries = seriesRefs.current.get('minusDI');
            if (adxSeries) {
              adxSeries.applyOptions({
                color: colors.adxColor || themeColors.adx,
              });
            }
            if (adxPlusDiSeries) {
              adxPlusDiSeries.applyOptions({
                color: colors.plusDiColor || themeColors.plusDI,
              });
            }
            if (adxMinusDiSeries) {
              adxMinusDiSeries.applyOptions({
                color: colors.minusDiColor || themeColors.minusDI,
              });
            }
            break;

          case 'rsi':
            const rsiSeries = seriesRefs.current.get('rsi');
            if (rsiSeries) {
              rsiSeries.applyOptions({
                color: colors.rsiColor || themeColors.rsi,
                lineStyle: getLineStyle(settings.rsiLineStyle || 'solid'),
              });
            }
            break;

          case 'williamsR':
            const williamsRSeries = seriesRefs.current.get('williamsR');
            if (williamsRSeries) {
              williamsRSeries.applyOptions({
                color: colors.williamsRColor || themeColors.williamsR,
              });
            }
            break;

          case 'momentum':
            const momentumSeries = seriesRefs.current.get('momentum');
            if (momentumSeries) {
              momentumSeries.applyOptions({
                color: colors.momentumColor || themeColors.momentum,
              });
            }
            break;

          case 'trix':
            const trixSeries = seriesRefs.current.get('trix');
            const trixSignalSeries = seriesRefs.current.get('trixSignal');
            if (trixSeries) {
              trixSeries.applyOptions({
                color: colors.trixColor || themeColors.trix,
              });
            }
            if (trixSignalSeries) {
              trixSignalSeries.applyOptions({
                color: colors.trixSignalColor || themeColors.trixSignal,
              });
            }
            break;

          case 'volumeOsc':
            const volumeOscSeries = seriesRefs.current.get('volumeOsc');
            if (volumeOscSeries) {
              // VolumeOsc는 Histogram이므로 데이터 재적용 필요
              applyChartData(true);
            }
            break;

          case 'bwi':
            const bwiSeries = seriesRefs.current.get('bwi');
            if (bwiSeries) {
              bwiSeries.applyOptions({
                color: colors.bwiColor || themeColors.bwi,
              });
            }
            break;

          case 'disparity':
            const disparitySeries = seriesRefs.current.get('disparity');
            if (disparitySeries) {
              disparitySeries.applyOptions({
                color: colors.disparityColor || themeColors.disparity,
              });
            }
            break;

          case 'newPsychological':
            const newPsySeries = seriesRefs.current.get('newPsychological');
            if (newPsySeries) {
              newPsySeries.applyOptions({
                color: colors.newPsychologicalColor || themeColors.newPsychological,
              });
            }
            break;

          case 'investPsychological':
            const investPsySeries = seriesRefs.current.get('investPsychological');
            if (investPsySeries) {
              investPsySeries.applyOptions({
                color: colors.investPsychologicalColor || themeColors.investPsychological,
              });
            }
            break;

          default:
            console.warn(`[IndicatorChart:${indicatorType}] 색상 업데이트 미지원 타입`);
        }
        console.log(`✅ [IndicatorChart:${indicatorType}] 색상 업데이트 완료`);
      } catch (error) {
        console.error(`❌ [IndicatorChart:${indicatorType}] 색상 업데이트 실패:`, error);
      }
    };

    // 보조지표 타입별 시리즈 생성 함수
    const createSeriesForType = (chart: IChartApi, indicatorType: IndicatorType) => {
      // 기존 시리즈를 차트에서 완전히 제거
      console.log(`[IndicatorChart:${indicatorType}] 기존 시리즈 제거 시작`);
      seriesRefs.current.forEach((series, key) => {
        try {
          chart.removeSeries(series);
          console.log(`[IndicatorChart:${indicatorType}] 시리즈 제거 완료: ${key}`);
      } catch (error) {
          console.warn(`[IndicatorChart:${indicatorType}] 시리즈 제거 실패: ${key}`, error);
      }
      });
      seriesRefs.current.clear();
      console.log(`[IndicatorChart:${indicatorType}] 모든 시리즈 제거 완료`);

      // 선유형 변환 함수
      const getLineStyle = (styleStr: string) => {
        if (styleStr === 'dotted') return LineStyle.Dotted;
        if (styleStr === 'dashed') return LineStyle.Dashed;
        return LineStyle.Solid;
      };

      switch (indicatorType) {
        case 'volume':
          const volumeSeries = chart.addHistogramSeries({
            priceFormat: { type: 'volume' },
            priceScaleId: 'right',  // ✅ 오른쪽 Y축 사용 (업비트 스타일)
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('volume', volumeSeries);
          break;

        case 'macd':
          // MACD Line
          const macdLineSeries = chart.addLineSeries({
            color: colorSettings.macdLine || '#2196F3',
            lineWidth: (colorSettings.macdLineWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.macdLineStyle || 'solid'),
            priceScaleId: 'right',  // ✅ 오른쪽 Y축 사용 (업비트 스타일)
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('macdLine', macdLineSeries);

          // Signal Line
          const signalLineSeries = chart.addLineSeries({
            color: colorSettings.signalLine || '#FF9800',
            lineWidth: (colorSettings.signalLineWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.signalLineStyle || 'solid'),
            priceScaleId: 'right',  // ✅ 오른쪽 Y축 사용 (업비트 스타일)
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('signalLine', signalLineSeries);

          // Histogram
          const histogramSeries = chart.addHistogramSeries({
            priceFormat: { type: 'price', precision: 2, minMove: 0.01 },
            priceScaleId: 'right',  // ✅ 오른쪽 Y축 사용 (업비트 스타일)
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          console.log('[IndicatorChart:MACD] 히스토그램 시리즈 생성 완료');
          seriesRefs.current.set('histogram', histogramSeries);

          // MACD 0선 (ON일 때만 표시)
          if (indicatorSettings.macdZeroLineEnabled) {
            macdLineSeries.createPriceLine({
              price: 0,
              color: colorSettings.macdZeroLineColor || (chartColors.gridColor || '#999999'),
              lineWidth: (colorSettings.macdZeroLineWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.macdZeroLineStyle || 'dotted'),
              axisLabelVisible: false,
              title: '',
            });
            console.log('[IndicatorChart:MACD] 0선 기준선 추가 완료');
          }

          console.log('[IndicatorChart:MACD] 시리즈 생성 완료');
          break;

        case 'rsi':
          // ✅ RSI 과열 영역 (기준선 위) - BaselineSeries 사용
          if (indicatorSettings.rsiOverboughtEnabled) {
            const rsiOverboughtLevel = indicatorSettings.rsiOverbought || 70;
            const rsiOverboughtAreaSeries = chart.addBaselineSeries({
              baseValue: { type: 'price', price: rsiOverboughtLevel },
              topLineColor: 'rgba(239, 83, 80, 0.8)',
              topFillColor1: 'rgba(239, 83, 80, 0.4)',
              topFillColor2: 'rgba(239, 83, 80, 0.1)',
              bottomLineColor: 'rgba(239, 83, 80, 0.8)',
              bottomFillColor1: 'rgba(239, 83, 80, 0.1)',
              bottomFillColor2: 'rgba(239, 83, 80, 0.05)',
              lineVisible: true,
              lineWidth: 1,
              priceScaleId: 'right',
              lastValueVisible: false,
              priceLineVisible: false,
            });
            seriesRefs.current.set('rsiOverboughtArea', rsiOverboughtAreaSeries);
            console.log(`✅ [IndicatorChart:RSI] 과열 영역 시리즈 생성 완료 (기준: ${rsiOverboughtLevel})`);
          }
          
          // ✅ RSI 침체 영역 (기준선 아래) - BaselineSeries 사용
          if (indicatorSettings.rsiOversoldEnabled) {
            const rsiOversoldLevel = indicatorSettings.rsiOversold || 30;
            const rsiOversoldAreaSeries = chart.addBaselineSeries({
              baseValue: { type: 'price', price: rsiOversoldLevel },
              topLineColor: 'rgba(38, 166, 154, 0.8)',
              topFillColor1: 'rgba(38, 166, 154, 0.05)',
              topFillColor2: 'rgba(38, 166, 154, 0.1)',
              bottomLineColor: 'rgba(38, 166, 154, 0.8)',
              bottomFillColor1: 'rgba(38, 166, 154, 0.1)',
              bottomFillColor2: 'rgba(38, 166, 154, 0.4)',
              lineVisible: true,
              lineWidth: 1,
              priceScaleId: 'right',
              lastValueVisible: false,
              priceLineVisible: false,
            });
            seriesRefs.current.set('rsiOversoldArea', rsiOversoldAreaSeries);
            console.log(`✅ [IndicatorChart:RSI] 침체 영역 시리즈 생성 완료 (기준: ${rsiOversoldLevel})`);
          }
          
          const rsiSeries = chart.addLineSeries({
            color: colorSettings.rsiColor || '#9C27B0',
            lineWidth: (colorSettings.rsiWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.rsiLineStyle || 'solid'),
            priceScaleId: 'right',  // ✅ 오른쪽 Y축 사용 (업비트 스타일)
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('rsi', rsiSeries);

          // RSI 신호선 (9일 이동평균) - 주황색 (ON일 때만 생성)
          if (indicatorSettings.rsiSignalEnabled) {
            const rsiSignalSeries = chart.addLineSeries({
              color: colorSettings.rsiSignalColor || '#FF9800',
              lineWidth: (colorSettings.rsiSignalWidth || 2) as any,
              lineStyle: getLineStyle(colorSettings.rsiSignalLineStyle || 'solid'),
              priceScaleId: 'right',
              lastValueVisible: true, // ✅ Y축에 최종값 표시
              priceLineVisible: false, // ✅ 가격선 완전 숨김
            });
            seriesRefs.current.set('rsiSignal', rsiSignalSeries);
          }

          // RSI 과매수 기준선 (ON일 때만 표시)
          if (indicatorSettings.rsiOverboughtEnabled) {
            const rsiOverboughtColor = colorSettings.rsiOverboughtColor || '#EF5350';
            rsiSeries.createPriceLine({
              price: indicatorSettings.rsiOverbought || 70,
              color: rsiOverboughtColor,
              lineWidth: (colorSettings.rsiOverboughtWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.rsiOverboughtLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: rsiOverboughtColor,
              title: '',
            });
          }

          // RSI 중앙값 기준선 (ON일 때만 표시)
          if (indicatorSettings.rsiMiddleEnabled) {
            const rsiMiddleColor = colorSettings.rsiMiddleColor || (chartColors.gridColor || '#999999');
            rsiSeries.createPriceLine({
              price: indicatorSettings.rsiMiddle || 50,
              color: rsiMiddleColor,
              lineWidth: (colorSettings.rsiMiddleWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.rsiMiddleLineStyle || 'dotted'),
              axisLabelVisible: true, // ✅ 중앙값도 표시
              axisLabelColor: rsiMiddleColor,
              title: '',
            });
          }

          // RSI 과매도 기준선 (ON일 때만 표시)
          if (indicatorSettings.rsiOversoldEnabled) {
            const rsiOversoldColor = colorSettings.rsiOversoldColor || '#26A69A';
            rsiSeries.createPriceLine({
              price: indicatorSettings.rsiOversold || 30,
              color: rsiOversoldColor,
              lineWidth: (colorSettings.rsiOversoldWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.rsiOversoldLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: rsiOversoldColor,
              title: '',
            });
          }

          console.log('[IndicatorChart:RSI] RSI 라인 + 신호선 + 기준선 + 영역 추가 완료');
          break;

        case 'stochastic':
          // ✅ Stochastic 과열 영역 (기준선 위) - BaselineSeries 사용
          if (indicatorSettings.stochasticOverboughtEnabled) {
            const stochOverboughtLevel = indicatorSettings.stochasticOverbought || 80;
            const stochOverboughtAreaSeries = chart.addBaselineSeries({
              baseValue: { type: 'price', price: stochOverboughtLevel },
              topLineColor: 'rgba(239, 83, 80, 0.8)',
              topFillColor1: 'rgba(239, 83, 80, 0.4)',
              topFillColor2: 'rgba(239, 83, 80, 0.1)',
              bottomLineColor: 'rgba(239, 83, 80, 0.8)',
              bottomFillColor1: 'rgba(239, 83, 80, 0.1)',
              bottomFillColor2: 'rgba(239, 83, 80, 0.05)',
              lineVisible: true,
              lineWidth: 1,
              priceScaleId: 'right',
              lastValueVisible: false,
              priceLineVisible: false,
            });
            seriesRefs.current.set('stochasticOverboughtArea', stochOverboughtAreaSeries);
            console.log(`✅ [IndicatorChart:Stochastic] 과열 영역 시리즈 생성 완료 (기준: ${stochOverboughtLevel})`);
          }
          
          // ✅ Stochastic 침체 영역 (기준선 아래) - BaselineSeries 사용
          if (indicatorSettings.stochasticOversoldEnabled) {
            const stochOversoldLevel = indicatorSettings.stochasticOversold || 20;
            const stochOversoldAreaSeries = chart.addBaselineSeries({
              baseValue: { type: 'price', price: stochOversoldLevel },
              topLineColor: 'rgba(38, 166, 154, 0.8)',
              topFillColor1: 'rgba(38, 166, 154, 0.05)',
              topFillColor2: 'rgba(38, 166, 154, 0.1)',
              bottomLineColor: 'rgba(38, 166, 154, 0.8)',
              bottomFillColor1: 'rgba(38, 166, 154, 0.1)',
              bottomFillColor2: 'rgba(38, 166, 154, 0.4)',
              lineVisible: true,
              lineWidth: 1,
              priceScaleId: 'right',
              lastValueVisible: false,
              priceLineVisible: false,
            });
            seriesRefs.current.set('stochasticOversoldArea', stochOversoldAreaSeries);
            console.log(`✅ [IndicatorChart:Stochastic] 침체 영역 시리즈 생성 완료 (기준: ${stochOversoldLevel})`);
          }
          
          const stochKSeries = chart.addLineSeries({
            color: colorSettings.stochasticK || '#00BCD4',
            lineWidth: (colorSettings.stochasticKWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.stochasticKLineStyle || 'solid'),
            priceScaleId: 'right',  // ✅ 오른쪽 Y축 사용 (업비트 스타일)
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('stochasticK', stochKSeries);

          const stochDSeries = chart.addLineSeries({
            color: colorSettings.stochasticD || '#FF5722',
            lineWidth: (colorSettings.stochasticDWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.stochasticDLineStyle || 'solid'),
            priceScaleId: 'right',  // ✅ 오른쪽 Y축 사용 (업비트 스타일)
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('stochasticD', stochDSeries);

          // Stochastic 과매수 기준선 (ON일 때만 표시)
          if (indicatorSettings.stochasticOverboughtEnabled) {
            const stochOverboughtColor = colorSettings.stochasticOverboughtColor || '#EF5350';
            stochKSeries.createPriceLine({
              price: indicatorSettings.stochasticOverbought || 80,
              color: stochOverboughtColor,
              lineWidth: (colorSettings.stochasticOverboughtWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.stochasticOverboughtLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: stochOverboughtColor,
              title: '',
            });
          }

          // Stochastic 중앙값 기준선 (ON일 때만 표시)
          if (indicatorSettings.stochasticNeutralEnabled) {
            const stochNeutralColor = colorSettings.stochasticNeutralColor || (chartColors.gridColor || '#999999');
            stochKSeries.createPriceLine({
              price: indicatorSettings.stochasticNeutral || 50,
              color: stochNeutralColor,
              lineWidth: (colorSettings.stochasticNeutralWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.stochasticNeutralLineStyle || 'dotted'),
              axisLabelVisible: true, // ✅ 중앙값도 표시
              axisLabelColor: stochNeutralColor,
              title: '',
            });
          }

          // Stochastic 과매도 기준선 (ON일 때만 표시)
          if (indicatorSettings.stochasticOversoldEnabled) {
            const stochOversoldColor = colorSettings.stochasticOversoldColor || '#26A69A';
            stochKSeries.createPriceLine({
              price: indicatorSettings.stochasticOversold || 20,
              color: stochOversoldColor,
              lineWidth: (colorSettings.stochasticOversoldWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.stochasticOversoldLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: stochOversoldColor,
              title: '',
            });
          }

          console.log('[IndicatorChart:Stochastic] 기준선 + 영역 추가 완료 (20, 50, 80)');
          break;

        case 'cci':
          // ✅ CCI 과열 영역 (기준선 위) - BaselineSeries 사용
          if (indicatorSettings.cciOverboughtEnabled) {
            const cciOverboughtLevel = indicatorSettings.cciOverbought || 100;
            const cciOverboughtAreaSeries = chart.addBaselineSeries({
              baseValue: { type: 'price', price: cciOverboughtLevel },
              topLineColor: 'rgba(239, 83, 80, 0.8)',
              topFillColor1: 'rgba(239, 83, 80, 0.4)',
              topFillColor2: 'rgba(239, 83, 80, 0.1)',
              bottomLineColor: 'rgba(239, 83, 80, 0.8)',
              bottomFillColor1: 'rgba(239, 83, 80, 0.1)',
              bottomFillColor2: 'rgba(239, 83, 80, 0.05)',
              lineVisible: true,
              lineWidth: 1,
              priceScaleId: 'right',
              lastValueVisible: false,
              priceLineVisible: false,
            });
            seriesRefs.current.set('cciOverboughtArea', cciOverboughtAreaSeries);
            console.log(`✅ [IndicatorChart:CCI] 과열 영역 시리즈 생성 완료 (기준: ${cciOverboughtLevel})`);
          }
          
          // ✅ CCI 침체 영역 (기준선 아래) - BaselineSeries 사용
          if (indicatorSettings.cciOversoldEnabled) {
            const cciOversoldLevel = indicatorSettings.cciOversold || -100;
            const cciOversoldAreaSeries = chart.addBaselineSeries({
              baseValue: { type: 'price', price: cciOversoldLevel },
              topLineColor: 'rgba(38, 166, 154, 0.8)',
              topFillColor1: 'rgba(38, 166, 154, 0.05)',
              topFillColor2: 'rgba(38, 166, 154, 0.1)',
              bottomLineColor: 'rgba(38, 166, 154, 0.8)',
              bottomFillColor1: 'rgba(38, 166, 154, 0.1)',
              bottomFillColor2: 'rgba(38, 166, 154, 0.4)',
              lineVisible: true,
              lineWidth: 1,
              priceScaleId: 'right',
              lastValueVisible: false,
              priceLineVisible: false,
            });
            seriesRefs.current.set('cciOversoldArea', cciOversoldAreaSeries);
            console.log(`✅ [IndicatorChart:CCI] 침체 영역 시리즈 생성 완료 (기준: ${cciOversoldLevel})`);
          }
          
          const cciSeries = chart.addLineSeries({
            color: colorSettings.cciColor || '#4CAF50',
            lineWidth: (colorSettings.cciWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.cciLineStyle || 'solid'),
            priceScaleId: 'right',  // ✅ 오른쪽 Y축 사용 (업비트 스타일)
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('cci', cciSeries);

          // CCI 신호선 (10일 이동평균) - 주황색 (ON일 때만 생성)
          if (indicatorSettings.cciSignalEnabled) {
            const cciSignalSeries = chart.addLineSeries({
              color: colorSettings.cciSignalColor || '#FF9800',
              lineWidth: (colorSettings.cciSignalWidth || 2) as any,
              lineStyle: getLineStyle(colorSettings.cciSignalLineStyle || 'solid'),
              priceScaleId: 'right',
              lastValueVisible: true, // ✅ Y축에 최종값 표시
              priceLineVisible: false, // ✅ 가격선 완전 숨김
            });
            seriesRefs.current.set('cciSignal', cciSignalSeries);
          }

          // CCI 과매수 기준선 (ON일 때만 표시)
          if (indicatorSettings.cciOverboughtEnabled) {
            const cciOverboughtColor = colorSettings.cciOverboughtColor || '#EF5350';
            cciSeries.createPriceLine({
              price: indicatorSettings.cciOverbought || 100,
              color: cciOverboughtColor,
              lineWidth: (colorSettings.cciOverboughtWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.cciOverboughtLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: cciOverboughtColor,
              title: '',
            });
          }

          // CCI 중앙값 기준선 (ON일 때만 표시)
          if (indicatorSettings.cciMiddleEnabled) {
            const cciMiddleColor = colorSettings.cciMiddleColor || (chartColors.gridColor || '#999999');
            cciSeries.createPriceLine({
              price: indicatorSettings.cciMiddle || 0,
              color: cciMiddleColor,
              lineWidth: (colorSettings.cciMiddleWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.cciMiddleLineStyle || 'dotted'),
              axisLabelVisible: true, // ✅ 중앙값도 표시
              axisLabelColor: cciMiddleColor,
              title: '',
            });
          }

          // CCI 과매도 기준선 (ON일 때만 표시)
          if (indicatorSettings.cciOversoldEnabled) {
            const cciOversoldColor = colorSettings.cciOversoldColor || '#26A69A';
            cciSeries.createPriceLine({
              price: indicatorSettings.cciOversold || -100,
              color: cciOversoldColor,
              lineWidth: (colorSettings.cciOversoldWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.cciOversoldLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: cciOversoldColor,
              title: '',
            });
          }

          console.log('[IndicatorChart:CCI] CCI 라인 + 신호선 + 기준선 + 영역 추가 완료 (-100, 0, +100)');
          break;

        case 'momentum':
          const momentumSeries = chart.addLineSeries({
            color: colorSettings.momentumColor || '#2196F3',
            lineWidth: (colorSettings.momentumWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.momentumLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('momentum', momentumSeries);

          // Momentum 0선 (ON일 때만 표시)
          if (indicatorSettings.momentumZeroLineEnabled) {
            const momentumZeroLineColor = colorSettings.momentumZeroLineColor || (chartColors.gridColor || '#999999');
            momentumSeries.createPriceLine({
              price: 0,
              color: momentumZeroLineColor,
              lineWidth: (colorSettings.momentumZeroLineWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.momentumZeroLineStyle || 'dotted'),
              axisLabelVisible: true, // ✅ 0선 라벨도 표시
              axisLabelColor: momentumZeroLineColor,
              title: '',
            });
            console.log('[IndicatorChart:Momentum] 0선 기준선 추가 완료');
          }

          console.log('[IndicatorChart:Momentum] 기준선 추가 완료');
          break;

        case 'williamsR':
          const williamsRSeries = chart.addLineSeries({
            color: colorSettings.williamsRColor || '#00BCD4',
            lineWidth: (colorSettings.williamsRWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.williamsRLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('williamsR', williamsRSeries);

          // Williams %R 과매수 기준선 (ON일 때만 표시)
          if (indicatorSettings.williamsROverboughtEnabled) {
            const williamsOverboughtColor = colorSettings.williamsROverboughtColor || '#EF5350';
          williamsRSeries.createPriceLine({
            price: indicatorSettings.williamsROverbought || -20,
              color: williamsOverboughtColor,
            lineWidth: (colorSettings.williamsROverboughtWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.williamsROverboughtLineStyle || 'dashed'),
            axisLabelVisible: true,
              axisLabelColor: williamsOverboughtColor,
            title: '',
          });
            console.log('[IndicatorChart:WilliamsR] 과열선 추가 완료');
          }

          // Williams %R 중앙값 기준선 (ON일 때만 표시)
          if (indicatorSettings.williamsRMiddleEnabled) {
            const williamsMiddleColor = colorSettings.williamsRMiddleColor || chartColors.gridColor || '#4a4a4a';
            williamsRSeries.createPriceLine({
              price: indicatorSettings.williamsRMiddle || -50,
              color: williamsMiddleColor,
              lineWidth: (colorSettings.williamsRMiddleWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.williamsRMiddleLineStyle || 'dotted'),
              axisLabelVisible: true, // ✅ 중앙값도 표시
              axisLabelColor: williamsMiddleColor,
              title: '',
            });
            console.log('[IndicatorChart:WilliamsR] 중앙값선 추가 완료');
          }

          // Williams %R 과매도 기준선 (ON일 때만 표시)
          if (indicatorSettings.williamsROversoldEnabled) {
            const williamsOversoldColor = colorSettings.williamsROversoldColor || '#26A69A';
          williamsRSeries.createPriceLine({
            price: indicatorSettings.williamsROversold || -80,
              color: williamsOversoldColor,
            lineWidth: (colorSettings.williamsROversoldWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.williamsROversoldLineStyle || 'dashed'),
            axisLabelVisible: true,
              axisLabelColor: williamsOversoldColor,
            title: '',
          });
            console.log('[IndicatorChart:WilliamsR] 침체선 추가 완료');
          }

          console.log('[IndicatorChart:WilliamsR] 기준선 추가 완료');
          break;

        case 'dmi':
        case 'adx':
          const adxSeries = chart.addLineSeries({
            color: colorSettings.adxColor || '#9C27B0',
            lineWidth: (colorSettings.adxWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.adxLineStyle || 'solid'),
            priceScaleId: 'right',  // ✅ 오른쪽 Y축 사용 (업비트 스타일)
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
            visible: indicatorType === 'dmi' ? indicatorSettings.dmiAdxEnabled : indicatorSettings.adxEnabled,  // ✅ DMI는 dmiAdxEnabled, ADX는 adxEnabled 사용
          });
          seriesRefs.current.set('adx', adxSeries);

          const plusDISeries = chart.addLineSeries({
            color: colorSettings.plusDiColor || '#4CAF50',
            lineWidth: (colorSettings.plusDiWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.plusDiLineStyle || 'solid'),
            priceScaleId: 'right',  // ✅ 오른쪽 Y축 사용 (업비트 스타일)
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
            visible: indicatorSettings.plusDiEnabled,  // ✅ +DI ON/OFF 제어
          });
          seriesRefs.current.set('plusDI', plusDISeries);

          const minusDISeries = chart.addLineSeries({
            color: colorSettings.minusDiColor || '#F44336',
            lineWidth: (colorSettings.minusDiWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.minusDiLineStyle || 'solid'),
            priceScaleId: 'right',  // ✅ 오른쪽 Y축 사용 (업비트 스타일)
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
            visible: indicatorSettings.minusDiEnabled,  // ✅ -DI ON/OFF 제어
          });
          seriesRefs.current.set('minusDI', minusDISeries);

          // DMI일 때는 DMI 기준선, ADX일 때는 ADX 기준선
          if (indicatorType === 'dmi') {
            // ✅ DMI 기준선은 plusDI 시리즈에 추가 (ADX OFF 시에도 유지되도록)
            // DMI 과열 기준선 (ON일 때만 표시)
            if (indicatorSettings.dmiOverboughtEnabled) {
              const dmiOverboughtColor = colorSettings.dmiOverboughtColor || '#EF5350';
              plusDISeries.createPriceLine({
                price: indicatorSettings.dmiOverbought || 30,
                color: dmiOverboughtColor,
                lineWidth: (colorSettings.dmiOverboughtWidth || 1) as any,
                lineStyle: getLineStyle(colorSettings.dmiOverboughtLineStyle || 'dashed'),
                axisLabelVisible: true,
                axisLabelColor: dmiOverboughtColor,
                title: '',
              });
            }

            // DMI 중간값 기준선 (ON일 때만 표시)
            if (indicatorSettings.dmiMiddleEnabled) {
              const dmiMiddleColor = colorSettings.dmiMiddleColor || '#FFA726';
              plusDISeries.createPriceLine({
                price: indicatorSettings.dmiMiddle || 20,
                color: dmiMiddleColor,
                lineWidth: (colorSettings.dmiMiddleWidth || 1) as any,
                lineStyle: getLineStyle(colorSettings.dmiMiddleLineStyle || 'dashed'),
                axisLabelVisible: true,
                axisLabelColor: dmiMiddleColor,
                title: '',
              });
            }

            // DMI 침체 기준선 (ON일 때만 표시)
            if (indicatorSettings.dmiOversoldEnabled) {
              const dmiOversoldColor = colorSettings.dmiOversoldColor || '#26A69A';
              plusDISeries.createPriceLine({
                price: indicatorSettings.dmiOversold || 10,
                color: dmiOversoldColor,
                lineWidth: (colorSettings.dmiOversoldWidth || 1) as any,
                lineStyle: getLineStyle(colorSettings.dmiOversoldLineStyle || 'dashed'),
                axisLabelVisible: true,
                axisLabelColor: dmiOversoldColor,
                title: '',
              });
            }
            console.log('[IndicatorChart:DMI] DMI 기준선 추가 완료 (과열, 중간값, 침체) - plusDI 시리즈에 추가');
          } else {
            // ADX 강한 추세(25) 기준선 (ON일 때만 표시)
            if (indicatorSettings.adxStrongEnabled) {
              const adxStrongColor = colorSettings.adxStrongColor || '#4CAF50';
              adxSeries.createPriceLine({
                price: indicatorSettings.adxStrongThreshold || 25,
                color: adxStrongColor,
                lineWidth: (colorSettings.adxStrongWidth || 1) as any,
                lineStyle: getLineStyle(colorSettings.adxStrongLineStyle || 'dashed'),
                axisLabelVisible: true,
                axisLabelColor: adxStrongColor,
                title: '',
              });
            }

            // ADX 약한 추세(20) 기준선 (ON일 때만 표시)
            if (indicatorSettings.adxWeakEnabled) {
              const adxWeakColor = colorSettings.adxWeakColor || '#FF9800';
              adxSeries.createPriceLine({
                price: indicatorSettings.adxWeakThreshold || 20,
                color: adxWeakColor,
                lineWidth: (colorSettings.adxWeakWidth || 1) as any,
                lineStyle: getLineStyle(colorSettings.adxWeakLineStyle || 'dashed'),
                axisLabelVisible: true, // ✅ 약한 추세 라벨도 표시
                axisLabelColor: adxWeakColor,
                title: '',
              });
            }
            console.log('[IndicatorChart:ADX] ADX 기준선 추가 완료 (강한 추세, 약한 추세)');
          }
          break;

        case 'obv':
          const obvSeries = chart.addLineSeries({
            color: colorSettings.obvColor || '#FF9800',
            lineWidth: (colorSettings.obvWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.obvLineStyle || 'solid'),
            priceScaleId: 'right',  // ✅ 오른쪽 Y축 사용 (업비트 스타일)
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('obv', obvSeries);

          // OBV 신호선 (9일 이동평균) - 하늘색
          const obvSignalSeries = chart.addLineSeries({
            color: colorSettings.obvSignalColor || '#00BCD4',
            lineWidth: (colorSettings.obvSignalWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.obvSignalLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('obvSignal', obvSignalSeries);

          console.log('[IndicatorChart:OBV] OBV 라인 + 신호선 추가 완료');
          break;

        case 'trix':
          const trixSeries = chart.addLineSeries({
            color: colorSettings.trixColor || '#E91E63',
            lineWidth: (colorSettings.trixWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.trixLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('trix', trixSeries);

          const trixSignalSeries = chart.addLineSeries({
            color: colorSettings.trixSignalColor || '#FFC107',
            lineWidth: (colorSettings.trixSignalWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.trixSignalLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('trixSignal', trixSignalSeries);

          // TRIX 과매수 기준선 (ON일 때만 표시)
          if (indicatorSettings.trixOverboughtEnabled) {
            const trixOverboughtColor = colorSettings.trixOverboughtColor || '#EF5350';
            trixSeries.createPriceLine({
              price: indicatorSettings.trixOverbought || 0.5,
              color: trixOverboughtColor,
              lineWidth: (colorSettings.trixOverboughtWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.trixOverboughtLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: trixOverboughtColor,
              title: '',
            });
          }

          // TRIX 중앙값 기준선 (ON일 때만 표시)
          if (indicatorSettings.trixMiddleEnabled) {
            const trixMiddleColor = colorSettings.trixMiddleColor || (chartColors.gridColor || '#999999');
            trixSeries.createPriceLine({
              price: indicatorSettings.trixMiddle || 0,
              color: trixMiddleColor,
              lineWidth: (colorSettings.trixMiddleWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.trixMiddleLineStyle || 'dotted'),
              axisLabelVisible: true, // ✅ 중앙값도 표시
              axisLabelColor: trixMiddleColor,
              title: '',
            });
          }

          // TRIX 과매도 기준선 (ON일 때만 표시)
          if (indicatorSettings.trixOversoldEnabled) {
            const trixOversoldColor = colorSettings.trixOversoldColor || '#26A69A';
            trixSeries.createPriceLine({
              price: indicatorSettings.trixOversold || -0.5,
              color: trixOversoldColor,
              lineWidth: (colorSettings.trixOversoldWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.trixOversoldLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: trixOversoldColor,
              title: '',
            });
          }

          console.log('[IndicatorChart:TRIX] TRIX 라인 + 신호선 + 기준선 추가 완료');
          break;

        case 'newPsychological':
          const newPsySeries = chart.addLineSeries({
            color: colorSettings.newPsychologicalColor || '#9C27B0',
            lineWidth: (colorSettings.newPsychologicalWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.newPsychologicalLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('newPsy', newPsySeries);

          // 신심리도 과열 기준선 (ON일 때만 표시)
          if (indicatorSettings.newPsychologicalOverboughtEnabled) {
            const newPsyOverboughtColor = colorSettings.newPsychologicalOverboughtColor || '#EF5350';
            newPsySeries.createPriceLine({
              price: indicatorSettings.newPsychologicalOverbought || 75,
              color: newPsyOverboughtColor,
              lineWidth: (colorSettings.newPsychologicalOverboughtWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.newPsychologicalOverboughtLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: newPsyOverboughtColor,
              title: '',
            });
          }

          // 신심리도 중앙값 기준선 (ON일 때만 표시)
          if (indicatorSettings.newPsychologicalMiddleEnabled) {
            newPsySeries.createPriceLine({
              price: indicatorSettings.newPsychologicalMiddle || 50,
              color: colorSettings.newPsychologicalNeutralColor || chartColors.gridColor || '#4a4a4a',
              lineWidth: (colorSettings.newPsychologicalNeutralWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.newPsychologicalNeutralLineStyle || 'dotted'),
              axisLabelVisible: false,
              title: '',
            });
          }

          // 신심리도 과매도 기준선 (ON일 때만 표시)
          if (indicatorSettings.newPsychologicalOversoldEnabled) {
            const newPsyOversoldColor = colorSettings.newPsychologicalOversoldColor || '#26A69A';
            newPsySeries.createPriceLine({
              price: indicatorSettings.newPsychologicalOversold || 25,
              color: newPsyOversoldColor,
              lineWidth: (colorSettings.newPsychologicalOversoldWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.newPsychologicalOversoldLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: newPsyOversoldColor,
              title: '',
            });
          }
          break;

        case 'investPsychological':
          const investPsySeries = chart.addLineSeries({
            color: colorSettings.investPsychologicalColor || '#673AB7',
            lineWidth: (colorSettings.investPsychologicalWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.investPsychologicalLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('investPsy', investPsySeries);

          // 투자심리도 과열 기준선 (ON일 때만 표시)
          if (indicatorSettings.investPsychologicalOverboughtEnabled) {
            const investPsyOverboughtColor = colorSettings.investPsychologicalOverboughtColor || '#EF5350';
            investPsySeries.createPriceLine({
              price: indicatorSettings.investPsychologicalOverbought || 75,
              color: investPsyOverboughtColor,
              lineWidth: (colorSettings.investPsychologicalOverboughtWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.investPsychologicalOverboughtLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: investPsyOverboughtColor,
              title: '',
            });
          }

          // 투자심리도 중앙값 기준선 (ON일 때만 표시)
          if (indicatorSettings.investPsychologicalMiddleEnabled) {
            investPsySeries.createPriceLine({
              price: indicatorSettings.investPsychologicalMiddle || 50,
              color: colorSettings.investPsychologicalMiddleColor || (chartColors.gridColor || '#999999'),
              lineWidth: (colorSettings.investPsychologicalMiddleWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.investPsychologicalMiddleLineStyle || 'dotted'),
              axisLabelVisible: false,
              title: '',
            });
          }

          // 투자심리도 침체 기준선 (ON일 때만 표시)
          if (indicatorSettings.investPsychologicalOversoldEnabled) {
            const investPsyOversoldColor = colorSettings.investPsychologicalOversoldColor || '#26A69A';
            investPsySeries.createPriceLine({
              price: indicatorSettings.investPsychologicalOversold || 25,
              color: investPsyOversoldColor,
              lineWidth: (colorSettings.investPsychologicalOversoldWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.investPsychologicalOversoldLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: investPsyOversoldColor,
              title: '',
            });
          }
          
          console.log('[IndicatorChart:투자심리도] 투자심리도 라인 + 기준선 추가 완료');
          break;

        case 'disparity':
          // 이격도 - 4개의 선 생성 (초단기, 단기, 중기, 장기)
          const disparityUltraShortSeries = chart.addLineSeries({
            color: colorSettings.disparityUltraShortColor || '#FF6B35',
            lineWidth: (colorSettings.disparityUltraShortWidth || 1.5) as LineWidth,
            lineStyle: getLineStyle(colorSettings.disparityUltraShortLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
            visible: indicatorSettings.disparityUltraShortEnabled ?? true,
          });
          seriesRefs.current.set('disparityUltraShort', disparityUltraShortSeries);

          const disparityShortSeries = chart.addLineSeries({
            color: colorSettings.disparityShortColor || '#4CAF50',
            lineWidth: (colorSettings.disparityShortWidth || 2) as LineWidth,
            lineStyle: getLineStyle(colorSettings.disparityShortLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
            visible: indicatorSettings.disparityShortEnabled ?? true,
          });
          seriesRefs.current.set('disparityShort', disparityShortSeries);

          const disparityMidSeries = chart.addLineSeries({
            color: colorSettings.disparityMidColor || '#2196F3',
            lineWidth: (colorSettings.disparityMidWidth || 2) as LineWidth,
            lineStyle: getLineStyle(colorSettings.disparityMidLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
            visible: indicatorSettings.disparityMidEnabled ?? true,
          });
          seriesRefs.current.set('disparityMid', disparityMidSeries);

          const disparityLongSeries = chart.addLineSeries({
            color: colorSettings.disparityLongColor || '#9C27B0',
            lineWidth: (colorSettings.disparityLongWidth || 2) as LineWidth,
            lineStyle: getLineStyle(colorSettings.disparityLongLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
            visible: indicatorSettings.disparityLongEnabled ?? true,
          });
          seriesRefs.current.set('disparityLong', disparityLongSeries);

          // ✅ 이격도 100 기준선 - visible인 첫 번째 시리즈에 추가 (초단기5 OFF 시에도 유지)
          if (indicatorSettings.disparityBaseLineEnabled ?? true) {
            // visible인 시리즈 찾기 (우선순위: 초단기5 > 단기10 > 중기20 > 장기60)
            const visibleSeries = 
              (indicatorSettings.disparityUltraShortEnabled ?? true) ? disparityUltraShortSeries :
              (indicatorSettings.disparityShortEnabled ?? true) ? disparityShortSeries :
              (indicatorSettings.disparityMidEnabled ?? true) ? disparityMidSeries :
              disparityLongSeries; // 기본값: 장기60 (마지막 시리즈)
            
            visibleSeries.createPriceLine({
              price: 100,
              color: colorSettings.disparityBaseLineColor || (chartColors.gridColor || '#999999'),
              lineWidth: (colorSettings.disparityBaseLineWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.disparityBaseLineStyle || 'dotted'),
              axisLabelVisible: false,
              title: '',
            });
          }
          break;

        case 'bwi':
          const bwiSeries = chart.addLineSeries({
            color: colorSettings.bwiColor || '#FF5722',
            lineWidth: (colorSettings.bwiWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.bwiLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('bwi', bwiSeries);
          
          console.log('[IndicatorChart:BWI] BWI 라인 추가 완료');
          break;

        case 'volumeOsc':
          const volumeOscSeries = chart.addLineSeries({
            color: colorSettings.volumeOscColor || '#795548',
            lineWidth: (colorSettings.volumeOscWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.volumeOscLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false, // ✅ 가격선 완전 숨김
          });
          seriesRefs.current.set('volumeOsc', volumeOscSeries);

          // Volume OSC 0선 (ON일 때만 표시)
          if (indicatorSettings.volumeOscZeroLineEnabled) {
            const volumeOscZeroLineColor = colorSettings.volumeOscZeroLineColor || (chartColors.gridColor || '#999999');
            volumeOscSeries.createPriceLine({
              price: 0,
              color: volumeOscZeroLineColor,
              lineWidth: (colorSettings.volumeOscZeroLineWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.volumeOscZeroLineStyle || 'dotted'),
              axisLabelVisible: true, // ✅ 0선 라벨도 표시
              axisLabelColor: volumeOscZeroLineColor,
              title: '',
            });
          }
          
          console.log('[IndicatorChart:VolumeOSC] VolumeOSC 라인 + 기준선 추가 완료');
          break;

        case 'vr':
          // ✅ VR 메인 라인 (항상 생성)
          const vrSeries = chart.addLineSeries({
            color: colorSettings.vrColor || '#607D8B',
            lineWidth: (colorSettings.vrWidth || 2) as any,
            lineStyle: getLineStyle(colorSettings.vrLineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false,
            visible: indicatorSettings.vrEnabled !== false, // ✅ VR ON/OFF 제어
          });
          seriesRefs.current.set('vr', vrSeries);

          // ✅ VR2 라인 (항상 생성하되, vr2Enabled에 따라 visible 제어)
          const vr2Series = chart.addLineSeries({
            color: colorSettings.vr2Color || '#FF9800',
            lineWidth: (colorSettings.vr2Width || 2) as any,
            lineStyle: getLineStyle(colorSettings.vr2LineStyle || 'solid'),
            priceScaleId: 'right',
            lastValueVisible: true, // ✅ Y축에 최종값 표시
            priceLineVisible: false,
            visible: indicatorSettings.vr2Enabled === true, // ✅ VR2 ON/OFF 제어
          });
          seriesRefs.current.set('vr2', vr2Series);

          // VR 과열 기준선 (ON일 때만 표시)
          if (indicatorSettings.vrOverboughtEnabled) {
            const vrOverboughtColor = colorSettings.vrOverboughtColor || '#EF5350';
            vrSeries.createPriceLine({
              price: indicatorSettings.vrOverbought || 150,
              color: vrOverboughtColor,
              lineWidth: (colorSettings.vrOverboughtWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.vrOverboughtLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: vrOverboughtColor,
              title: '',
            });
          }

          // VR 기준선 (ON일 때만 표시)
          if (indicatorSettings.vrBaseLineEnabled) {
            const vrBaseLineColor = colorSettings.vrBaseLineColor || (chartColors.gridColor || '#999999');
            vrSeries.createPriceLine({
              price: indicatorSettings.vrBaseLine || 100,
              color: vrBaseLineColor,
              lineWidth: (colorSettings.vrBaseLineWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.vrBaseLineStyle || 'dotted'),
              axisLabelVisible: true, // ✅ 기준선 라벨도 표시
              axisLabelColor: vrBaseLineColor,
              title: '',
            });
          }

          // VR 침체 기준선 (ON일 때만 표시)
          if (indicatorSettings.vrOversoldEnabled) {
            const vrOversoldColor = colorSettings.vrOversoldColor || '#26A69A';
            vrSeries.createPriceLine({
              price: indicatorSettings.vrOversold || 70,
              color: vrOversoldColor,
              lineWidth: (colorSettings.vrOversoldWidth || 1) as any,
              lineStyle: getLineStyle(colorSettings.vrOversoldLineStyle || 'dashed'),
              axisLabelVisible: true,
              axisLabelColor: vrOversoldColor,
              title: '',
            });
          }
          
          console.log('[IndicatorChart:VR] VR + VR2 라인 생성 완료 (VR visible:', indicatorSettings.vrEnabled, ', VR2 visible:', indicatorSettings.vr2Enabled, ')');
          break;
      }
    };

    // ✅ 데이터 적용 로직을 별도 함수로 분리
    const applyChartData = useCallback((forceApply: boolean = false) => {
      // ✅ 최신 데이터 사용 (클로저 문제 해결)
      const currentData = dataRef.current;
      
      if (!chartRef.current || currentData.length === 0) {
        console.log(`[IndicatorChart:${type}] 차트 없음 또는 데이터 없음`);
        return;
      }

      // ✅ 이미 업데이트 중이면 skip (동시 실행 방지)
      if (isDataUpdatingRef.current) {
        console.log(`[IndicatorChart:${type}] 이미 데이터 업데이트 중 - 스킵`);
        return;
      }

      // ✅ 관련 설정 추출 (변경 여부 확인용)
      const getRelevantKeyPatterns = (indicatorType: IndicatorType): string[] => {
        switch (indicatorType) {
          case 'volume':
            return ['volume'];
          case 'macd':
            return ['macd', 'signal', 'histogram', 'slowEma'];
          case 'rsi':
            return ['rsi'];
          case 'stochastic':
            return ['stochastic'];
          case 'cci':
            return ['cci'];
          case 'williamsR':
            return ['williamsR'];
          case 'adx':
            return ['adx', 'plusDi', 'minusDi'];
          case 'dmi':
            return ['dmi', 'plusDi', 'minusDi'];
          case 'trix':
            return ['trix'];
          case 'obv':
            return ['obv'];
          case 'momentum':
            return ['momentum'];
          case 'volumeOsc':
            return ['volumeOsc'];
          case 'vr':
            return ['vr', 'vr2'];  // ✅ vr2 추가
          case 'bwi':
            return ['bwi'];
          case 'disparity':
            return ['disparity'];
          case 'newPsychological':
            return ['newPsychological'];
          case 'investPsychological':
            return ['investPsychological'];
          default:
            return [indicatorType];
        }
      };

      const patterns = getRelevantKeyPatterns(type);
      
      // ✅ 현재 타입과 관련된 설정만 추출
      const relevantIndicatorKeys = Object.keys(indicatorSettings).filter(key => 
        patterns.some(pattern => key.startsWith(pattern))
      );
      const relevantColorKeys = Object.keys(colorSettings).filter(key => 
        patterns.some(pattern => key.startsWith(pattern))
      );

      // 관련 설정값들을 문자열로 직렬화
      const currentDataUpdateSettings = relevantIndicatorKeys
        .map(key => `${key}:${(indicatorSettings as any)[key]}`)
        .sort()
        .join('|');
      const currentDataUpdateColors = relevantColorKeys
        .map(key => `${key}:${(colorSettings as any)[key]}`)
        .sort()
        .join('|');

      // ✅ data 변경 여부 확인
      const currentDataLength = currentData.length;
      const currentDataFirstTime = currentData.length > 0 ? currentData[0].time : '';
      const currentDataLastTime = currentData.length > 0 ? currentData[currentData.length - 1].time : '';
      
      const isDataChanged = 
        prevDataLengthRef.current !== currentDataLength ||
        prevDataFirstTimeRef.current !== currentDataFirstTime ||
        prevDataLastTimeRef.current !== currentDataLastTime;

      // ✅ 강제 적용이 아닐 때만 변경 여부 확인
      if (!forceApply) {
        // ✅ 첫 렌더링 또는 시리즈 재생성 시 (ref 초기화됨)
        const isFirstRenderOrRecreated = prevDataUpdateSettingsRef.current === '' || prevDataUpdateColorsRef.current === '';
        
        if (isFirstRenderOrRecreated) {
          console.log(`[IndicatorChart:${type}] 첫 렌더링 또는 시리즈 재생성 - 데이터 적용`);
          // ref 업데이트는 데이터 적용 완료 후에 수행 (아래쪽)
        } else {
          // ✅ data가 변경되지 않고, 관련 설정도 변경되지 않았으면 skip
          if (
            !isDataChanged &&
            prevDataUpdateSettingsRef.current === currentDataUpdateSettings &&
            prevDataUpdateColorsRef.current === currentDataUpdateColors
          ) {
            console.log(`[IndicatorChart:${type}] 데이터/관련 설정 변경 없음 - 데이터 재적용 생략`);
            return;
          }
          
          // ref 업데이트는 데이터 적용 완료 후에 수행 (아래쪽)
        }
      } else {
        // forceApply === true: 무조건 데이터 적용
        console.log(`[IndicatorChart:${type}] 강제 데이터 적용 (시리즈 재생성 후)`);
      }

      // ✅ 데이터 업데이트 시작 - 플래그 설정
      isDataUpdatingRef.current = true;
      console.log(`[IndicatorChart:${type}] 데이터 업데이트 시작: ${currentData.length}개`);

      // ✅ 데이터 업데이트 시마다 grid, priceScale, timeScale 옵션 재적용 (수평선 완전 제거)
      const gridOptions = {
        grid: {
          vertLines: { 
            visible: true, 
            color: chartColors.gridColor,
          },
          horzLines: { 
            visible: false,
          },
        },
        rightPriceScale: {
          borderVisible: false,  // Y축 경계선 숨김
          ticksVisible: false,   // 눈금 숨김
        },
        timeScale: {
          borderVisible: false,  // X축 경계선 숨김
        },
      };
      console.log(`[IndicatorChart:${type}] 🚫 데이터 업데이트 시 수평선 제거 옵션 재적용`);
      chartRef.current.applyOptions(gridOptions);

      try {
        const formattedData = currentData.map(item => {
          const timeStr = item.time ?? item.timestamp ?? item.date;
          const timeInSeconds = timeStr ? new Date(timeStr as string).getTime() / 1000 : NaN;
          return {
            ...item,
            timeSeconds: timeInSeconds as Time,
          };
        });

        // 보조지표 타입별 데이터 설정
        switch (type) {
          case 'volume':
            const volumeSeries = seriesRefs.current.get('volume');
            if (volumeSeries) {
              // ✅ 미래 영역 대응: undefined 값을 마지막 유효한 값으로 대체
              let lastValidVolume: number | undefined = undefined;
              const volumeData = formattedData.map(d => {
                if (d.volume !== undefined && d.volume !== null) {
                  lastValidVolume = d.volume;
                }
                const isRising = (d.close ?? 0) >= (d.open ?? 0);
                const isEnabled = isRising ? indicatorSettings.volumeRisingEnabled : indicatorSettings.volumeFallingEnabled;
                
                return {
                  time: d.timeSeconds,
                  value: isEnabled ? (d.volume !== undefined ? d.volume : (lastValidVolume ?? 0)) : 0,
                  color: d.volumeColor || (isRising ? colorSettings.volumeRising || '#ef4444' : colorSettings.volumeFalling || '#2196f3'),
                };
              });
              volumeSeries.setData(volumeData);
              console.log(`[IndicatorChart:volume] 데이터: ${volumeData.length}개 (상승 ON: ${indicatorSettings.volumeRisingEnabled}, 하락 ON: ${indicatorSettings.volumeFallingEnabled})`);
            }
            break;

          case 'macd':
            const macdLineSeries = seriesRefs.current.get('macdLine');
            const signalLineSeries = seriesRefs.current.get('signalLine');
            const histogramSeries = seriesRefs.current.get('histogram');

            // ✅ MACD 데이터 수집 및 이상치 제거
            const allMacdValues: number[] = [];

            if (macdLineSeries) {
              // ✅ 유효한 데이터만 필터링하여 전달 (0 fallback 제거)
              const macdLineData = formattedData
                .filter(d => d.macdLine !== undefined && d.macdLine !== null && isFinite(d.macdLine))
                .map(d => {
                  allMacdValues.push(d.macdLine!);
                return {
                  time: d.timeSeconds,
                    value: d.macdLine!,
                };
              });
              macdLineSeries.setData(macdLineData);
              console.log(`[IndicatorChart:MACD] Line 데이터: ${macdLineData.length}개 (유효한 데이터만)`);
            }

            if (signalLineSeries) {
              // ✅ 유효한 데이터만 필터링하여 전달 (0 fallback 제거)
              const signalLineData = formattedData
                .filter(d => d.signalLine !== undefined && d.signalLine !== null && isFinite(d.signalLine))
                .map(d => {
                  allMacdValues.push(d.signalLine!);
                return {
                  time: d.timeSeconds,
                    value: d.signalLine!,
                };
              });
              signalLineSeries.setData(signalLineData);
              console.log(`[IndicatorChart:MACD] Signal 데이터: ${signalLineData.length}개 (유효한 데이터만)`);
            }

            if (histogramSeries) {
              // ✅ 유효한 데이터만 필터링하여 전달 (0 fallback 제거)
              // ✅ 히스토그램 상승/하락 각각의 enable 상태 확인
              const histogramData = formattedData
                .filter(d => {
                  if (d.macdHistogram === undefined || d.macdHistogram === null || !isFinite(d.macdHistogram)) {
                    return false;
                  }
                  // 상승 히스토그램이 disabled이고 값이 양수면 제외
                  if (d.macdHistogram > 0 && indicatorSettings.histogramPositiveEnabled === false) {
                    return false;
                  }
                  // 하락 히스토그램이 disabled이고 값이 음수면 제외
                  if (d.macdHistogram < 0 && indicatorSettings.histogramNegativeEnabled === false) {
                    return false;
                  }
                  return true;
                })
                .map(d => {
                  allMacdValues.push(d.macdHistogram!);
                  return {
                    time: d.timeSeconds,
                    value: d.macdHistogram!,
                    color: d.macdHistogram! > 0 
                      ? (colorSettings.histogramPositive || '#26a69a')
                      : (colorSettings.histogramNegative || '#ef5350'),
                };
              });
              console.log(`[IndicatorChart:MACD] 히스토그램 데이터: ${histogramData.length}개 (유효한 데이터만)`,
                histogramData.length > 0 ? histogramData.slice(-3) : '없음');
              histogramSeries.setData(histogramData);
            } else {
              console.warn('[IndicatorChart:MACD] histogramSeries가 없습니다!');
            }

            // ✅ MACD 전용 autoscaleInfoProvider: 이상치 제거 및 적절한 Y축 범위 설정
            if (allMacdValues.length > 0 && chartRef.current) {
              // 이상치 제거 (IQR 방식)
              const sortedValues = [...allMacdValues].sort((a, b) => a - b);
              const q1Index = Math.floor(sortedValues.length * 0.25);
              const q3Index = Math.floor(sortedValues.length * 0.75);
              const q1 = sortedValues[q1Index];
              const q3 = sortedValues[q3Index];
              const iqr = q3 - q1;
              const lowerBound = q1 - 1.5 * iqr;
              const upperBound = q3 + 1.5 * iqr;

              // 이상치를 제거한 값들로 범위 계산
              const filteredValues = allMacdValues.filter(v => v >= lowerBound && v <= upperBound);

              if (filteredValues.length > 0) {
                const minValue = Math.min(...filteredValues);
                const maxValue = Math.max(...filteredValues);
                const range = maxValue - minValue;
                const margin = range * 0.1; // 10% 여백

                const finalMin = minValue - margin;
                const finalMax = maxValue + margin;

                console.log(`[IndicatorChart:MACD] Y축 범위 계산: ${finalMin.toFixed(2)} ~ ${finalMax.toFixed(2)} (이상치 제거 후)`);

                // 모든 MACD 시리즈에 autoscaleInfoProvider 적용
                [macdLineSeries, signalLineSeries, histogramSeries].forEach(series => {
                  if (series) {
                    series.applyOptions({
                      autoscaleInfoProvider: () => ({
                        priceRange: {
                          minValue: finalMin,
                          maxValue: finalMax,
                        },
                      }),
                    });
                  }
                });
              }
            }
            break;

          case 'rsi':
            const rsiSeries = seriesRefs.current.get('rsi');
            const rsiSignalSeries = seriesRefs.current.get('rsiSignal');
            const rsiOverboughtAreaSeries = seriesRefs.current.get('rsiOverboughtArea');
            const rsiOversoldAreaSeries = seriesRefs.current.get('rsiOversoldArea');

            if (rsiSeries) {
              // ✅ 미래 영역 대응: undefined 값을 마지막 유효한 값으로 대체
              let lastValidRsi: number | undefined = undefined;
              const allRsiData = formattedData.map(d => {
                if (d.rsi !== undefined && d.rsi !== null) {
                  lastValidRsi = d.rsi;
                }
                return {
                  time: d.timeSeconds,
                  value: d.rsi !== undefined ? d.rsi : (lastValidRsi ?? 50),
                };
              });
              rsiSeries.setData(allRsiData);
              console.log(`[IndicatorChart:RSI] RSI 데이터: ${allRsiData.length}개 (미래 영역 포함)`);

              // ✅ RSI 과열 영역 데이터 (기준선 위) - BaselineSeries는 RSI 값 그대로 사용
              if (rsiOverboughtAreaSeries && indicatorSettings.rsiOverboughtEnabled) {
                const rsiOverboughtLevel = indicatorSettings.rsiOverbought || 70;
                rsiOverboughtAreaSeries.setData(allRsiData);
                const overboughtCount = allRsiData.filter(d => d.value > rsiOverboughtLevel).length;
                console.log(`✅ [IndicatorChart:RSI] 과열 영역 데이터 설정 완료 (기준: ${rsiOverboughtLevel}, 총: ${allRsiData.length}개, 과열: ${overboughtCount}개)`);
                if (allRsiData.length > 0) {
                  console.log(`   샘플 데이터:`, allRsiData.slice(-5).map(d => `${d.value.toFixed(2)}`).join(', '));
                }
              }

              // ✅ RSI 침체 영역 데이터 (기준선 아래) - BaselineSeries는 RSI 값 그대로 사용
              if (rsiOversoldAreaSeries && indicatorSettings.rsiOversoldEnabled) {
                const rsiOversoldLevel = indicatorSettings.rsiOversold || 30;
                rsiOversoldAreaSeries.setData(allRsiData);
                const oversoldCount = allRsiData.filter(d => d.value < rsiOversoldLevel).length;
                console.log(`✅ [IndicatorChart:RSI] 침체 영역 데이터 설정 완료 (기준: ${rsiOversoldLevel}, 총: ${allRsiData.length}개, 침체: ${oversoldCount}개)`);
                if (allRsiData.length > 0) {
                  console.log(`   샘플 데이터:`, allRsiData.slice(-5).map(d => `${d.value.toFixed(2)}`).join(', '));
                }
              }

              // RSI 신호선 계산 (RSI의 9일 단순이동평균)
              if (rsiSignalSeries && allRsiData.length > 0) {
                const signalPeriod = 9;
                const rsiSignalData: { time: number; value: number }[] = [];

                const validRsiData = allRsiData.filter(d => d.value !== undefined && d.value !== null);

                let lastValidSignal: number | undefined = undefined;
                for (let i = signalPeriod - 1; i < validRsiData.length; i++) {
                  let sum = 0;
                  for (let j = 0; j < signalPeriod; j++) {
                    sum += validRsiData[i - j].value!;
                  }
                  const signalValue = sum / signalPeriod;
                  lastValidSignal = signalValue;
                  rsiSignalData.push({
                    time: validRsiData[i].time as number,
                    value: signalValue,
                  });
                }

                // ✅ 미래 영역: 마지막 유효한 값 사용
                for (let i = validRsiData.length; i < allRsiData.length; i++) {
                  rsiSignalData.push({
                    time: allRsiData[i].time as number,
                    value: lastValidSignal ?? 50,
                  });
                }

                rsiSignalSeries.setData(rsiSignalData);
                console.log(`[IndicatorChart:RSI] 신호선 데이터: ${rsiSignalData.length}개 (${indicatorSettings.rsiSignalPeriod || 9}일 이동평균, 미래 영역 포함)`);
              }
            }
            break;

          case 'stochastic':
            const stochKSeries = seriesRefs.current.get('stochasticK');
            const stochDSeries = seriesRefs.current.get('stochasticD');
            const stochOverboughtAreaSeries = seriesRefs.current.get('stochasticOverboughtArea');
            const stochOversoldAreaSeries = seriesRefs.current.get('stochasticOversoldArea');

            if (stochKSeries) {
              let lastValidStochK: number | undefined = undefined;
              const stochKData = formattedData.map(d => {
                const stochKValue = d.stochasticK !== undefined ? d.stochasticK : (d as any).stochK;
                if (stochKValue !== undefined && stochKValue !== null) {
                  lastValidStochK = stochKValue;
                }
                return {
                  time: d.timeSeconds,
                  value: stochKValue !== undefined ? stochKValue : (lastValidStochK ?? 50),
                };
              });
              stochKSeries.setData(stochKData);
              console.log(`[IndicatorChart:Stochastic] %K 데이터: ${stochKData.length}개 (워밍업 데이터로 정확하게 계산됨, 미래 영역 포함)`);
              
              // ✅ Stochastic 과열 영역 데이터 - BaselineSeries는 %K 값 그대로 사용
              if (stochOverboughtAreaSeries && indicatorSettings.stochasticOverboughtEnabled) {
                const stochOverboughtLevel = indicatorSettings.stochasticOverbought || 80;
                stochOverboughtAreaSeries.setData(stochKData);
                console.log(`[IndicatorChart:Stochastic] 과열 영역 데이터 설정 완료 (기준: ${stochOverboughtLevel}, ${stochKData.length}개)`);
              }

              // ✅ Stochastic 침체 영역 데이터 - BaselineSeries는 %K 값 그대로 사용
              if (stochOversoldAreaSeries && indicatorSettings.stochasticOversoldEnabled) {
                const stochOversoldLevel = indicatorSettings.stochasticOversold || 20;
                stochOversoldAreaSeries.setData(stochKData);
                console.log(`[IndicatorChart:Stochastic] 침체 영역 데이터 설정 완료 (기준: ${stochOversoldLevel}, ${stochKData.length}개)`);
              }
            }

            if (stochDSeries) {
              let lastValidStochD: number | undefined = undefined;
              const stochDData = formattedData.map(d => {
                const stochDValue = d.stochasticD !== undefined ? d.stochasticD : (d as any).stochD;
                if (stochDValue !== undefined && stochDValue !== null) {
                  lastValidStochD = stochDValue;
                }
                return {
                  time: d.timeSeconds,
                  value: stochDValue !== undefined ? stochDValue : (lastValidStochD ?? 50),
                };
              });
              stochDSeries.setData(stochDData);
              console.log(`[IndicatorChart:Stochastic] %D 데이터: ${stochDData.length}개 (워밍업 데이터로 정확하게 계산됨, 미래 영역 포함)`);
            }
            break;

          case 'momentum':
            const momentumSeries = seriesRefs.current.get('momentum');
            if (momentumSeries) {
              let lastValidMomentum: number | undefined = undefined;
              const momentumData = formattedData.map(d => {
                if (d.momentum !== undefined && d.momentum !== null) {
                  lastValidMomentum = d.momentum;
                }
                return {
                  time: d.timeSeconds,
                  value: d.momentum !== undefined ? d.momentum : (lastValidMomentum ?? 0),
                };
              });
              momentumSeries.setData(momentumData);
              console.log(`[IndicatorChart:Momentum] 데이터: ${momentumData.length}개 (미래 영역 포함)`);
            } else {
              console.warn('[IndicatorChart:Momentum] 시리즈를 찾을 수 없음');
            }
            break;

          case 'cci':
            const cciSeries = seriesRefs.current.get('cci');
            const cciSignalSeries = seriesRefs.current.get('cciSignal');
            const cciOverboughtAreaSeries = seriesRefs.current.get('cciOverboughtArea');
            const cciOversoldAreaSeries = seriesRefs.current.get('cciOversoldArea');

            if (cciSeries) {
              // ✅ 미래 영역 대응: undefined 값을 마지막 유효한 값으로 대체
              let lastValidCci: number | undefined = undefined;
              const allCciData = formattedData.map(d => {
                const cciValue = d.cci !== undefined ? d.cci : (d as any).cci13;
                if (cciValue !== undefined && cciValue !== null) {
                  lastValidCci = cciValue;
                }
                return {
                  time: d.timeSeconds,
                  value: cciValue !== undefined ? cciValue : (lastValidCci ?? 0),
                };
              });

              cciSeries.setData(allCciData);
              console.log(`[IndicatorChart:CCI] 데이터: ${allCciData.length}개 (미래 영역 포함, 워밍업 데이터로 정확하게 계산됨)`);
              
              // ✅ CCI 과열 영역 데이터 - BaselineSeries는 CCI 값 그대로 사용
              if (cciOverboughtAreaSeries && indicatorSettings.cciOverboughtEnabled) {
                const cciOverboughtLevel = indicatorSettings.cciOverbought || 100;
                cciOverboughtAreaSeries.setData(allCciData);
                console.log(`[IndicatorChart:CCI] 과열 영역 데이터 설정 완료 (기준: ${cciOverboughtLevel}, ${allCciData.length}개)`);
              }

              // ✅ CCI 침체 영역 데이터 - BaselineSeries는 CCI 값 그대로 사용
              if (cciOversoldAreaSeries && indicatorSettings.cciOversoldEnabled) {
                const cciOversoldLevel = indicatorSettings.cciOversold || -100;
                cciOversoldAreaSeries.setData(allCciData);
                console.log(`[IndicatorChart:CCI] 침체 영역 데이터 설정 완료 (기준: ${cciOversoldLevel}, ${allCciData.length}개)`);
              }

              // CCI 신호선 계산 (CCI의 10일 단순이동평균)
              if (cciSignalSeries && allCciData.length > 0) {
                const signalPeriod = 10;
                const cciSignalData: Array<{ time: Time; value: number }> = [];

                const validCciData = allCciData.filter(d => d.value !== undefined && d.value !== null);

                let lastValidSignal: number | undefined = undefined;
                for (let i = signalPeriod - 1; i < validCciData.length; i++) {
                  let sum = 0;
                  for (let j = 0; j < signalPeriod; j++) {
                    sum += validCciData[i - j].value;
                  }
                  const signalValue = sum / signalPeriod;
                  lastValidSignal = signalValue;
                  cciSignalData.push({
                    time: validCciData[i].time,
                    value: signalValue,
                  });
                }

                // ✅ 미래 영역: 마지막 유효한 값 사용
                for (let i = validCciData.length; i < allCciData.length; i++) {
                  cciSignalData.push({
                    time: allCciData[i].time,
                    value: lastValidSignal ?? 0,
                  });
                }

                cciSignalSeries.setData(cciSignalData);
                console.log(`[IndicatorChart:CCI] 신호선 데이터: ${cciSignalData.length}개 (${indicatorSettings.cciSignalPeriod || 10}일 이동평균, 미래 영역 포함)`);
              }
            } else {
              console.warn('[IndicatorChart:CCI] 시리즈를 찾을 수 없음');
            }
            break;

          case 'williamsR':
            const williamsRSeries = seriesRefs.current.get('williamsR');
            if (williamsRSeries) {
              let lastValidWilliamsR: number | undefined = undefined;
              const williamsRData = formattedData.map(d => {
                if (d.williamsR !== undefined && d.williamsR !== null) {
                  lastValidWilliamsR = d.williamsR;
                }
                return {
                  time: d.timeSeconds,
                  value: d.williamsR !== undefined ? d.williamsR : (lastValidWilliamsR ?? -50),
                };
              });
              williamsRSeries.setData(williamsRData);
              console.log(`[IndicatorChart:WilliamsR] 데이터: ${williamsRData.length}개 (미래 영역 포함)`);
            } else {
              console.warn('[IndicatorChart:WilliamsR] 시리즈를 찾을 수 없음');
            }
            break;

          case 'dmi':
          case 'adx':
            const adxSeries = seriesRefs.current.get('adx');
            const plusDISeries = seriesRefs.current.get('plusDI');
            const minusDISeries = seriesRefs.current.get('minusDI');

            if (adxSeries) {
              let lastValidAdx: number | undefined = undefined;
              const adxData = formattedData.map(d => {
                if (d.adx !== undefined && d.adx !== null) {
                  lastValidAdx = d.adx;
                }
                return {
                  time: d.timeSeconds,
                  value: d.adx !== undefined ? d.adx : (lastValidAdx ?? 25),
                };
              });
              adxSeries.setData(adxData);
              console.log(`[IndicatorChart:DMI] ADX 데이터: ${adxData.length}개 (워밍업 데이터로 정확하게 계산됨, 미래 영역 포함)`);
            }

            if (plusDISeries) {
              let lastValidPlusDI: number | undefined = undefined;
              const plusDIData = formattedData.map(d => {
                if (d.plusDI !== undefined && d.plusDI !== null) {
                  lastValidPlusDI = d.plusDI;
                }
                return {
                  time: d.timeSeconds,
                  value: d.plusDI !== undefined ? d.plusDI : (lastValidPlusDI ?? 25),
                };
              });
              plusDISeries.setData(plusDIData);
              console.log(`[IndicatorChart:DMI] +DI 데이터: ${plusDIData.length}개 (워밍업 데이터로 정확하게 계산됨, 미래 영역 포함)`);
            }

            if (minusDISeries) {
              let lastValidMinusDI: number | undefined = undefined;
              const minusDIData = formattedData.map(d => {
                if (d.minusDI !== undefined && d.minusDI !== null) {
                  lastValidMinusDI = d.minusDI;
                }
                return {
                  time: d.timeSeconds,
                  value: d.minusDI !== undefined ? d.minusDI : (lastValidMinusDI ?? 25),
                };
              });
              minusDISeries.setData(minusDIData);
              console.log(`[IndicatorChart:DMI] -DI 데이터: ${minusDIData.length}개 (워밍업 데이터로 정확하게 계산됨, 미래 영역 포함)`);
            }
            break;

          case 'obv':
            const obvSeries = seriesRefs.current.get('obv');
            const obvSignalSeries = seriesRefs.current.get('obvSignal');

            if (obvSeries) {
              // ✅ 미래 영역 대응: undefined 값을 마지막 유효한 값으로 대체
              let lastValidObv: number | undefined = undefined;
              const allObvData = formattedData.map(d => {
                if (d.obv !== undefined && d.obv !== null) {
                  lastValidObv = d.obv;
                }
                return {
                  time: d.timeSeconds,
                  value: d.obv !== undefined ? d.obv : (lastValidObv ?? 0),
                };
              });
              obvSeries.setData(allObvData);
              console.log(`[IndicatorChart:OBV] 데이터: ${allObvData.length}개 (미래 영역 포함)`);

              // OBV 신호선 계산 (OBV의 이동평균)
              if (obvSignalSeries && allObvData.length > 0) {
                const signalPeriod = indicatorSettings.obvSignalPeriod || 9;
                const obvSignalData: Array<{ time: Time; value: number }> = [];

                const validObvData = allObvData.filter(d => d.value !== undefined && d.value !== null);

                let lastValidSignal: number | undefined = undefined;
                for (let i = signalPeriod - 1; i < validObvData.length; i++) {
                  let sum = 0;
                  for (let j = 0; j < signalPeriod; j++) {
                    sum += validObvData[i - j].value!;
                  }
                  const signalValue = sum / signalPeriod;
                  lastValidSignal = signalValue;
                  obvSignalData.push({
                    time: validObvData[i].time,
                    value: signalValue,
                  });
                }

                // ✅ 미래 영역: 마지막 유효한 값 사용
                for (let i = validObvData.length; i < allObvData.length; i++) {
                  obvSignalData.push({
                    time: allObvData[i].time,
                    value: lastValidSignal ?? 0,
                  });
                }

                obvSignalSeries.setData(obvSignalData);
                console.log(`[IndicatorChart:OBV] 신호선 데이터: ${obvSignalData.length}개 (${indicatorSettings.obvSignalPeriod || 9}일 이동평균, 미래 영역 포함)`);
              }
            } else {
              console.warn('[IndicatorChart:OBV] 시리즈를 찾을 수 없음');
            }
            break;

          case 'trix':
            const trixSeries = seriesRefs.current.get('trix');
            const trixSignalSeries = seriesRefs.current.get('trixSignal');

            if (trixSeries) {
              let lastValidTrix: number | undefined = undefined;
              const trixData = formattedData.map(d => {
                if (d.trix !== undefined && d.trix !== null) {
                  lastValidTrix = d.trix;
                }
                return {
                  time: d.timeSeconds,
                  value: d.trix !== undefined ? d.trix : (lastValidTrix ?? 0),
                };
              });
              trixSeries.setData(trixData);
              console.log(`[IndicatorChart:TRIX] TRIX 데이터: ${trixData.length}개 (미래 영역 포함)`);
            }

            if (trixSignalSeries) {
              let lastValidTrixSignal: number | undefined = undefined;
              const trixSignalData = formattedData.map(d => {
                if (d.trixSignal !== undefined && d.trixSignal !== null) {
                  lastValidTrixSignal = d.trixSignal;
                }
                return {
                  time: d.timeSeconds,
                  value: d.trixSignal !== undefined ? d.trixSignal : (lastValidTrixSignal ?? 0),
                };
              });
              trixSignalSeries.setData(trixSignalData);
              console.log(`[IndicatorChart:TRIX] Signal 데이터: ${trixSignalData.length}개 (미래 영역 포함)`);
            }
            break;

          case 'newPsychological':
            const newPsySeries = seriesRefs.current.get('newPsy');
            if (newPsySeries) {
              let lastValidNewPsy: number | undefined = undefined;
              const newPsyData = formattedData.map(d => {
                if (d.newPsy !== undefined && d.newPsy !== null) {
                  lastValidNewPsy = d.newPsy;
                }
                return {
                  time: d.timeSeconds,
                  value: d.newPsy !== undefined ? d.newPsy : (lastValidNewPsy ?? 50),
                };
              });
              newPsySeries.setData(newPsyData);
              console.log(`[IndicatorChart:신심리도] 데이터: ${newPsyData.length}개 (미래 영역 포함)`);
            } else {
              console.warn(`[IndicatorChart:신심리도] ⚠️ 시리즈가 없음 - 데이터 적용 불가`);
            }
            break;

          case 'investPsychological':
            const investPsySeries = seriesRefs.current.get('investPsy');
            if (investPsySeries) {
              let lastValidInvestPsy: number | undefined = undefined;
              const investPsyData = formattedData.map(d => {
                if (d.investPsy !== undefined && d.investPsy !== null) {
                  lastValidInvestPsy = d.investPsy;
                }
                return {
                  time: d.timeSeconds,
                  value: d.investPsy !== undefined ? d.investPsy : (lastValidInvestPsy ?? 50),
                };
              });
              investPsySeries.setData(investPsyData);
              console.log(`[IndicatorChart:투자심리도] 데이터: ${investPsyData.length}개 (미래 영역 포함)`);
            }
            break;

          case 'disparity':
            // 이격도 - 4개의 선 데이터 설정
            const disparityUltraShortSeries = seriesRefs.current.get('disparityUltraShort');
            const disparityShortSeries = seriesRefs.current.get('disparityShort');
            const disparityMidSeries = seriesRefs.current.get('disparityMid');
            const disparityLongSeries = seriesRefs.current.get('disparityLong');

            if (disparityUltraShortSeries) {
              let lastValidUltraShort: number | undefined = undefined;
              const ultraShortData = formattedData.map(d => {
                if (d.disparityUltraShort !== undefined && d.disparityUltraShort !== null) {
                  lastValidUltraShort = d.disparityUltraShort;
                }
                return {
                  time: d.timeSeconds,
                  value: d.disparityUltraShort !== undefined ? d.disparityUltraShort : (lastValidUltraShort ?? 100),
                };
              });
              disparityUltraShortSeries.setData(ultraShortData);
              console.log(`[IndicatorChart:이격도(초단기)] 데이터: ${ultraShortData.length}개`);
            }

            if (disparityShortSeries) {
              let lastValidShort: number | undefined = undefined;
              const shortData = formattedData.map(d => {
                if (d.disparityShort !== undefined && d.disparityShort !== null) {
                  lastValidShort = d.disparityShort;
                }
                return {
                  time: d.timeSeconds,
                  value: d.disparityShort !== undefined ? d.disparityShort : (lastValidShort ?? 100),
                };
              });
              disparityShortSeries.setData(shortData);
              console.log(`[IndicatorChart:이격도(단기)] 데이터: ${shortData.length}개`);
            }

            if (disparityMidSeries) {
              let lastValidMid: number | undefined = undefined;
              const midData = formattedData.map(d => {
                if (d.disparityMid !== undefined && d.disparityMid !== null) {
                  lastValidMid = d.disparityMid;
                }
                return {
                  time: d.timeSeconds,
                  value: d.disparityMid !== undefined ? d.disparityMid : (lastValidMid ?? 100),
                };
              });
              disparityMidSeries.setData(midData);
              console.log(`[IndicatorChart:이격도(중기)] 데이터: ${midData.length}개`);
            }

            if (disparityLongSeries) {
              let lastValidLong: number | undefined = undefined;
              const longData = formattedData.map(d => {
                if (d.disparityLong !== undefined && d.disparityLong !== null) {
                  lastValidLong = d.disparityLong;
                }
                return {
                  time: d.timeSeconds,
                  value: d.disparityLong !== undefined ? d.disparityLong : (lastValidLong ?? 100),
                };
              });
              disparityLongSeries.setData(longData);
              console.log(`[IndicatorChart:이격도(장기)] 데이터: ${longData.length}개`);
            }
            break;

          case 'bwi':
            const bwiSeries = seriesRefs.current.get('bwi');
            if (bwiSeries) {
              let lastValidBwi: number | undefined = undefined;
              const bwiData = formattedData.map(d => {
                if (d.bwi !== undefined && d.bwi !== null) {
                  lastValidBwi = d.bwi;
                }
                return {
                  time: d.timeSeconds,
                  value: d.bwi !== undefined ? d.bwi : (lastValidBwi ?? 50),
                };
              });
              bwiSeries.setData(bwiData);
              console.log(`[IndicatorChart:BWI] 데이터: ${bwiData.length}개 (미래 영역 포함)`);
            }
            break;

          case 'volumeOsc':
            const volumeOscSeries = seriesRefs.current.get('volumeOsc');
            if (volumeOscSeries) {
              let lastValidVolumeOsc: number | undefined = undefined;
              const volumeOscData = formattedData.map(d => {
                if (d.volumeOsc !== undefined && d.volumeOsc !== null) {
                  lastValidVolumeOsc = d.volumeOsc;
                }
                return {
                  time: d.timeSeconds,
                  value: d.volumeOsc !== undefined ? d.volumeOsc : (lastValidVolumeOsc ?? 0),
                };
              });
              volumeOscSeries.setData(volumeOscData);
              console.log(`[IndicatorChart:VolumeOSC] 데이터: ${volumeOscData.length}개`);
            }
            break;

          case 'vr':
            const vrSeries = seriesRefs.current.get('vr');
            const vr2Series = seriesRefs.current.get('vr2');

            // ✅ VR 데이터 (DMI plusDI 패턴과 동일)
            if (vrSeries) {
              const vrData = formattedData
                .filter(d => d.vr !== undefined && d.vr !== null)
                .map(d => ({
                  time: d.timeSeconds,
                  value: d.vr!,
                }));
              vrSeries.setData(vrData);
              console.log(`[IndicatorChart:VR] VR 데이터: ${vrData.length}개`);
            }

            // ✅ VR2 데이터 (DMI minusDI 패턴과 동일)
            if (vr2Series) {
              const vr2Data = formattedData
                .filter(d => d.vr2 !== undefined && d.vr2 !== null)
                .map(d => ({
                  time: d.timeSeconds,
                  value: d.vr2!,
                }));
              vr2Series.setData(vr2Data);
              console.log(`[IndicatorChart:VR] VR2 데이터: ${vr2Data.length}개`);
              if (vr2Data.length > 0) {
                console.log(`🔍 [IndicatorChart:VR] VR2 샘플 (최근 5개):`, vr2Data.slice(-5).map(d => d.value.toFixed(2)));
              }
            }
            break;
        }

        // ✅ 데이터 업데이트 직후 X축 범위 즉시 적용 (부모로부터 받은 initialVisibleRange만 사용)
        // ⚠️ lastVisibleRangeRef 사용 금지 - 부모가 전달하는 initialVisibleRange가 항상 최신 범위임
        if (initialVisibleRange) {
          const { from, to } = initialVisibleRange;

          // 적용 함수
          const applyRange = () => {
            if (chartRef.current) {
              try {
                chartRef.current.timeScale().setVisibleRange({
                  from: from as Time,
                  to: to as Time,
                });
              } catch (e) {
                // 에러 무시
              }
            }
          };

          // 즉시 적용
          applyRange();

          // 30ms, 80ms, 150ms, 300ms, 600ms 후 재적용 (보조지표 데이터 로딩 후 자동 fitContent 방지)
          setTimeout(applyRange, 30);
          setTimeout(applyRange, 80);
          setTimeout(applyRange, 150);
          setTimeout(applyRange, 300);
          setTimeout(() => {
            applyRange();
            console.log(`[IndicatorChart:${type}] X축 범위 최종 적용 완료: ${new Date(from * 1000).toISOString().slice(11, 19)} ~ ${new Date(to * 1000).toISOString().slice(11, 19)}`);
          }, 600);
        }

        // ✅ 데이터 업데이트 후 Y축 완전 리셋 (autoScale 재활성화)
        setTimeout(() => {
          if (chartRef.current) {
            try {
              // 1. 오른쪽 Y축 스케일 리셋
              const priceScale = chartRef.current.priceScale('right');
              priceScale.applyOptions({
                autoScale: true,
              });

              // 2. 모든 시리즈의 autoscaleInfoProvider 제거
              seriesRefs.current.forEach((series) => {
                series.applyOptions({
                  autoscaleInfoProvider: undefined,
                });
              });

              console.log(`[IndicatorChart:${type}] 데이터 업데이트 후 Y축 리셋 완료`);
            } catch (e) {
              console.warn(`[IndicatorChart:${type}] Y축 리셋 실패:`, e);
            }
          }

          // ✅ 데이터 업데이트 완료 - 다중 시리즈를 위한 충분한 지연
          // requestAnimationFrame으로 브라우저 렌더링 사이클에 맞춰 대기
          requestAnimationFrame(() => {
            requestAnimationFrame(() => {
          setTimeout(() => {
                console.log(`✅ [IndicatorChart:${type}] 데이터 업데이트 완료 - 시리즈 준비됨`);
                
                // ✅ 데이터 업데이트 완료 후 마지막 호버 시간 재적용
                if (lastExternalHoverTimeRef.current) {
                  const hoverTime = lastExternalHoverTimeRef.current;
                  const timeStr = typeof hoverTime === 'number' ? new Date(hoverTime * 1000).toISOString().slice(11, 19) : new Date(hoverTime as string).toISOString().slice(11, 19);
                  console.log(`🔄 [IndicatorChart:${type}] 크로스헤어 재적용: ${timeStr}`);
                  
                  // 재시도 메커니즘과 함께 크로스헤어 적용
                  applyCrosshairPosition(hoverTime, 0);
                }
              }, 100); // 다중 시리즈 완료 대기
            });
          });
          }, 50);

      // ✅ 데이터 적용 완료 후 ref 업데이트 및 플래그 해제
      setTimeout(() => {
        // ref는 항상 업데이트 (다음 호출을 위해)
        prevDataUpdateSettingsRef.current = currentDataUpdateSettings;
        prevDataUpdateColorsRef.current = currentDataUpdateColors;
        prevDataLengthRef.current = currentDataLength;
        prevDataFirstTimeRef.current = currentDataFirstTime;
        prevDataLastTimeRef.current = currentDataLastTime;
        isDataUpdatingRef.current = false;
        console.log(`[IndicatorChart:${type}] 데이터 업데이트 완전히 종료 (forceApply: ${forceApply})`);
      }, 200); // 모든 비동기 작업 완료 대기

      } catch (error) {
        console.error(`[IndicatorChart:${type}] 데이터 설정 오류:`, error);
        // 에러 발생 시에도 ref 업데이트 및 플래그 해제 (무한 재시도 방지)
        prevDataUpdateSettingsRef.current = currentDataUpdateSettings;
        prevDataUpdateColorsRef.current = currentDataUpdateColors;
        prevDataLengthRef.current = currentDataLength;
        prevDataFirstTimeRef.current = currentDataFirstTime;
        prevDataLastTimeRef.current = currentDataLastTime;
        isDataUpdatingRef.current = false;
      }
    }, [type, initialVisibleRange, indicatorSettings, colorSettings, chartColors]); // ✅ data 제거 (dataRef.current 사용)

    // ✅ data 길이나 시간 범위 변경 시에만 applyChartData 호출
    useEffect(() => {
      if (!chartRef.current || data.length === 0) return;
      
      // ✅ 시리즈 재생성 중이면 스킵 (시리즈 재생성 useEffect에서 applyChartData를 직접 호출함)
      if (isSeriesRecreatingRef.current) {
        console.log(`[IndicatorChart:${type}] 시리즈 재생성 중 - 데이터 업데이트 스킵 (재생성 후 자동 적용됨)`);
        return;
      }
      
      const currentDataLength = data.length;
      // ✅ applyChartData·formattedData는 item.time 기준 — 일목 미래 바는 timestamp 없이 time만 있음 (timestamp만 보면 매 렌더 불일치 → applyChartData 스킵/레이스)
      const getPointTimeKey = (p: IndicatorDataPoint | undefined) =>
        (p?.time ?? p?.timestamp ?? p?.date ?? '') as string;
      const currentDataFirstTime = getPointTimeKey(data[0]);
      const currentDataLastTime = getPointTimeKey(data[data.length - 1]);
      
      // 데이터 변경 감지 (길이나 시간 범위 또는 settingsVersion 변경)
      const lengthChanged = currentDataLength !== prevDataLengthRef.current;
      const firstTimeChanged = currentDataFirstTime !== prevDataFirstTimeRef.current;
      const lastTimeChanged = currentDataLastTime !== prevDataLastTimeRef.current;
      const versionChanged = propSettingsVersion !== prevDataSettingsVersionRef.current;
      
      const dataChanged = lengthChanged || firstTimeChanged || lastTimeChanged || versionChanged;
      
      if (dataChanged) {
        console.log(`🔄 [IndicatorChart:${type}] 데이터 변경 감지 - applyChartData 호출`, {
          길이변경: lengthChanged ? `${prevDataLengthRef.current} → ${currentDataLength}` : 'X',
          시작시간변경: firstTimeChanged ? 'O' : 'X',
          종료시간변경: lastTimeChanged ? 'O' : 'X',
          버전변경: versionChanged ? `${prevDataSettingsVersionRef.current} → ${propSettingsVersion}` : 'X'
        });
        applyChartData(false);
        prevDataSettingsVersionRef.current = propSettingsVersion;
      }
      // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [data, type, propSettingsVersion]);

    // ✅ 초기 visible range 설정 (부모로부터 받은 범위 적용)
    // ⚠️ lastVisibleRangeRef 저장 금지 - 부모가 관리하는 initialVisibleRange만 사용
    useEffect(() => {
      if (!chartRef.current || !initialVisibleRange) return;

      // ✅ 시리즈 재생성 중이면 범위 설정 스킵 (데이터 적용 후에 자동으로 범위 설정됨)
      if (isSeriesRecreatingRef.current) {
        console.log(`[IndicatorChart:${type}] 시리즈 재생성 중 - 범위 설정 스킵`);
        return;
      }

      // ⚠️ 시리즈가 준비되지 않았으면 짧은 지연 후 재시도
      if (seriesRefs.current.size === 0) {
        const timer = setTimeout(() => {
          if (!chartRef.current || !initialVisibleRange) return;
          // 재시도 시에도 시리즈 재생성 중이면 스킵
          if (isSeriesRecreatingRef.current) return;
          
          const { from, to } = initialVisibleRange;
          try {
            chartRef.current.timeScale().setVisibleRange({
              from: from as Time,
              to: to as Time,
            });
            console.log(`✅ [IndicatorChart:${type}] 범위 설정 (지연): ${new Date(from * 1000).toISOString().slice(11, 19)} ~ ${new Date(to * 1000).toISOString().slice(11, 19)}`);
          } catch (e) {
            console.warn(`[IndicatorChart:${type}] 범위 설정 실패 (지연):`, e);
          }
        }, 100);
        return () => clearTimeout(timer);
      }

      const { from, to } = initialVisibleRange;

      try {
        chartRef.current.timeScale().setVisibleRange({
          from: from as Time,
          to: to as Time,
        });
        console.log(`✅ [IndicatorChart:${type}] 범위 설정: ${new Date(from * 1000).toISOString().slice(11, 19)} ~ ${new Date(to * 1000).toISOString().slice(11, 19)}`);
      } catch (e) {
        console.warn(`[IndicatorChart:${type}] 범위 설정 실패:`, e);
      }
    }, [type, initialVisibleRange]); // ✅ initialVisibleRange 변경 시마다 실행

    // ✅ 외부에서 제어하는 시간 범위 동기화 (모든 차트 간 X축 동기화)
    useEffect(() => {
      if (!chartRef.current || !sharedTimeRange) return;

      // 시리즈 재생성 중이면 스킵
      if (isSeriesRecreatingRef.current) {
        return;
      }

      const { from, to } = sharedTimeRange;

      try {
        chartRef.current.timeScale().setVisibleRange({
          from: from as Time,
          to: to as Time,
        });
        console.log(`✅ [IndicatorChart:${type}] 외부 시간 범위 동기화: ${new Date(from * 1000).toISOString().slice(11, 19)} ~ ${new Date(to * 1000).toISOString().slice(11, 19)}`);
      } catch (e) {
        console.warn(`[IndicatorChart:${type}] 외부 시간 범위 동기화 실패:`, e);
      }
    }, [type, sharedTimeRange]); // ✅ sharedTimeRange 변경 시마다 실행


    // ✅ 사용하지 않는 state 제거됨 - 재시도 메커니즘으로 대체

    // 외부 호버 시간 동기화 (재시도 메커니즘 포함)
    useEffect(() => {
      if (!externalHoverTime) {
        lastExternalHoverTimeRef.current = null;
        return;
      }

      // 마지막 호버 시간 저장
      lastExternalHoverTimeRef.current = externalHoverTime;

      // 이미 설정 중이면 무시 (떨림 방지)
      if (isSettingExternalCrosshairRef.current) {
        return;
      }

        isSettingExternalCrosshairRef.current = true;

      // 재시도 메커니즘과 함께 크로스헤어 적용
      applyCrosshairPosition(externalHoverTime, 0);

      // 빠른 반응을 위해 짧은 대기 시간 (1프레임)
        setTimeout(() => {
          isSettingExternalCrosshairRef.current = false;
        }, 16);

      // Cleanup: 컴포넌트 언마운트 시 재시도 타이머 정리
      return () => {
        if (retryTimeoutRef.current) {
          clearTimeout(retryTimeoutRef.current);
          retryTimeoutRef.current = null;
        }
      };
    }, [externalHoverTime, type, applyCrosshairPosition]);

    // 타이틀 표시 텍스트
    const getTitleText = () => {
      if (title) return title;

      switch (type) {
        case 'volume': return '거래량';
        case 'macd': return 'MACD';
        case 'rsi': return 'RSI';
        case 'stochastic': return 'Stochastic';
        case 'cci': return 'CCI';
        case 'adx': return 'ADX';
        case 'obv': return 'OBV';
        default: return '';
      }
    };

    return (
      <Box sx={{ position: 'relative', width: '100%', mb: 0, p: 0 }}>
        {/* 타이틀 - hideTitle이 false일 때만 표시 (오른쪽 상단) */}
        {!hideTitle && getTitleText() && (
          <Box
            sx={{
              position: 'absolute',
              top: 4,
              right: 110,
              zIndex: 10,
              bgcolor: themeMode === 'dark' ? chartColors.background : 'rgba(245, 247, 250, 0.98)',
              px: 1.5,
              py: 0.5,
              borderRadius: 1,
              border: themeMode === 'dark' ? 'none' : '1px solid rgba(0, 0, 0, 0.15)',
              boxShadow: themeMode === 'dark' ? 'none' : '0 2px 6px rgba(0, 0, 0, 0.12)',
            }}
          >
            <Typography
              variant="caption"
              sx={{
                fontSize: '12px',
                color: themeMode === 'dark' ? '#e0e0e0' : '#1a1a1a',
                fontWeight: 700,
                letterSpacing: 0.5,
              }}
            >
              {getTitleText()}
            </Typography>
          </Box>
        )}

        {/* 차트 컨테이너 - Y축 너비를 고정하여 모든 차트의 X축 정렬 */}
        <div
          ref={chartContainerRef}
          style={{
            width: '100%',
            height: `${height}px`,
            position: 'relative',
            boxSizing: 'border-box',  // ✅ border를 width 안에 포함 (메인 차트 및 서브차트와 완전히 일치)
          }}
          className="indicator-chart-container"
        />
      </Box>
    );
  }
);

IndicatorChart.displayName = 'IndicatorChart';

// React.memo 제거: Context(colorSettings, indicatorSettings)를 사용하므로 
// Context 변경 시 자동으로 리렌더링되어야 함
// React.memo를 사용하면 Context 변경을 감지하지 못할 수 있음
export default IndicatorChart;
