import type { OHLCVBar } from '../types';
import type { Bar } from 'oakscriptjs';
import {
  createDefaultSmaParams,
  createDefaultSmaPlots,
  calculateSmaMulti,
  normalizeSmaConfig,
} from './smaConfig';
import { normalizeIchimokuConfig, mergeIchimokuPlots } from './ichimokuConfig';
import {
  normalizePsychologicalParams,
  resolvePsychologicalIndicatorType,
} from './psychologicalConfig';
import {
  normalizeBollingerParams,
  resolveBollingerBars,
  resolveBbBandBackground,
  mergeBbPlots,
} from './bollingerConfig';
import type { Timeframe } from '../types';

export type IndicatorCategory =
  | 'Moving Averages'
  | 'Oscillators'
  | 'Momentum'
  | 'Trend'
  | 'Volatility'
  | 'Channels & Bands'
  | 'Volume'
  | 'Candlestick Patterns';

export interface PlotDef {
  id: string;
  title: string;
  color: string;
  type: 'line' | 'histogram' | 'area' | 'scatter';
  lineWidth?: number;
  /** 라인 시리즈 선 유형 (기본 solid) */
  lineStyle?: HLineStyle;
}

export type HLineStyle = 'solid' | 'dashed' | 'dotted';

export interface HLineDef {
  price:      number;
  color:      string;
  /** 표시 이름: '과열선' | '중앙선' | '침체선' | '기준선' 등 */
  label?:     string;
  visible?:   boolean;    // false 이면 차트에서 숨김 (기본 true)
  lineStyle?: HLineStyle; // 선 유형 (기본 'dashed')
  lineWidth?: number;     // 선 굵기 1~4 (기본 1)
}

/**
 * price 값과 같은 hlines 배열 내 다른 값들을 기반으로
 * 기본 레이블을 자동 결정합니다.
 */
/** 가격 값으로 hline 역할 레이블을 결정 — 용어 통일: 과열선/중앙선/침체선 */
export function getHLineLabel(price: number, allPrices: number[]): string {
  if (price === 0) return '중앙선';
  const uniq = [...new Set(allPrices)].sort((a, b) => b - a);
  if (uniq.length === 1) return '중앙선';
  if (price === uniq[0]) return '과열선';
  if (price === uniq[uniq.length - 1]) return '침체선';
  return '중앙선';
}

function migrateHLineLabel(label: string | undefined): string | undefined {
  if (!label) return undefined;
  if (label === '과매수' || label === '상한선') return '과열선';
  if (label === '과매도' || label === '하한선') return '침체선';
  if (label === '0선' || label === '중립선' || label === '중간선' || label === '기준선') return '중앙선';
  return label;
}

/** normalizeHLines 합성 중앙선(0) 등 레거시 저장값을 registry 기본 가격으로 교정 */
function resolveMergedHLinePrice(
  roleLabel: string,
  savedPrice: number,
  defPrice: number,
): number {
  if (roleLabel === '중앙선' && savedPrice === 0 && defPrice !== 0) {
    return defPrice;
  }
  return savedPrice;
}

/** 지표별 누락 수평선 합성 기본값 */
const HL_SYNTH_DEFAULTS: Record<string, { over: number; mid: number; under: number }> = {
  ADX:         { over: 40, mid: 25, under: 20 },
  DMI:         { over: 30, mid: 20, under: 10 },
  Stochastic:  { over: 80, mid: 50, under: 20 },
  StochRSI:    { over: 80, mid: 50, under: 20 },
};

/** Stochastic Slow 기본 수평선 — 과열 80 / 중앙 50(기본 off) / 침체 20 */
export const STOCHASTIC_DEFAULT_HLINES: HLineDef[] = [
  { price: 80, color: '#EF5350', label: '과열선', lineStyle: 'dashed', lineWidth: 1 },
  { price: 50, color: '#607D8B', label: '중앙선', lineStyle: 'dashed', lineWidth: 1, visible: false },
  { price: 20, color: '#4CAF50', label: '침체선', lineStyle: 'dashed', lineWidth: 1 },
];

/** ADX 기본 수평선 — 강한 추세(과열) / 기준(중앙) / 약한 추세(침체) */
export const ADX_DEFAULT_HLINES: HLineDef[] = [
  { price: 40, color: '#EF5350', label: '과열선', lineStyle: 'dashed', lineWidth: 1 },
  { price: 25, color: '#607D8B', label: '중앙선', lineStyle: 'dashed', lineWidth: 1 },
  { price: 20, color: '#4CAF50', label: '침체선', lineStyle: 'dashed', lineWidth: 1 },
];

/** DMI 기본 수평선 (업비트: 과열 30 / 중간 20 / 침체 10) */
export const DMI_DEFAULT_HLINES: HLineDef[] = [
  { price: 30, color: '#EF5350', label: '과열선', lineStyle: 'dashed', lineWidth: 1 },
  { price: 20, color: '#607D8B', label: '중앙선', lineStyle: 'dashed', lineWidth: 1 },
  { price: 10, color: '#4CAF50', label: '침체선', lineStyle: 'dashed', lineWidth: 1 },
];

export function normalizeDmiParams(
  params: Record<string, number | string | boolean>,
): Record<string, number | string | boolean> {
  const diLength = Math.max(1, Number(params.diLength ?? params.length ?? 14));
  const adxSmoothing = Math.max(1, Number(params.adxSmoothing ?? 14));
  return { ...params, diLength, adxSmoothing };
}

/** 저장된 hlines를 registry 기본값과 역할(라벨) 기준으로 병합 */
export function mergeHlines(
  saved: HLineDef[] | undefined,
  defaults: HLineDef[] | undefined,
  indicatorType?: string,
): HLineDef[] {
  const defs = (defaults ?? []).map(h => ({ ...h }));
  if (!defs.length) return (saved ?? []).map(h => ({ ...h }));

  const savedList = (saved ?? []).map(h => {
    const label = migrateHLineLabel(h.label);
    return label ? { ...h, label } : { ...h };
  });

  const prices = savedList.map(h => h.price);
  const labelOf = (h: HLineDef) => h.label ?? getHLineLabel(h.price, prices);

  const legacyMid =
    (indicatorType === 'ADX' || indicatorType === 'DMI') && savedList.length === 1
      ? savedList[0]
      : undefined;

  return defs
    .map(def => {
      const wantLabel = def.label ?? getHLineLabel(def.price, defs.map(d => d.price));
      const match =
        savedList.find(h => labelOf(h) === wantLabel)
        ?? (legacyMid && wantLabel === '중앙선' ? legacyMid : undefined);
      if (match) {
        const price = resolveMergedHLinePrice(wantLabel, match.price, def.price);
        return { ...def, ...match, label: wantLabel, price };
      }
      return { ...def, label: wantLabel };
    })
    .concat(
      savedList.filter(h => {
        const lbl = labelOf(h);
        return !defs.some(d => (d.label ?? getHLineLabel(d.price, defs.map(x => x.price))) === lbl);
      }).map(h => ({ ...h, label: labelOf(h) })),
    )
    .sort((a, b) => b.price - a.price);
}

