import type { IndicatorConfig } from '../types'; import { layoutKey } from './indicatorPaneMerge'; export type IndicatorChartSyncMode = | 'none' | 'remove-only' | 'add-only' | 'params-changed' | 'reorder-only' | 'layout-changed' | 'full'; export interface IndicatorChartChange { mode: IndicatorChartSyncMode; removedIds: string[]; added: IndicatorConfig[]; paramsChangedIds: string[]; } export function singleIndParamKey(i: IndicatorConfig): string { return `${JSON.stringify(i.params)}|${(i.plots ?? []).map(p => p.id).sort().join(',')}|${i.mergedWith ?? ''}`; } /** 차트에 로드된 지표 id 중 현재 설정에 없는(이전 탭 잔존) 항목이 있는지 */ export function chartHasStaleIndicators( loadedIds: Set, indicators: IndicatorConfig[], ): boolean { if (loadedIds.size === 0) return false; const expected = new Set( indicators.filter(i => i.hidden !== true).map(i => i.id), ); for (const id of loadedIds) { if (!expected.has(id)) return true; } return false; } /** 이전·현재 지표 목록 차이를 분류해 최소 갱신 경로를 선택 */ export function classifyIndicatorChartChange( prev: IndicatorConfig[], curr: IndicatorConfig[], ): IndicatorChartChange { const prevMap = new Map(prev.map(i => [i.id, i])); const currMap = new Map(curr.map(i => [i.id, i])); const removedIds = prev.filter(i => !currMap.has(i.id)).map(i => i.id); const added = curr.filter(i => !prevMap.has(i.id)); const paramsChangedIds: string[] = []; for (const c of curr) { const p = prevMap.get(c.id); if (!p) continue; if (singleIndParamKey(p) !== singleIndParamKey(c)) { paramsChangedIds.push(c.id); } } const empty: IndicatorChartChange = { mode: 'none', removedIds, added, paramsChangedIds, }; if (removedIds.length === 0 && added.length === 0 && paramsChangedIds.length === 0) { if (layoutKey(prev) !== layoutKey(curr)) { return { ...empty, mode: 'layout-changed' }; } const sameSet = prev.length === curr.length && prev.every(p => currMap.has(p.id)); if (sameSet && prev.map(i => i.id).join(',') !== curr.map(i => i.id).join(',')) { return { ...empty, mode: 'reorder-only' }; } return empty; } /** 병합·분리 등 mergedWith 변경 — params 키에도 잡히지만 pane 재배치가 필요 */ if ( removedIds.length === 0 && added.length === 0 && layoutKey(prev) !== layoutKey(curr) ) { return { ...empty, mode: 'layout-changed', paramsChangedIds }; } if (removedIds.length > 0 && added.length === 0 && paramsChangedIds.length === 0) { return { ...empty, mode: 'remove-only' }; } if (added.length > 0 && removedIds.length === 0 && paramsChangedIds.length === 0) { return { ...empty, mode: 'add-only' }; } if ( paramsChangedIds.length > 0 && removedIds.length === 0 && added.length === 0 ) { return { ...empty, mode: 'params-changed' }; } return { ...empty, mode: 'full' }; }