import type { LogicNode } from './strategyTypes'; import type { EdgeHandleBinding } from './strategyFlowLayout'; import { defaultStartMeta, type StartCombineOp, type StartNodeMeta } from './strategyStartNodes'; export type SignalFlowLayoutSnapshot = { positions: Record; edgeHandles: Record; orphans?: LogicNode[]; /** START 노드별 조건 판별 시간봉 */ startMeta?: Record; /** 기본 START 외 추가 START 노드 ID (순서 유지) */ extraStartIds?: string[]; /** 추가 START에 연결된 서브트리 루트 */ extraRoots?: Record; /** START 2개 이상일 때 분기 결합 (AND | OR) */ startCombineOp?: StartCombineOp; }; export type FlowLayoutChangePayload = Omit & { tab: 'buy' | 'sell'; }; export type StrategyFlowLayoutStore = { buy: SignalFlowLayoutSnapshot; sell: SignalFlowLayoutSnapshot; }; /** @deprecated 레거시 localStorage 키 — 신규 저장은 DB flow_layout_json만 사용 */ const LEGACY_STORAGE_KEY = 'gc_se_flow_layout_v1'; export function emptySignalFlowLayout(): SignalFlowLayoutSnapshot { return { positions: {}, edgeHandles: {}, orphans: [], startMeta: defaultStartMeta(), extraStartIds: [], extraRoots: {}, }; } export function emptyStrategyFlowLayout(): StrategyFlowLayoutStore { return { buy: emptySignalFlowLayout(), sell: emptySignalFlowLayout() }; } export function normalizeSignalFlowLayout( snap: SignalFlowLayoutSnapshot | null | undefined, ): SignalFlowLayoutSnapshot { if (!snap) return emptySignalFlowLayout(); return { positions: snap.positions ?? {}, edgeHandles: snap.edgeHandles ?? {}, orphans: snap.orphans ?? [], startMeta: snap.startMeta ?? defaultStartMeta(), extraStartIds: snap.extraStartIds ?? [], extraRoots: snap.extraRoots ?? {}, startCombineOp: snap.startCombineOp, }; } export function normalizeStrategyFlowLayout( store: StrategyFlowLayoutStore | null | undefined, ): StrategyFlowLayoutStore | null { if (!store) return null; return { buy: normalizeSignalFlowLayout(store.buy), sell: normalizeSignalFlowLayout(store.sell), }; } export function buildStrategyFlowLayoutStore( buy: Omit, sell: Omit, buyOrphans: LogicNode[], sellOrphans: LogicNode[], ): StrategyFlowLayoutStore { return { buy: { ...buy, orphans: buyOrphans }, sell: { ...sell, orphans: sellOrphans }, }; } /** DB에 flow layout이 없을 때만 1회 읽기 — 이후 저장은 서버로 이전 */ export function readLegacyFlowLayoutFromLocalStorage( strategyKey: string, ): StrategyFlowLayoutStore | null { try { const raw = localStorage.getItem(LEGACY_STORAGE_KEY); if (!raw) return null; const all = JSON.parse(raw) as Record; return normalizeStrategyFlowLayout(all[strategyKey] ?? null); } catch { return null; } } export function clearLegacyFlowLayoutFromLocalStorage(strategyKey: string): void { try { const raw = localStorage.getItem(LEGACY_STORAGE_KEY); if (!raw) return; const all = JSON.parse(raw) as Record; if (!(strategyKey in all)) return; delete all[strategyKey]; if (Object.keys(all).length === 0) localStorage.removeItem(LEGACY_STORAGE_KEY); else localStorage.setItem(LEGACY_STORAGE_KEY, JSON.stringify(all)); } catch { /* ignore */ } }