/** 수평선 배열을 과열·중앙·침체 3종으로 정규화 (설정 모달·차트 공통) */
export function normalizeHLines(
  hlines: HLineDef[],
  indicatorType?: string,
  defaults?: HLineDef[],
): HLineDef[] {
  let list = defaults?.length ? mergeHlines(hlines, defaults, indicatorType) : [...hlines];
  if (list.length === 0) return list;

  list = list.map(h => {
    const migrated = migrateHLineLabel(h.label);
    return migrated ? { ...h, label: migrated } : h;
  });

  const initPrices = list.map(h => h.price);
  const labeled = list.map(h => ({
    ...h,
    label: h.label ?? getHLineLabel(h.price, initPrices),
  }));
  const withFixedLabels = labeled.map(h =>
    h.label === '기준선' ? { ...h, label: '중앙선' } : h,
  );

  const prices = withFixedLabels.map(h => h.price);
  const getLabel = (h: HLineDef) => h.label ?? getHLineLabel(h.price, prices);

  const hasOver = withFixedLabels.some(h => getLabel(h) === '과열선');
  const hasCenter = withFixedLabels.some(h => getLabel(h) === '중앙선');
  const hasUnder = withFixedLabels.some(h => getLabel(h) === '침체선');

  if (hasOver && hasCenter && hasUnder) {
    return withFixedLabels.sort((a, b) => b.price - a.price);
  }

  const result = [...withFixedLabels];
  const spec = indicatorType ? HL_SYNTH_DEFAULTS[indicatorType] : undefined;
  const maxAbsPrice = prices.reduce((m, p) => Math.max(m, Math.abs(p)), 0);
  const synthUpper = maxAbsPrice > 0 ? parseFloat((maxAbsPrice * 1.5).toPrecision(3)) : 100;
  const synthLower = maxAbsPrice > 0 ? -parseFloat((maxAbsPrice * 1.5).toPrecision(3)) : -100;

  if (!hasOver) {
    result.push({
      price: spec?.over ?? synthUpper,
      color: '#EF5350',
      label: '과열선',
      visible: false,
      lineStyle: 'dashed',
      lineWidth: 1,
    });
  }
  if (!hasCenter) {
    result.push({
      price: spec?.mid ?? 0,
      color: '#607D8B',
      label: '중앙선',
      visible: false,
      lineStyle: 'dashed',
      lineWidth: 1,
    });
  }
  if (!hasUnder) {
    result.push({
      price: spec?.under ?? synthLower,
      color: '#4CAF50',
      label: '침체선',
      visible: false,
      lineStyle: 'dashed',
      lineWidth: 1,
    });
  }

  return result.sort((a, b) => b.price - a.price);
}

export interface IndicatorDef {
  type: string;
  name: string;
  koreanName: string;
  shortName: string;
  category: IndicatorCategory;
  overlay: boolean;
  defaultParams: Record<string, number | string | boolean>;
  plots: PlotDef[];
  hlines?: HLineDef[];
  returnsMarkers?: boolean;
  description: string;
}

// ─────────────────────────────────────────────────────────────────────────────
//  Full Indicator Registry (82 standard + 44 candlestick patterns)
// ─────────────────────────────────────────────────────────────────────────────
export const INDICATOR_REGISTRY: IndicatorDef[] = [

  // ── Moving Averages ──────────────────────────────────────────────────────
  { type:'SMA',    name:'Simple Moving Average',            koreanName:'단순이동평균',        shortName:'SMA',    category:'Moving Averages', overlay:true,  defaultParams:createDefaultSmaParams(),                     plots:createDefaultSmaPlots(),                                                                                          description:'단순 이동평균 (MA1~MA11)' },
  { type:'EMA',    name:'Exponential Moving Average',       koreanName:'지수이동평균',        shortName:'EMA',    category:'Moving Averages', overlay:true,  defaultParams:{length:21, src:'close'},                  plots:[{id:'plot0',title:'EMA',   color:'#FF6D00',type:'line',lineWidth:2}],                                                                                          description:'지수 이동평균' },
  { type:'WMA',    name:'Weighted Moving Average',          koreanName:'가중이동평균',        shortName:'WMA',    category:'Moving Averages', overlay:true,  defaultParams:{length:20, src:'close'},                  plots:[{id:'plot0',title:'WMA',   color:'#00BCD4',type:'line',lineWidth:2}],                                                                                          description:'가중 이동평균' },
  { type:'HMA',    name:'Hull Moving Average',              koreanName:'헐이동평균',          shortName:'HMA',    category:'Moving Averages', overlay:true,  defaultParams:{length:16, src:'close'},                  plots:[{id:'plot0',title:'HMA',   color:'#4CAF50',type:'line',lineWidth:2}],                                                                                          description:'헐 이동평균 (래그 최소)' },
  { type:'VWMA',   name:'Volume Weighted Moving Average',   koreanName:'거래량가중이동평균',  shortName:'VWMA',   category:'Moving Averages', overlay:true,  defaultParams:{length:20, src:'close'},                  plots:[{id:'plot0',title:'VWMA',  color:'#9C27B0',type:'line',lineWidth:2}],                                                                                          description:'거래량 가중 이동평균' },
  { type:'DEMA',   name:'Double Exponential MA',            koreanName:'이중지수이동평균',    shortName:'DEMA',   category:'Moving Averages', overlay:true,  defaultParams:{length:21, src:'close'},                  plots:[{id:'plot0',title:'DEMA',  color:'#F44336',type:'line',lineWidth:2}],                                                                                          description:'이중 지수 이동평균' },
  { type:'TEMA',   name:'Triple Exponential MA',            koreanName:'삼중지수이동평균',    shortName:'TEMA',   category:'Moving Averages', overlay:true,  defaultParams:{length:21, src:'close'},                  plots:[{id:'plot0',title:'TEMA',  color:'#FF9800',type:'line',lineWidth:2}],                                                                                          description:'삼중 지수 이동평균' },
  { type:'LSMA',   name:'Least Squares MA',                 koreanName:'최소제곱이동평균',    shortName:'LSMA',   category:'Moving Averages', overlay:true,  defaultParams:{length:25, src:'close', offset:0},        plots:[{id:'plot0',title:'LSMA',  color:'#00E5FF',type:'line',lineWidth:2}],                                                                                          description:'최소제곱 이동평균' },
  { type:'ALMA',   name:'Arnaud Legoux MA',                 koreanName:'아르노-레구이동평균', shortName:'ALMA',   category:'Moving Averages', overlay:true,  defaultParams:{length:21, offset:0.85, sigma:6},         plots:[{id:'plot0',title:'ALMA',  color:'#E040FB',type:'line',lineWidth:2}],                                                                                          description:'아르노 레구 이동평균' },
  { type:'RMA',    name:'Smoothed MA (SMMA/RMA)',           koreanName:'윌더스무딩이동평균',  shortName:'RMA',    category:'Moving Averages', overlay:true,  defaultParams:{length:14, src:'close'},                  plots:[{id:'plot0',title:'RMA',   color:'#8BC34A',type:'line',lineWidth:2}],                                                                                          description:'와일더 스무딩 이동평균' },
  { type:'SMMA',   name:'Smoothed Moving Average',          koreanName:'스무딩이동평균',      shortName:'SMMA',   category:'Moving Averages', overlay:true,  defaultParams:{length:14, src:'close'},                  plots:[{id:'plot0',title:'SMMA',  color:'#CDDC39',type:'line',lineWidth:2}],                                                                                          description:'스무딩 이동평균' },
  { type:'McGinleyDynamic', name:'McGinley Dynamic',        koreanName:'맥긴리다이나믹',      shortName:'MGD',    category:'Moving Averages', overlay:true,  defaultParams:{length:14},                               plots:[{id:'plot0',title:'MGD',   color:'#FF5722',type:'line',lineWidth:2}],                                                                                          description:'맥긴리 다이나믹 MA' },
  { type:'Median', name:'Median Price',                     koreanName:'중간값이동평균',      shortName:'Med',    category:'Moving Averages', overlay:true,  defaultParams:{length:14},                               plots:[{id:'plot0',title:'Median',color:'#607D8B',type:'line',lineWidth:1}],                                                                                          description:'중간값 (Median) 이동평균' },
  { type:'TWAP',   name:'Time Weighted Average Price',      koreanName:'시간가중평균가격',    shortName:'TWAP',   category:'Moving Averages', overlay:true,  defaultParams:{},                                        plots:[{id:'plot0',title:'TWAP',  color:'#795548',type:'line',lineWidth:2}],                                                                                          description:'시간 가중 평균 가격' },
  { type:'MACross',name:'MA Cross',                         koreanName:'이동평균교차',        shortName:'MACross',category:'Moving Averages', overlay:true,  defaultParams:{fastLength:9, slowLength:21, src:'close'},plots:[{id:'plot0',title:'Fast',  color:'#2962FF',type:'line',lineWidth:2},{id:'plot1',title:'Slow',color:'#FF6D00',type:'line',lineWidth:2}],                          description:'이동평균 교차 (골든크로스/데드크로스)' },

  // ── Channels & Bands ──────────────────────────────────────────────────────
  { type:'BollingerBands', name:'Bollinger Bands',          koreanName:'볼린저밴드',          shortName:'BB',     category:'Channels & Bands', overlay:true, defaultParams:{symbolMode:'main', refSymbol:'', length:20, mult:2, offset:0, src:'close', maType:'SMA'}, plots:[{id:'plot0',title:'중앙값',color:'#FF9800',type:'line',lineWidth:1},{id:'plot1',title:'어퍼',color:'#42A5F5',type:'line',lineWidth:1},{id:'plot2',title:'로우어',color:'#42A5F5',type:'line',lineWidth:1}], description:'볼린저 밴드 (SMA ± σ×곱)' },
  { type:'KeltnerChannels',name:'Keltner Channels',         koreanName:'켈트너채널',          shortName:'KC',     category:'Channels & Bands', overlay:true, defaultParams:{length:20, multiplier:2},                 plots:[{id:'plot0',title:'Upper', color:'#7E57C2',type:'line',lineWidth:1},{id:'plot1',title:'Middle',color:'#EF5350',type:'line',lineWidth:1},{id:'plot2',title:'Lower',color:'#7E57C2',type:'line',lineWidth:1}],               description:'켈트너 채널' },
  { type:'DonchianChannels',name:'Donchian Channels',       koreanName:'돈치안채널',          shortName:'DC',     category:'Channels & Bands', overlay:true, defaultParams:{length:20},                               plots:[{id:'plot0',title:'Upper', color:'#4CAF50',type:'line',lineWidth:1},{id:'plot1',title:'Basis',color:'#FFC107',type:'line',lineWidth:1},{id:'plot2',title:'Lower',color:'#4CAF50',type:'line',lineWidth:1}],                description:'돈치안 채널' },
  { type:'Envelope',        name:'Envelope',                koreanName:'엔벨로프',            shortName:'Env',    category:'Channels & Bands', overlay:true, defaultParams:{length:20, percent:0.1, src:'close'},      plots:[{id:'plot0',title:'Upper', color:'#009688',type:'line',lineWidth:1},{id:'plot1',title:'Basis',color:'#607D8B',type:'line',lineWidth:1},{id:'plot2',title:'Lower',color:'#009688',type:'line',lineWidth:1}],               description:'엔벨로프 채널' },
  { type:'BBPercentB',      name:'BB %B',                   koreanName:'볼린저밴드 %B',       shortName:'BB%B',   category:'Channels & Bands', overlay:false,defaultParams:{length:20, mult:2, src:'close'},          plots:[{id:'plot0',title:'%B',    color:'#2196F3',type:'line',lineWidth:2}], hlines:[{price:1,color:'#EF5350'},{price:0.5,color:'#607D8B'},{price:0,color:'#4CAF50'}], description:'볼린저밴드 내 가격 위치 (%B)' },
  { type:'BBBandWidth',     name:'BB BandWidth',            koreanName:'볼린저밴드폭',        shortName:'BBW',    category:'Channels & Bands', overlay:false,defaultParams:{length:20, mult:2, src:'close'},          plots:[{id:'plot0',title:'BBW',   color:'#9C27B0',type:'line',lineWidth:2}],                                                                                          description:'볼린저밴드 폭 (변동성 측정)' },

  // ── Oscillators ───────────────────────────────────────────────────────────
  { type:'RSI',               name:'Relative Strength Index',    koreanName:'상대강도지수',          shortName:'RSI',    category:'Oscillators', overlay:false, defaultParams:{length:14, src:'close', maType:'SMA', maLength:14}, plots:[{id:'plot0',title:'RSI',color:'#7E57C2',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#F23645',type:'line',lineWidth:1}], hlines:[{price:70,color:'#EF5350'},{price:50,color:'#607D8B'},{price:30,color:'#4CAF50'}], description:'상대 강도 지수 (0~100)' },
  { type:'Stochastic',        name:'Stochastic Slow',            koreanName:'스토케스틱 슬로우',     shortName:'Stoch',  category:'Oscillators', overlay:false, defaultParams:{kLength:14, dSmoothing:3, smooth:3},      plots:[{id:'plot0',title:'%K',color:'#2196F3',type:'line',lineWidth:1},{id:'plot1',title:'%D',color:'#FF6D00',type:'line',lineWidth:1}], hlines:STOCHASTIC_DEFAULT_HLINES, description:'Stochastic Slow %K/%D (Slowing 적용)' },
  { type:'StochRSI',          name:'Stochastic RSI',             koreanName:'스토캐스틱RSI',         shortName:'StochRSI',category:'Oscillators',overlay:false, defaultParams:{lengthRSI:14, lengthStoch:14, smoothK:3, smoothD:3, src:'close'}, plots:[{id:'plot0',title:'%K',color:'#2196F3',type:'line',lineWidth:1},{id:'plot1',title:'%D',color:'#FF6D00',type:'line',lineWidth:1}], hlines:STOCHASTIC_DEFAULT_HLINES, description:'스토캐스틱 RSI' },
  { type:'CCI',               name:'Commodity Channel Index',    koreanName:'상품채널지수',          shortName:'CCI',    category:'Oscillators', overlay:false, defaultParams:{length:13, src:'hlc3', maType:'SMA', maLength:20}, plots:[{id:'plot0',title:'CCI',color:'#2962FF',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#F23645',type:'line',lineWidth:1}], hlines:[{price:100,color:'#EF5350'},{price:0,color:'#607D8B'},{price:-100,color:'#4CAF50'}], description:'상품 채널 지수' },
  { type:'WilliamsPercentRange',name:'Williams %R',              koreanName:'윌리엄스 %R',           shortName:'W%R',    category:'Oscillators', overlay:false, defaultParams:{length:14, src:'close'},                    plots:[{id:'plot0',title:'%R', color:'#9C27B0',type:'line',lineWidth:2}],                                  hlines:[{price:-20,color:'#EF5350'},{price:-50,color:'#607D8B'},{price:-80,color:'#4CAF50'}], description:'윌리엄스 %R 오실레이터' },
  { type:'AwesomeOscillator',  name:'Awesome Oscillator',        koreanName:'어썸오실레이터',        shortName:'AO',     category:'Oscillators', overlay:false, defaultParams:{},                                        plots:[{id:'plot0',title:'AO',color:'#26A69A',type:'histogram'}],                                           hlines:[{price:0,color:'#607D8B'}],                                                         description:'AO 오실레이터' },
  { type:'ChandeMO',          name:'Chande Momentum Oscillator', koreanName:'챈드모멘텀오실레이터',  shortName:'CMO',    category:'Oscillators', overlay:false, defaultParams:{length:9, src:'close'},                   plots:[{id:'plot0',title:'CMO',color:'#4DB6AC',type:'line',lineWidth:2}],                                  hlines:[{price:50,color:'#EF5350'},{price:0,color:'#607D8B'},{price:-50,color:'#4CAF50'}], description:'챈드 모멘텀 오실레이터' },
  { type:'DPO',               name:'Detrended Price Oscillator', koreanName:'비트렌드가격오실레이터',shortName:'DPO',    category:'Oscillators', overlay:false, defaultParams:{length:21, src:'close'},                  plots:[{id:'plot0',title:'DPO',color:'#FF7043',type:'line',lineWidth:2}],                                  hlines:[{price:0,color:'#607D8B'}],                                                         description:'비트렌드 가격 오실레이터' },
  { type:'RVI',               name:'Relative Vigor Index',       koreanName:'상대활력지수',          shortName:'RVI',    category:'Oscillators', overlay:false, defaultParams:{length:10},                               plots:[{id:'plot0',title:'RVI', color:'#26A69A',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#EF5350',type:'line',lineWidth:1}], hlines:[{price:0,color:'#607D8B'}], description:'상대 활력 지수' },
  { type:'FisherTransform',   name:'Fisher Transform',           koreanName:'피셔변환',              shortName:'Fish',   category:'Oscillators', overlay:false, defaultParams:{length:9},                                plots:[{id:'plot0',title:'Fisher', color:'#2962FF',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#FF6D00',type:'line',lineWidth:1}], hlines:[{price:2.5,color:'#EF5350'},{price:0,color:'#607D8B'},{price:-2.5,color:'#4CAF50'}], description:'피셔 변환 오실레이터' },
  { type:'UltimateOscillator', name:'Ultimate Oscillator',       koreanName:'울티메이트오실레이터',  shortName:'UO',     category:'Oscillators', overlay:false, defaultParams:{length1:7, length2:14, length3:28},       plots:[{id:'plot0',title:'UO',  color:'#7E57C2',type:'line',lineWidth:2}],                                  hlines:[{price:70,color:'#EF5350'},{price:50,color:'#607D8B'},{price:30,color:'#4CAF50'}], description:'울티메이트 오실레이터' },
  { type:'ConnorsRSI',        name:'Connors RSI',                koreanName:'코너스RSI',             shortName:'CRSI',   category:'Oscillators', overlay:false, defaultParams:{rsiLength:3, upDownLength:2, rocLength:100}, plots:[{id:'plot0',title:'CRSI',color:'#AB47BC',type:'line',lineWidth:2}],                               hlines:[{price:90,color:'#EF5350'},{price:10,color:'#4CAF50'}],                             description:'코너스 RSI' },
  { type:'SMIErgodic',        name:'SMI Ergodic',                koreanName:'SMI에르고딕',           shortName:'SMI',    category:'Oscillators', overlay:false, defaultParams:{longLength:20, shortLength:5, signalLength:5}, plots:[{id:'plot0',title:'SMI',color:'#26A69A',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#EF5350',type:'line',lineWidth:1}], hlines:[{price:0.4,color:'#EF535080'},{price:0,color:'#607D8B'},{price:-0.4,color:'#4CAF5080'}], description:'SMI 에르고딕' },
  { type:'SMIErgodicOscillator',name:'SMI Ergodic Oscillator',   koreanName:'SMI에르고딕오실레이터', shortName:'SMIO',   category:'Oscillators', overlay:false, defaultParams:{longLength:20, shortLength:5, signalLength:5}, plots:[{id:'plot0',title:'SMIO',color:'#00BCD4',type:'histogram'}],                                    hlines:[{price:0.4,color:'#EF535080'},{price:0,color:'#607D8B'},{price:-0.4,color:'#4CAF5080'}], description:'SMI 에르고딕 오실레이터' },
  { type:'WoodiesCCI',        name:'Woodies CCI',                koreanName:'우디CCI',               shortName:'WCCI',   category:'Oscillators', overlay:false, defaultParams:{ccilength:14, turbolen:6},                plots:[{id:'plot0',title:'Turbo',color:'#FF6D00',type:'histogram'},{id:'plot1',title:'CCI',color:'#2962FF',type:'line',lineWidth:2}], hlines:[{price:100,color:'#EF535080'},{price:0,color:'#607D8B'},{price:-100,color:'#4CAF5080'}], description:'우디 CCI' },
  { type:'RelativeVolatilityIndex',name:'Relative Volatility Index',koreanName:'상대변동성지수',    shortName:'RVI2',   category:'Oscillators', overlay:false, defaultParams:{length:14, stdevLength:10},               plots:[{id:'plot0',title:'RVI',color:'#FF7043',type:'line',lineWidth:2}],                                  hlines:[{price:60,color:'#EF5350'},{price:40,color:'#4CAF50'}],                             description:'상대 변동성 지수' },
  { type:'ChopZone',          name:'Chop Zone',                  koreanName:'변동성구분지표',        shortName:'CZ',     category:'Oscillators', overlay:false, defaultParams:{length:30, atrLength:1},                  plots:[{id:'plot0',title:'CZ',  color:'#FF9800',type:'histogram'}],                                        hlines:[],                                                                                  description:'변동성 vs 추세 구분 지표' },

  // ── Momentum ──────────────────────────────────────────────────────────────
  { type:'MACD',             name:'MACD',                        koreanName:'이동평균수렴발산',    shortName:'MACD',   category:'Momentum', overlay:false, defaultParams:{fastLength:12, slowLength:26, signalLength:9, src:'close'},   plots:[{id:'plot0',title:'Histogram',color:'#26A69A',type:'histogram'},{id:'plot1',title:'MACD',color:'#2962FF',type:'line',lineWidth:1},{id:'plot2',title:'Signal',color:'#FF6D00',type:'line',lineWidth:1}],                 hlines:[{price:0,color:'#607D8B'}], description:'이동평균 수렴·발산' },
  { type:'Momentum',         name:'Momentum',                    koreanName:'모멘텀',              shortName:'MOM',    category:'Momentum', overlay:false, defaultParams:{length:10, src:'close'},                                       plots:[{id:'plot0',title:'MOM',  color:'#26A69A',type:'line',lineWidth:2}],                      hlines:[{price:0,color:'#607D8B'}],  description:'모멘텀 지표' },
  { type:'ROC',              name:'Rate of Change',              koreanName:'가격변화율',          shortName:'ROC',    category:'Momentum', overlay:false, defaultParams:{length:9, src:'close'},                                        plots:[{id:'plot0',title:'ROC',  color:'#00BCD4',type:'line',lineWidth:2}],                      hlines:[{price:10,color:'#EF535080'},{price:0,color:'#607D8B'},{price:-10,color:'#4CAF5080'}], description:'가격 변화율 (%)' },
  { type:'TSI',              name:'True Strength Index',         koreanName:'참강도지수',          shortName:'TSI',    category:'Momentum', overlay:false, defaultParams:{longLength:25, shortLength:13, signalLength:13, src:'close'},  plots:[{id:'plot0',title:'TSI',  color:'#2196F3',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#FF6D00',type:'line',lineWidth:1}], hlines:[{price:25,color:'#EF5350'},{price:0,color:'#607D8B'},{price:-25,color:'#4CAF50'}], description:'참 강도 지수' },
  { type:'TRIX',             name:'TRIX',                        koreanName:'삼중지수변화율',      shortName:'TRIX',   category:'Momentum', overlay:false, defaultParams:{length:12, signalLength:9},                       plots:[{id:'plot0',title:'TRIX', color:'#26A69A',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#FF6D00',type:'line',lineWidth:1}], hlines:[{price:0,color:'#607D8B', label:'중앙선'}], description:'삼중 지수 변화율 (ln→3×EMA→Δ×10000)' },
  { type:'KnowSureThing',    name:'Know Sure Thing (KST)',       koreanName:'KST모멘텀',           shortName:'KST',    category:'Momentum', overlay:false, defaultParams:{},                                                             plots:[{id:'plot0',title:'KST',  color:'#9C27B0',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#FF9800',type:'line',lineWidth:1}], hlines:[{price:0,color:'#607D8B'}], description:'KST 모멘텀 지표' },
  { type:'CoppockCurve',     name:'Coppock Curve',               koreanName:'콥독커브',            shortName:'Copp',   category:'Momentum', overlay:false, defaultParams:{},                                                             plots:[{id:'plot0',title:'Coppock',color:'#E91E63',type:'line',lineWidth:2}],                     hlines:[{price:0,color:'#607D8B'}],  description:'콥독 커브 장기 모멘텀' },
  { type:'BullBearPower',    name:'Bull Bear Power',             koreanName:'매수매도세력',        shortName:'BBP',    category:'Momentum', overlay:false, defaultParams:{length:13, src:'close'},                                       plots:[{id:'plot0',title:'BBP',  color:'#26A69A',type:'histogram'}],                             hlines:[{price:0,color:'#607D8B'}],  description:'매수/매도 세력 비교' },
  { type:'ElderForceIndex',  name:'Elder Force Index',           koreanName:'엘더강도지수',        shortName:'EFI',    category:'Momentum', overlay:false, defaultParams:{length:13},                                                    plots:[{id:'plot0',title:'EFI',  color:'#00BCD4',type:'histogram'}],                             hlines:[{price:0,color:'#607D8B'}],  description:'엘더 강도 지수' },
  { type:'PriceOscillator',  name:'Price Oscillator (PPO)',      koreanName:'가격오실레이터',      shortName:'PPO',    category:'Momentum', overlay:false, defaultParams:{fastLength:12, slowLength:26, src:'close'},                    plots:[{id:'plot0',title:'PPO',  color:'#FF7043',type:'line',lineWidth:2}],                      hlines:[{price:2,color:'#EF535080'},{price:0,color:'#607D8B'},{price:-2,color:'#4CAF5080'}], description:'가격 오실레이터 (MACD의 %)' },
  { type:'EaseOfMovement',   name:'Ease of Movement',            koreanName:'이동용이성',          shortName:'EOM',    category:'Momentum', overlay:false, defaultParams:{length:14},                                                    plots:[{id:'plot0',title:'EOM',  color:'#009688',type:'line',lineWidth:2}],                      hlines:[{price:0,color:'#607D8B'}],  description:'이동 용이성 지표' },

  // ── Trend ──────────────────────────────────────────────────────────────
  { type:'ADX',             name:'Average Directional Index',    koreanName:'평균방향성지수',  shortName:'ADX',    category:'Trend', overlay:false, defaultParams:{adxSmoothing:14, diLength:14},                  plots:[{id:'plot0',title:'ADX',color:'#FF6D00',type:'line',lineWidth:2},{id:'plot1',title:'+DI',color:'#4CAF50',type:'line',lineWidth:1},{id:'plot2',title:'-DI',color:'#EF5350',type:'line',lineWidth:1}], hlines:ADX_DEFAULT_HLINES, description:'평균 방향성 지수 (추세 강도)' },
  { type:'DMI',             name:'Directional Movement Index',   koreanName:'방향성이동지수',  shortName:'DMI',    category:'Trend', overlay:false, defaultParams:{diLength:14, adxSmoothing:14}, plots:[{id:'plot0',title:'+DI',color:'#2962FF',type:'line',lineWidth:1},{id:'plot1',title:'-DI',color:'#FF6D00',type:'line',lineWidth:1},{id:'plot2',title:'DX',color:'#FF9800',type:'line',lineWidth:1},{id:'plot3',title:'ADX',color:'#E91E63',type:'line',lineWidth:1},{id:'plot4',title:'ADXR',color:'#9C27B0',type:'line',lineWidth:1}], hlines:DMI_DEFAULT_HLINES, description:'방향성 이동 지수 (+DI·-DI·DX·ADX·ADXR)' },
  { type:'Aroon',           name:'Aroon',                        koreanName:'아룬지표',        shortName:'Aroon',  category:'Trend', overlay:false, defaultParams:{length:14},                                     plots:[{id:'plot0',title:'Up',  color:'#4CAF50',type:'line',lineWidth:1},{id:'plot1',title:'Down',color:'#EF5350',type:'line',lineWidth:1}],                                                                        hlines:[{price:70,color:'#4CAF5060'},{price:30,color:'#EF535060'}], description:'아룬 추세 지표' },
  { type:'IchimokuCloud',   name:'Ichimoku Cloud',               koreanName:'일목균형표',      shortName:'Ichi',   category:'Trend', overlay:true,  defaultParams:{conversionPeriods:9, basePeriods:26, laggingSpan2Periods:52, displacement:26}, plots:[{id:'plot0',title:'전환선',color:'#2196F3',type:'line',lineWidth:1},{id:'plot1',title:'기준선',color:'#EF5350',type:'line',lineWidth:1},{id:'plot2',title:'후행스팬',color:'#4CAF50',type:'line',lineWidth:1},{id:'plot3',title:'선행스팬1',color:'#EF5350',type:'line',lineWidth:1},{id:'plot4',title:'선행스팬2',color:'#9C27B0',type:'line',lineWidth:1}], description:'일목균형표' },
  { type:'Supertrend',      name:'Supertrend',                   koreanName:'슈퍼트렌드',      shortName:'ST',     category:'Trend', overlay:true,  defaultParams:{atrPeriod:10, factor:3},                        plots:[{id:'plot0',title:'ST',  color:'#4CAF50',type:'line',lineWidth:2}],                                                                                                   description:'슈퍼트렌드 (ATR 기반)' },
  { type:'ParabolicSAR',    name:'Parabolic SAR',                koreanName:'파라볼릭SAR',     shortName:'SAR',    category:'Trend', overlay:true,  defaultParams:{start:0.02, increment:0.02, maximum:0.2},       plots:[{id:'plot0',title:'SAR', color:'#FF6D00',type:'scatter'}],                                                                                                           description:'파라볼릭 SAR' },
  { type:'Choppiness',      name:'Choppiness Index',             koreanName:'불규칙성지수',    shortName:'CHOP',   category:'Trend', overlay:false, defaultParams:{length:14},                                     plots:[{id:'plot0',title:'CHOP',color:'#FFC107',type:'line',lineWidth:2}],                                  hlines:[{price:61.8,color:'#EF535080'},{price:38.2,color:'#4CAF5080'}],                     description:'변동성 vs 추세 구분 (61.8이하=추세)' },
  { type:'MassIndex',       name:'Mass Index',                   koreanName:'매스인덱스',      shortName:'MI',     category:'Trend', overlay:false, defaultParams:{length:25, length2:9},                          plots:[{id:'plot0',title:'MI',  color:'#E91E63',type:'line',lineWidth:2}],                                  hlines:[{price:27,color:'#EF535080'},{price:26.5,color:'#4CAF5080'}],                       description:'매스 인덱스 (반전 탐지)' },
  { type:'VortexIndicator', name:'Vortex Indicator',             koreanName:'보텍스지표',      shortName:'VI',     category:'Trend', overlay:false, defaultParams:{length:14},                                     plots:[{id:'plot0',title:'VI+', color:'#4CAF50',type:'line',lineWidth:1},{id:'plot1',title:'VI-',color:'#EF5350',type:'line',lineWidth:1}],                                 hlines:[{price:1,color:'#607D8B'}],  description:'보텍스 지표 (추세 전환 확인)' },
  { type:'WilliamsAlligator',name:'Williams Alligator',          koreanName:'윌리엄스악어',    shortName:'Alli',   category:'Trend', overlay:true,  defaultParams:{},                                              plots:[{id:'plot0',title:'Jaw', color:'#2196F3',type:'line',lineWidth:1},{id:'plot1',title:'Teeth',color:'#4CAF50',type:'line',lineWidth:1},{id:'plot2',title:'Lips',color:'#EF5350',type:'line',lineWidth:1}], description:'윌리엄스 악어 (3개 SMMA)' },
  { type:'TrendStrengthIndex',name:'Trend Strength Index',       koreanName:'추세강도지수',    shortName:'TSI2',   category:'Trend', overlay:false, defaultParams:{},                                              plots:[{id:'plot0',title:'TSI', color:'#9C27B0',type:'line',lineWidth:2}],                                                                                                   description:'추세 강도 지수' },
  { type:'BBTrend',         name:'BB Trend',                     koreanName:'볼린저밴드추세',  shortName:'BBT',    category:'Trend', overlay:false, defaultParams:{length:20, mult:2},                             plots:[{id:'plot0',title:'BBTrend',color:'#FF7043',type:'histogram'}],                                     hlines:[{price:0,color:'#607D8B'}],  description:'볼린저밴드 기반 추세 방향' },
  { type:'ChandeKrollStop', name:'Chande Kroll Stop',            koreanName:'챈드크롤스탑',    shortName:'CKS',    category:'Trend', overlay:true,  defaultParams:{p:10, q:9, x:3},                               plots:[{id:'plot0',title:'Stop+',color:'#4CAF50',type:'line',lineWidth:2},{id:'plot1',title:'Stop-',color:'#EF5350',type:'line',lineWidth:2}],                           description:'챈드 크롤 스탑' },
  { type:'ZigZag',          name:'ZigZag',                       koreanName:'지그재그',        shortName:'ZZ',     category:'Trend', overlay:true,  defaultParams:{deviation:5, depth:10, backstep:3},            plots:[{id:'plot0',title:'ZZ',  color:'#FF5722',type:'line',lineWidth:2}],                                                                                                   description:'지그재그 (스윙 고점·저점 연결)' },
  { type:'WilliamsFractals',name:'Williams Fractals',            koreanName:'윌리엄스프랙탈',  shortName:'Frac',   category:'Trend', overlay:true,  defaultParams:{},                                              plots:[],                                                                                                          returnsMarkers:true,                                                                        description:'윌리엄스 프랙탈 (고점·저점 마커)' },
  { type:'RankCorrelationIndex',name:'Rank Correlation Index (RCI)',koreanName:'순위상관지수', shortName:'RCI',    category:'Oscillators', overlay:false, defaultParams:{length1:9, length2:26, length3:52},      plots:[{id:'plot0',title:'RCI9',color:'#2196F3',type:'line',lineWidth:2},{id:'plot1',title:'RCI26',color:'#FF9800',type:'line',lineWidth:1}], hlines:[{price:80,color:'#EF535080'},{price:-80,color:'#4CAF5080'}], description:'순위 상관 지수 (추세 강도)' },

  // ── Volatility ────────────────────────────────────────────────────────────
  { type:'ATR',               name:'Average True Range',         koreanName:'평균실제범위',    shortName:'ATR',    category:'Volatility', overlay:false, defaultParams:{length:14, smoothing:'RMA'},              plots:[{id:'plot0',title:'ATR',  color:'#FF9800',type:'line',lineWidth:2}],                                                                                         description:'평균 진범위 (변동성)' },
  { type:'ADR',               name:'Average Day Range',          koreanName:'평균일일범위',    shortName:'ADR',    category:'Volatility', overlay:false, defaultParams:{length:14},                               plots:[{id:'plot0',title:'ADR',  color:'#FF5722',type:'line',lineWidth:2}],                                                                                         description:'평균 일일 범위' },
  { type:'StandardDeviation', name:'Standard Deviation',         koreanName:'표준편차',        shortName:'StdDev', category:'Volatility', overlay:false, defaultParams:{length:20, src:'close'},                  plots:[{id:'plot0',title:'StdDev',color:'#9C27B0',type:'line',lineWidth:2}],                                                                                      description:'표준 편차 (가격 변동성)' },
  { type:'HistoricalVolatility',name:'Historical Volatility',    koreanName:'역사적변동성',    shortName:'HV',     category:'Volatility', overlay:false, defaultParams:{length:20, annualBars:252},               plots:[{id:'plot0',title:'HV',   color:'#E91E63',type:'line',lineWidth:2}],                                                                                         description:'역사적 변동성 (연화)' },

  // ── Volume ────────────────────────────────────────────────────────────────
  { type:'OBV',                 name:'On Balance Volume',          koreanName:'잔량균형지수',        shortName:'OBV',   category:'Volume', overlay:false, defaultParams:{maType:'SMA', maLength:9},                      plots:[{id:'plot0',title:'OBV',  color:'#2196F3',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#FF9800',type:'line',lineWidth:1}], hlines:[], description:'잔량 균형 지수 (OBV + 이동평균 신호선)' },
  { type:'VR',                  name:'Volume Ratio',               koreanName:'거래량비율',          shortName:'VR',    category:'Volume', overlay:false, defaultParams:{length:10},                                 plots:[{id:'plot0',title:'VR',   color:'#AB47BC',type:'line',lineWidth:2}],                                  hlines:[{price:200,color:'#EF5350'},{price:100,color:'#607D8B'},{price:50,color:'#4CAF50'}], description:'거래량 비율 (VR)' },
  { type:'Disparity',           name:'Disparity Index',            koreanName:'이격도',              shortName:'Disp',  category:'Oscillators', overlay:false, defaultParams:{ultraLength:5, shortLength:10, midLength:20, longLength:60, src:'close'}, plots:[{id:'plot0',title:'초단기',color:'#E91E63',type:'line',lineWidth:1},{id:'plot1',title:'단기',color:'#FF9800',type:'line',lineWidth:1},{id:'plot2',title:'중기',color:'#4CAF50',type:'line',lineWidth:1},{id:'plot3',title:'장기',color:'#2196F3',type:'line',lineWidth:1}], hlines:[{price:100,color:'#607D8B'}], description:'이격도 (종가÷이평×100)' },
  { type:'Psychological',       name:'Psychological Line',         koreanName:'심리도',              shortName:'Psy',   category:'Oscillators', overlay:false, defaultParams:{length:12},                                 plots:[{id:'plot0',title:'심리도',color:'#00BCD4',type:'line',lineWidth:2}],                              hlines:[{price:75,color:'#EF5350',label:'과열선'},{price:50,color:'#607D8B',label:'중앙선'},{price:25,color:'#4CAF50',label:'침체선'}], description:'심리도 (상승일 비율)' },
  { type:'InvestPsychological', name:'Invest Psychological Line',  koreanName:'투자심리도',          shortName:'IPsych',category:'Oscillators', overlay:false, defaultParams:{length:10},                                 plots:[{id:'plot0',title:'투자심리도',color:'#7E57C2',type:'line',lineWidth:2}],                          hlines:[{price:75,color:'#EF5350',label:'과열선'},{price:50,color:'#607D8B',label:'중앙선'},{price:25,color:'#4CAF50',label:'침체선'}], description:'투자심리도 (상승일 거래량 비중)' },
  { type:'MFI',                 name:'Money Flow Index',           koreanName:'자금흐름지수',        shortName:'MFI',   category:'Volume', overlay:false, defaultParams:{length:14},                                 plots:[{id:'plot0',title:'MFI',  color:'#00BCD4',type:'line',lineWidth:2}],                                  hlines:[{price:80,color:'#EF5350'},{price:20,color:'#4CAF50'}], description:'자금 흐름 지수' },
  { type:'ChaikinMF',           name:'Chaikin Money Flow',         koreanName:'차이킨자금흐름',      shortName:'CMF',   category:'Volume', overlay:false, defaultParams:{length:20},                                 plots:[{id:'plot0',title:'CMF',  color:'#4CAF50',type:'histogram'}],                                        hlines:[{price:0.25,color:'#EF5350'},{price:0,color:'#607D8B'},{price:-0.25,color:'#4CAF50'}], description:'차이킨 자금 흐름' },
  { type:'ChaikinOscillator',   name:'Chaikin Oscillator',         koreanName:'차이킨오실레이터',    shortName:'CO',    category:'Volume', overlay:false, defaultParams:{fastLength:3, slowLength:10},               plots:[{id:'plot0',title:'CO',   color:'#FF9800',type:'histogram'}],                                        hlines:[{price:0,color:'#607D8B'}], description:'차이킨 오실레이터' },
  { type:'PVT',                 name:'Price Volume Trend',         koreanName:'가격거래량추세',      shortName:'PVT',   category:'Volume', overlay:false, defaultParams:{},                                          plots:[{id:'plot0',title:'PVT',  color:'#7E57C2',type:'line',lineWidth:2}],                                                                                         description:'가격 거래량 추세' },
  { type:'NetVolume',           name:'Net Volume',                 koreanName:'순거래량',            shortName:'NV',    category:'Volume', overlay:false, defaultParams:{},                                          plots:[{id:'plot0',title:'NV',   color:'#26A69A',type:'histogram'}],                                        hlines:[{price:0,color:'#607D8B'}], description:'순 거래량 (매수-매도)' },
  { type:'VolumeDelta',         name:'Volume Delta',               koreanName:'거래량델타',          shortName:'VD',    category:'Volume', overlay:false, defaultParams:{},                                          plots:[{id:'plot0',title:'VD',   color:'#2196F3',type:'histogram'}],                                        hlines:[{price:0,color:'#607D8B'}], description:'볼륨 델타 (매수/매도 차이)' },
  { type:'CumulativeVolumeDelta',name:'Cumulative Volume Delta',   koreanName:'누적거래량델타',      shortName:'CVD',   category:'Volume', overlay:false, defaultParams:{},                                          plots:[{id:'plot0',title:'CVD',  color:'#E91E63',type:'line',lineWidth:2}],                                  hlines:[{price:0,color:'#607D8B'}], description:'누적 볼륨 델타' },
  { type:'VolumeOscillator',    name:'Volume Oscillator',          koreanName:'거래량오실레이터',    shortName:'VO',    category:'Volume', overlay:false, defaultParams:{shortLength:5, longLength:10},               plots:[{id:'plot0',title:'VO',   color:'#FF7043',type:'histogram'}],                                        hlines:[{price:10,color:'#EF535080'},{price:0,color:'#607D8B'},{price:-10,color:'#4CAF5080'}], description:'볼륨 오실레이터' },
  { type:'KlingerOscillator',   name:'Klinger Oscillator',         koreanName:'클링거오실레이터',    shortName:'KVO',   category:'Volume', overlay:false, defaultParams:{fastLength:34, slowLength:55, signalLength:13}, plots:[{id:'plot0',title:'KVO',color:'#2962FF',type:'line',lineWidth:2},{id:'plot1',title:'Signal',color:'#FF6D00',type:'line',lineWidth:1}], hlines:[{price:0,color:'#607D8B'}], description:'클링거 오실레이터' },
  { type:'RelativeVolumeAtTime',name:'Relative Volume at Time',    koreanName:'시간대별상대거래량',  shortName:'RVAT',  category:'Volume', overlay:false, defaultParams:{length:24, sessionType:'Regular'},          plots:[{id:'plot0',title:'RVAT', color:'#9C27B0',type:'histogram'}],                                        hlines:[{price:1,color:'#607D8B'}], description:'시간대별 상대 거래량' },

  // ── Candlestick Patterns ───────────────────────────────────────────────────
  { type:'Hammer',             name:'Hammer',                      koreanName:'망치형',          shortName:'H',     category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'망치형 (상승 반전)' },
  { type:'InvertedHammer',     name:'Inverted Hammer',             koreanName:'역망치형',        shortName:'IH',    category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'역망치형 (상승 반전)' },
  { type:'HangingMan',         name:'Hanging Man',                 koreanName:'교수형',          shortName:'HM',    category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'교수형 (하락 반전)' },
  { type:'ShootingStar',       name:'Shooting Star',               koreanName:'슈팅스타',        shortName:'SS',    category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'슈팅스타 (하락 반전)' },
  { type:'Doji',               name:'Doji',                        koreanName:'도지형',          shortName:'Doji',  category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'도지형' },
  { type:'DragonflyDoji',      name:'Dragonfly Doji',              koreanName:'잠자리도지',      shortName:'DDoji', category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'잠자리 도지' },
  { type:'GravestoneDoji',     name:'Gravestone Doji',             koreanName:'비석도지',        shortName:'GDoji', category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'비석 도지' },
  { type:'EngulfingBullish',   name:'Bullish Engulfing',           koreanName:'강세포용형',      shortName:'BE',    category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'강세 포용형' },
  { type:'EngulfingBearish',   name:'Bearish Engulfing',           koreanName:'약세포용형',      shortName:'BrE',   category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'약세 포용형' },
  { type:'HaramiBullish',      name:'Bullish Harami',              koreanName:'강세하라미',      shortName:'BH',    category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'강세 하라미' },
  { type:'HaramiBearish',      name:'Bearish Harami',              koreanName:'약세하라미',      shortName:'BrH',   category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'약세 하라미' },
  { type:'MorningStar',        name:'Morning Star',                koreanName:'샛별형',          shortName:'MS',    category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'샛별형 (강세 반전)' },
  { type:'EveningStar',        name:'Evening Star',                koreanName:'저녁별형',        shortName:'ES',    category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'저녁별형 (약세 반전)' },
  { type:'MorningDojiStar',    name:'Morning Doji Star',           koreanName:'도지샛별형',      shortName:'MDS',   category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'도지 샛별형' },
  { type:'EveningDojiStar',    name:'Evening Doji Star',           koreanName:'도지저녁별형',    shortName:'EDS',   category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'도지 저녁별형' },
  { type:'ThreeWhiteSoldiers', name:'Three White Soldiers',        koreanName:'삼백병사형',      shortName:'3WS',   category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'삼병 (강세 패턴)' },
  { type:'ThreeBlackCrows',    name:'Three Black Crows',           koreanName:'흑삼병형',        shortName:'3BC',   category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'흑삼병 (약세 패턴)' },
  { type:'Piercing',           name:'Piercing Line',               koreanName:'관통형',          shortName:'PL',    category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'관통형 (상승 반전)' },
  { type:'DarkCloudCover',     name:'Dark Cloud Cover',            koreanName:'먹구름형',        shortName:'DCC',   category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'먹구름형 (하락 반전)' },
  { type:'TweezerBottom',      name:'Tweezer Bottom',              koreanName:'집게바닥형',      shortName:'TwB',   category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'집게바닥형 (상승 반전)' },
  { type:'TweezerTop',         name:'Tweezer Top',                 koreanName:'집게천장형',      shortName:'TwT',   category:'Candlestick Patterns', overlay:true, defaultParams:{},                               plots:[], returnsMarkers:true, description:'집게천장형 (하락 반전)' },
];

export function getIndicatorDef(type: string): IndicatorDef | undefined {
  return INDICATOR_REGISTRY.find(d => d.type === type);
}

/** 실시간 차트 pane·범례 타이틀 (심리도·투자심리도·이격도 등 한글명 우선) */
export function getIndicatorChartTitle(type: string): string {
  const resolved = type === 'NewPsychological' ? 'Psychological' : type;
  const def = getIndicatorDef(resolved);
  if (!def) return type;
  if (
    resolved === 'Psychological'
    || resolved === 'InvestPsychological'
    || resolved === 'Disparity'
  ) {
    return def.koreanName;
  }
  return def.shortName;
}

/**
 * DB/슬롯에 일부 플롯만 저장된 경우 registry 기본 플롯과 병합.
 * (예: CCI plot0만 있으면 신호선 plot1 이 설정·차트에서 누락됨)
 */
export function mergePlotDefs(saved: PlotDef[] | undefined, defaults: PlotDef[]): PlotDef[] {
  if (!defaults.length) return (saved ?? []).map(p => ({ ...p }));
  const savedMap = new Map((saved ?? []).map(p => [p.id, p]));
  return defaults.map(def => {
    const s = savedMap.get(def.id);
    return s ? { ...def, ...s, id: def.id, title: def.title } : { ...def };
  });
}

/** 업비트 OBV 신호선 MA 종류 (단순·지수·가중만) */
export const OBV_MA_TYPE_OPTIONS = ['SMA', 'EMA', 'WMA'] as const;
export type ObvMaType = (typeof OBV_MA_TYPE_OPTIONS)[number];

export function normalizeObvParams(
  params: Record<string, number | string | boolean>,
): Record<string, number | string | boolean> {
  const raw = String(params.maType ?? 'SMA');
  const maType: ObvMaType = raw === 'EMA' || raw === 'WMA' ? raw : 'SMA';
  const maLength = Math.max(1, Number(params.maLength ?? 9) || 9);
  return { ...params, maType, maLength };
}

/** 차트·설정 모달용 — 파라미터·플롯·가시성을 registry 기본값과 병합 */
export function enrichIndicatorConfig(
  cfg: import('../types').IndicatorConfig
): import('../types').IndicatorConfig {
  const resolvedType = resolvePsychologicalIndicatorType(cfg.type);
  const cfgNorm = resolvedType !== cfg.type ? { ...cfg, type: resolvedType } : cfg;
  const def = getIndicatorDef(cfgNorm.type);
  if (!def) return cfgNorm;

  if (cfgNorm.type === 'SMA') {
    return normalizeSmaConfig({
      ...cfgNorm,
      params: {
        ...(def.defaultParams as Record<string, number | string | boolean>),
        ...cfg.params,
      },
      plots: mergePlotDefs(cfg.plots, def.plots),
    });
  }

  if (cfgNorm.type === 'IchimokuCloud') {
    return normalizeIchimokuConfig({
      ...cfgNorm,
      params: {
        ...(def.defaultParams as Record<string, number | string | boolean>),
        ...cfgNorm.params,
      },
      plots: mergeIchimokuPlots(cfgNorm.plots, def.plots),
    });
  }

  let params = {
    ...(def.defaultParams as Record<string, number | string | boolean>),
    ...cfgNorm.params,
  };
  if (cfgNorm.type === 'OBV') {
    params = normalizeObvParams(params);
  }
  if (cfgNorm.type === 'Psychological' || cfgNorm.type === 'InvestPsychological') {
    params = normalizePsychologicalParams(cfgNorm.type, params);
  }
  if (cfgNorm.type === 'BollingerBands') {
    params = normalizeBollingerParams(params);
  }
  if (cfgNorm.type === 'DMI') {
    params = normalizeDmiParams(params);
  }
  const plots = cfgNorm.type === 'BollingerBands'
    ? mergeBbPlots(cfgNorm.plots, def.plots)
    : mergePlotDefs(cfgNorm.plots, def.plots);
  const plotVisibility: Record<string, boolean> = { ...cfgNorm.plotVisibility };
  for (const p of plots) {
    if (plotVisibility[p.id] === undefined) plotVisibility[p.id] = true;
  }
  if (cfgNorm.type === 'OBV') {
    plotVisibility.plot1 = plotVisibility.plot1 !== false;
  } else if ((cfgNorm.type === 'CCI' || cfgNorm.type === 'RSI') && params.maType === 'None') {
    plotVisibility.plot1 = false;
  }

  const out: import('../types').IndicatorConfig = {
    ...cfgNorm,
    params,
    plots,
    plotVisibility,
    hlines: normalizeHLines(cfgNorm.hlines ?? [], cfgNorm.type, def.hlines),
  };
  if (cfgNorm.type === 'BollingerBands') {
    out.bandBackground = resolveBbBandBackground(cfgNorm.bandBackground);
  }
  return out;
}

export function toBars(ohlcv: OHLCVBar[]): Bar[] {
  return ohlcv as unknown as Bar[];
}

export type PlotData = Array<{ time: number; value: number; color?: string }>;
export type IndicatorResult = Record<string, PlotData>;

export interface MarkerData {
  time: number;
  position: 'aboveBar' | 'belowBar';
  shape: 'arrowUp' | 'arrowDown' | 'circle' | 'square';
  color: string;
  text: string;
}

/**
 * 프로그램 내부 파라미터 키 → lightweight-charts-indicators 라이브러리 파라미터 키 변환.
 *
 * 라이브러리가 기대하는 파라미터 이름이 내부 registry/DB 키와 다른 경우만 등록.
 * indicatorRegistry.ts defaultParams 와 라이브러리 defaultInputs 를 대조하여 작성.
 */
const PARAM_KEY_MAP: Record<string, Record<string, string>> = {
  // Stochastic: 내부 {kLength, smooth, dSmoothing} → 라이브러리 {periodK, smoothK, periodD}
  Stochastic: {
    kLength:    'periodK',
    smooth:     'smoothK',
    dSmoothing: 'periodD',
  },
};

/**
 * 지표 파라미터를 라이브러리가 인식하는 키 이름으로 변환한다.
 * 매핑이 없는 지표는 원본을 그대로 반환.
 */
function translateParams(
  type: string,
  params: Record<string, number | string | boolean>
): Record<string, number | string | boolean> {
  const keyMap = PARAM_KEY_MAP[type];
  if (!keyMap) return params;

  const translated: Record<string, number | string | boolean> = {};
  for (const [k, v] of Object.entries(params)) {
    translated[keyMap[k] ?? k] = v;
  }
  return translated;
}

const CUSTOM_CALCULATORS: Record<
  string,
  (bars: OHLCVBar[], params: Record<string, number | string | boolean>) => Record<string, PlotData>
> = {};

async function loadCustomCalculators() {
  if (Object.keys(CUSTOM_CALCULATORS).length > 0) return;
  const c = await import('./customIndicators');
  CUSTOM_CALCULATORS.TRIX = c.calcTRIXWithSignal;
  CUSTOM_CALCULATORS.OBV = c.calcOBVWithMA;
  CUSTOM_CALCULATORS.VR = c.calcVR;
  CUSTOM_CALCULATORS.Disparity = c.calcDisparityUpbit;
  CUSTOM_CALCULATORS.Psychological = c.calcPsychological;
  CUSTOM_CALCULATORS.NewPsychological = c.calcPsychological;
  CUSTOM_CALCULATORS.InvestPsychological = c.calcInvestPsychological;
  CUSTOM_CALCULATORS.BollingerBands = c.calcBollingerBands;
  CUSTOM_CALCULATORS.DMI = c.calcDMI;
}

/** 차트 심볼·타임프레임 (BB 다른 심볼용) */
let chartContext: { market: string; timeframe: Timeframe } = { market: '', timeframe: '1D' };

export function setIndicatorChartContext(market: string, timeframe: Timeframe): void {
  chartContext = { market, timeframe };
}

export async function calculateIndicator(
  type: string,
  bars: OHLCVBar[],
  params: Record<string, number | string | boolean>
): Promise<{ plots: IndicatorResult; markers?: MarkerData[] }> {
  if (type === 'SMA') {
    return { plots: calculateSmaMulti(bars, params) };
  }

  let calcBars = bars;
  let calcParams = params;
  if (type === 'BollingerBands') {
    calcParams = normalizeBollingerParams(params);
    calcBars = await resolveBollingerBars(bars, calcParams, chartContext.timeframe);
  }

  await loadCustomCalculators();
  const custom = CUSTOM_CALCULATORS[type];
  if (custom) {
    return { plots: custom(calcBars, calcParams) as IndicatorResult };
  }

  const b = toBars(bars);
  const lib = await import('lightweight-charts-indicators');
  const ind = (lib as unknown as Record<string, {
    calculate: (b: Bar[], p: unknown) => { plots?: Record<string, PlotData>; markers?: MarkerData[] }
  }>)[type];
  if (!ind?.calculate) throw new Error(`Unknown indicator: ${type}`);
  // 파라미터 키 이름을 라이브러리 기대값으로 변환 후 전달
  const result = ind.calculate(b, translateParams(type, params));
  return {
    plots: (result.plots ?? {}) as IndicatorResult,
    markers: result.markers as MarkerData[] | undefined,
  };
}
