/**
 * 전략편집기 — 노드 기반 논리 트리 UI (React Flow)
 */
import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
import { ReactFlowProvider } from '@xyflow/react';
import DraggableModalFrame from './DraggableModalFrame';
import type { Theme } from '../types/index';
import { loadStrategies, saveStrategy, deleteStrategy, type StrategyDto as ApiStrategyDto } from '../utils/backendApi';
import type { LogicNode } from '../utils/strategyTypes';
import {
  buildStrategyEditorDef,
  loadStratsLocal,
  saveStratsLocal,
  mergeAtRoot,
  makeNode,
  updateNode,
  CondEditor,
  genId,
  type StrategyDto,
} from '../utils/strategyEditorShared';
import { findLogicNode, getIndicatorPeriodLabel } from '../utils/strategyFlowLayout';
import {
  decodeConditionForEditor,
  encodeConditionForSave,
  addExtraStartSection,
  hasMultipleStartSections,
  normalizeStartCombineOp,
  updateStartCombineOp,
  type EditorConditionState,
} from '../utils/strategyConditionSerde';
import {
  defaultStartMeta,
  type StartCombineOp,
} from '../utils/strategyStartNodes';
import {
  COMPOSITE_INDICATOR_ITEMS,
  compositePeriodLabel,
} from '../utils/compositeIndicators';
import {
  emptySignalFlowLayout,
  loadStrategyFlowLayout,
  migrateStrategyFlowLayout,
  saveStrategyFlowLayout,
  deleteStrategyFlowLayout,
  type SignalFlowLayoutSnapshot,
  type FlowLayoutChangePayload,
  type StrategyFlowLayoutStore,
} from '../utils/strategyEditorLayoutStorage';
import LogicExpressionPreview from './strategyEditor/LogicExpressionPreview';
import StrategyEditorCanvas, { type FlowLayoutSeed } from './strategyEditor/StrategyEditorCanvas';
import StrategyListEditor from './strategyEditor/StrategyListEditor';
import StartCombineOpControl from './strategyEditor/StartCombineOpControl';
import { layoutFlushRef } from './strategyEditor/strategyEditorCallbacks';
import {
  loadEditorMode,
  saveEditorMode,
  type StrategyEditorMode,
} from '../utils/strategyEditorModeStorage';
import {
  getStrategyTemplates,
  simpleTemplateToNode,
  type StrategyTemplateDef,
} from '../utils/strategyPresets';
import {
  buildStrategyExportPayload,
  buildStrategyListExportPayload,
  downloadStrategyJson,
  listImportItemToStrategyDto,
  parseStrategyImportFile,
  pickJsonFile,
  strategyDtoToListExportItem,
} from '../utils/strategyImportExport';
import PaletteChip from './strategyEditor/PaletteChip';
import { readStoredSize, storeSize, usePanelResize } from './strategyEditor/usePanelResize';
import StrategyDescriptionModal from './strategyEditor/StrategyDescriptionModal';
import '../styles/strategyEditor.css';
import '../styles/strategyEditorTheme.css';

const LEFT_PANEL_MIN = 220;
const LEFT_PANEL_MAX = 520;
const LEFT_PANEL_DEFAULT = 280;
const TERMINAL_MIN = 88;
const TERMINAL_MAX = 420;
const TERMINAL_DEFAULT = 140;

interface Props {
  theme: Theme;
}

function readTabLayout(strategyKey: string, tab: 'buy' | 'sell'): SignalFlowLayoutSnapshot {
  const stored = loadStrategyFlowLayout(strategyKey);
  const snap = stored?.[tab];
  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: normalizeStartCombineOp(snap.startCombineOp),
  };
}

function toEditorState(
  root: LogicNode | null,
  layout: Pick<SignalFlowLayoutSnapshot, 'startMeta' | 'extraStartIds' | 'extraRoots' | 'startCombineOp'>,
): EditorConditionState {
  return {
    root,
    startMeta: layout.startMeta ?? defaultStartMeta(),
    extraStartIds: layout.extraStartIds ?? [],
    extraRoots: layout.extraRoots ?? {},
    startCombineOp: normalizeStartCombineOp(layout.startCombineOp),
  };
}

function normalizeTabLayoutState(snap: SignalFlowLayoutSnapshot) {
  return {
    positions: snap.positions ?? {},
    edgeHandles: snap.edgeHandles ?? {},
    startMeta: snap.startMeta ?? defaultStartMeta(),
    extraStartIds: snap.extraStartIds ?? [],
    extraRoots: snap.extraRoots ?? {},
    startCombineOp: normalizeStartCombineOp(snap.startCombineOp),
  };
}

export default function StrategyEditorPage({ theme }: Props) {
  const DEF = useMemo(() => buildStrategyEditorDef(), []);

  const [strategies, setStrategies] = useState<StrategyDto[]>(() => loadStratsLocal());
  const [selectedId, setSelectedId] = useState<number | null>(null);
  const [buyCondition, setBuyCondition] = useState<LogicNode | null>(null);
  const [sellCondition, setSellCondition] = useState<LogicNode | null>(null);
  const [signalTab, setSignalTab] = useState<'buy' | 'sell'>('buy');
  const [rightTab, setRightTab] = useState<'indicators' | 'templates'>('indicators');
  const [paletteSearch, setPaletteSearch] = useState('');
  const [selectedPaletteKey, setSelectedPaletteKey] = useState<string | null>(null);
  const [stratName, setStratName] = useState('');
  const [stratDesc, setStratDesc] = useState('');
  const [isSaving, setIsSaving] = useState(false);
  const [saveOpen, setSaveOpen] = useState(false);
  const [deleteOpen, setDeleteOpen] = useState(false);
  const [descOpen, setDescOpen] = useState(false);
  const [deleteId, setDeleteId] = useState<number | null>(null);
  const [selectedNodeId, setSelectedNodeId] = useState<string | null>(null);
  const initialDraftBuy = readTabLayout('draft', 'buy');
  const initialDraftSell = readTabLayout('draft', 'sell');
  const [buyOrphans, setBuyOrphans] = useState<LogicNode[]>(() => initialDraftBuy.orphans ?? []);
  const [sellOrphans, setSellOrphans] = useState<LogicNode[]>(() => initialDraftSell.orphans ?? []);
  const [snack, setSnack] = useState<{ msg: string; ok: boolean } | null>(null);
  const [saveToast, setSaveToast] = useState(false);
  const [leftWidth, setLeftWidth] = useState(() => readStoredSize('se-left-width', LEFT_PANEL_DEFAULT));
  const [terminalHeight, setTerminalHeight] = useState(() => readStoredSize('se-terminal-height', TERMINAL_DEFAULT));
  const leftWidthRef = useRef(leftWidth);
  const terminalHeightRef = useRef(terminalHeight);
  const [buyLayout, setBuyLayout] = useState(() => ({
    positions: initialDraftBuy.positions,
    edgeHandles: initialDraftBuy.edgeHandles,
    startMeta: initialDraftBuy.startMeta ?? defaultStartMeta(),
    extraStartIds: initialDraftBuy.extraStartIds ?? [],
    extraRoots: initialDraftBuy.extraRoots ?? {},
    startCombineOp: normalizeStartCombineOp(initialDraftBuy.startCombineOp),
  }));
  const [sellLayout, setSellLayout] = useState(() => ({
    positions: initialDraftSell.positions,
    edgeHandles: initialDraftSell.edgeHandles,
    startMeta: initialDraftSell.startMeta ?? defaultStartMeta(),
    extraStartIds: initialDraftSell.extraStartIds ?? [],
    extraRoots: initialDraftSell.extraRoots ?? {},
    startCombineOp: normalizeStartCombineOp(initialDraftSell.startCombineOp),
  }));
  const buyLayoutRef = useRef(buyLayout);
  const sellLayoutRef = useRef(sellLayout);
  buyLayoutRef.current = buyLayout;
  sellLayoutRef.current = sellLayout;
  const layoutRevisionRef = useRef(0);
  const persistLayoutTimerRef = useRef<number | null>(null);
  const layoutPersistReadyRef = useRef(false);
  leftWidthRef.current = leftWidth;
  terminalHeightRef.current = terminalHeight;

  const [layoutSeedKey, setLayoutSeedKey] = useState('draft:buy:0');
  const [editorMode, setEditorMode] = useState<StrategyEditorMode>(() => loadEditorMode());

  const onLeftSplitter = usePanelResize(
    'vertical',
    setLeftWidth,
    () => leftWidthRef.current,
    LEFT_PANEL_MIN,
    LEFT_PANEL_MAX,
    v => storeSize('se-left-width', v),
  );

  const onTerminalSplitter = usePanelResize(
    'horizontal',
    setTerminalHeight,
    () => terminalHeightRef.current,
    TERMINAL_MIN,
    TERMINAL_MAX,
    v => storeSize('se-terminal-height', v),
  );

  const bodyStyle = useMemo(() => ({
    '--se-left-width': `${leftWidth}px`,
    '--se-terminal-height': `${terminalHeight}px`,
  }) as React.CSSProperties, [leftWidth, terminalHeight]);

  const showSnack = (msg: string, ok = true) => {
    setSnack({ msg, ok });
    setTimeout(() => setSnack(null), 3000);
  };

  const currentRoot = signalTab === 'buy' ? buyCondition : sellCondition;
  const setCurrentRoot = signalTab === 'buy' ? setBuyCondition : setSellCondition;
  const currentOrphans = signalTab === 'buy' ? buyOrphans : sellOrphans;
  const setCurrentOrphans = signalTab === 'buy' ? setBuyOrphans : setSellOrphans;

  const currentLayout = signalTab === 'buy' ? buyLayout : sellLayout;
  const setCurrentLayout = signalTab === 'buy' ? setBuyLayout : setSellLayout;

  const buyEditorState = useMemo(
    () => toEditorState(buyCondition, buyLayout),
    [buyCondition, buyLayout],
  );
  const sellEditorState = useMemo(
    () => toEditorState(sellCondition, sellLayout),
    [sellCondition, sellLayout],
  );
  const currentEditorState = signalTab === 'buy' ? buyEditorState : sellEditorState;

  const selectedLogicNode = useMemo(() => {
    if (!selectedNodeId) return null;
    return findLogicNode(
      currentRoot,
      currentOrphans,
      selectedNodeId,
      currentLayout.extraRoots ?? {},
    );
  }, [selectedNodeId, currentRoot, currentOrphans, currentLayout.extraRoots]);

  const orphanTotal = buyOrphans.length + sellOrphans.length;

  const layoutStrategyKey = selectedId != null ? String(selectedId) : 'draft';

  const persistFlowLayout = useCallback((strategyKey: string) => {
    saveStrategyFlowLayout(strategyKey, {
      buy: { ...buyLayoutRef.current, orphans: buyOrphans },
      sell: { ...sellLayoutRef.current, orphans: sellOrphans },
    });
  }, [buyOrphans, sellOrphans]);

  const schedulePersistFlowLayout = useCallback((strategyKey: string) => {
    if (persistLayoutTimerRef.current != null) window.clearTimeout(persistLayoutTimerRef.current);
    persistLayoutTimerRef.current = window.setTimeout(() => {
      persistLayoutTimerRef.current = null;
      persistFlowLayout(strategyKey);
    }, 150);
  }, [persistFlowLayout]);

  const bumpLayoutSeed = useCallback((strategyKey: string, tab: 'buy' | 'sell') => {
    layoutRevisionRef.current += 1;
    setLayoutSeedKey(`${strategyKey}:${tab}:${layoutRevisionRef.current}`);
  }, []);

  const resetFlowLayout = useCallback((strategyKey: string, tab: 'buy' | 'sell' = 'buy') => {
    const emptyBuy = emptySignalFlowLayout();
    const emptySell = emptySignalFlowLayout();
    setBuyLayout(normalizeTabLayoutState(emptyBuy));
    setSellLayout(normalizeTabLayoutState(emptySell));
    setBuyOrphans([]);
    setSellOrphans([]);
    saveStrategyFlowLayout(strategyKey, { buy: emptyBuy, sell: emptySell });
    bumpLayoutSeed(strategyKey, tab);
  }, [bumpLayoutSeed]);

  const applyStoredFlowLayout = useCallback((strategyKey: string, tab: 'buy' | 'sell' = 'buy') => {
    const stored = loadStrategyFlowLayout(strategyKey);
    if (stored) {
      const nextBuy = {
        positions: stored.buy.positions ?? {},
        edgeHandles: stored.buy.edgeHandles ?? {},
        startMeta: stored.buy.startMeta ?? defaultStartMeta(),
        extraStartIds: stored.buy.extraStartIds ?? [],
        extraRoots: stored.buy.extraRoots ?? {},
        startCombineOp: normalizeStartCombineOp(stored.buy.startCombineOp),
      };
      const nextSell = {
        positions: stored.sell.positions ?? {},
        edgeHandles: stored.sell.edgeHandles ?? {},
        startMeta: stored.sell.startMeta ?? defaultStartMeta(),
        extraStartIds: stored.sell.extraStartIds ?? [],
        extraRoots: stored.sell.extraRoots ?? {},
        startCombineOp: normalizeStartCombineOp(stored.sell.startCombineOp),
      };
      setBuyLayout(nextBuy);
      setSellLayout(nextSell);
      setBuyOrphans(stored.buy.orphans ?? []);
      setSellOrphans(stored.sell.orphans ?? []);
    } else {
      const emptyBuy = emptySignalFlowLayout();
      const emptySell = emptySignalFlowLayout();
      setBuyLayout(normalizeTabLayoutState(emptyBuy));
      setSellLayout(normalizeTabLayoutState(emptySell));
      setBuyOrphans([]);
      setSellOrphans([]);
    }
    bumpLayoutSeed(strategyKey, tab);
  }, [bumpLayoutSeed]);

  const handleLayoutChange = useCallback((snapshot: FlowLayoutChangePayload) => {
    const patch = {
      positions: snapshot.positions,
      edgeHandles: snapshot.edgeHandles,
    };
    if (snapshot.tab === 'buy') setBuyLayout(prev => ({ ...prev, ...patch }));
    else setSellLayout(prev => ({ ...prev, ...patch }));
    schedulePersistFlowLayout(layoutStrategyKey);
  }, [layoutStrategyKey, schedulePersistFlowLayout]);

  const handleStartMetaChange = useCallback((meta: Record<string, { candleType: string }>) => {
    setCurrentLayout(prev => ({ ...prev, startMeta: meta }));
    schedulePersistFlowLayout(layoutStrategyKey);
  }, [setCurrentLayout, layoutStrategyKey, schedulePersistFlowLayout]);

  const handleExtraStartIdsChange = useCallback((ids: string[]) => {
    setCurrentLayout(prev => ({ ...prev, extraStartIds: ids }));
    schedulePersistFlowLayout(layoutStrategyKey);
    bumpLayoutSeed(layoutStrategyKey, signalTab);
  }, [setCurrentLayout, layoutStrategyKey, schedulePersistFlowLayout, bumpLayoutSeed, signalTab]);

  const handleExtraRootsChange = useCallback((roots: Record<string, LogicNode | null>) => {
    setCurrentLayout(prev => ({ ...prev, extraRoots: roots }));
    schedulePersistFlowLayout(layoutStrategyKey);
  }, [setCurrentLayout, layoutStrategyKey, schedulePersistFlowLayout]);

  const handleEditorStateChange = useCallback((next: EditorConditionState) => {
    setCurrentRoot(next.root);
    setCurrentLayout(prev => ({
      ...prev,
      startMeta: next.startMeta,
      extraStartIds: next.extraStartIds,
      extraRoots: next.extraRoots,
      startCombineOp: normalizeStartCombineOp(next.startCombineOp),
    }));
    schedulePersistFlowLayout(layoutStrategyKey);
  }, [setCurrentRoot, setCurrentLayout, layoutStrategyKey, schedulePersistFlowLayout]);

  const handleStartCombineOpChange = useCallback((op: StartCombineOp) => {
    handleEditorStateChange(updateStartCombineOp(currentEditorState, op));
  }, [handleEditorStateChange, currentEditorState]);

  const switchSignalTab = useCallback((tab: 'buy' | 'sell') => {
    if (editorMode === 'graph') layoutFlushRef.current?.();
    persistFlowLayout(layoutStrategyKey);
    layoutRevisionRef.current += 1;
    setLayoutSeedKey(`${layoutStrategyKey}:${tab}:${layoutRevisionRef.current}`);
    setSignalTab(tab);
    setSelectedNodeId(null);
  }, [layoutStrategyKey, persistFlowLayout, editorMode]);

  const handleEditorModeChange = useCallback((mode: StrategyEditorMode) => {
    if (mode === editorMode) return;
    if (editorMode === 'graph') {
      layoutFlushRef.current?.();
      persistFlowLayout(layoutStrategyKey);
    }
    if (mode === 'list' && (buyOrphans.length > 0 || sellOrphans.length > 0)) {
      showSnack('목록 방식에서는 미연결(고아) 노드가 표시되지 않습니다', false);
    }
    setEditorMode(mode);
    saveEditorMode(mode);
    setSelectedNodeId(null);
  }, [editorMode, layoutStrategyKey, persistFlowLayout, buyOrphans.length, sellOrphans.length]);

  const layoutSeed = useMemo((): FlowLayoutSeed => {
    const source = signalTab === 'buy' ? buyLayout : sellLayout;
    return {
      seedKey: layoutSeedKey,
      positions: source.positions,
      edgeHandles: source.edgeHandles,
    };
  }, [signalTab, layoutSeedKey, buyLayout, sellLayout]);

  useEffect(() => () => {
    if (persistLayoutTimerRef.current != null) window.clearTimeout(persistLayoutTimerRef.current);
  }, []);

  useEffect(() => {
    if (!layoutPersistReadyRef.current) {
      layoutPersistReadyRef.current = true;
      return;
    }
    schedulePersistFlowLayout(layoutStrategyKey);
  }, [buyOrphans, sellOrphans, layoutStrategyKey, schedulePersistFlowLayout]);

  useEffect(() => { saveStratsLocal(strategies); }, [strategies]);

  useEffect(() => {
    loadStrategies().then(async list => {
      if (list?.length) {
        setStrategies(list.map(s => ({
          id: s.id ?? Date.now(),
          name: s.name,
          description: s.description,
          buyCondition: s.buyCondition as LogicNode | null ?? null,
          sellCondition: s.sellCondition as LogicNode | null ?? null,
          enabled: s.enabled ?? true,
          createdAt: s.createdAt,
          updatedAt: s.updatedAt,
        })));
      } else {
        const local = loadStratsLocal();
        if (!local.length) return;
        const migrated: StrategyDto[] = [];
        for (const s of local) {
          try {
            const saved = await saveStrategy({
              name: s.name,
              description: s.description,
              buyCondition: s.buyCondition,
              sellCondition: s.sellCondition,
              enabled: s.enabled ?? true,
            });
            if (saved?.id) {
              migrated.push({
                id: saved.id,
                name: saved.name,
                description: saved.description,
                buyCondition: saved.buyCondition as LogicNode | null ?? null,
                sellCondition: saved.sellCondition as LogicNode | null ?? null,
                enabled: saved.enabled ?? true,
                createdAt: saved.createdAt,
                updatedAt: saved.updatedAt,
              });
            }
          } catch { /* skip */ }
        }
        if (migrated.length) setStrategies(migrated);
      }
    }).catch(() => {});
  }, []);

  const handleSelectStrategy = (s: StrategyDto) => {
    const buyDecoded = decodeConditionForEditor(s.buyCondition ?? null);
    const sellDecoded = decodeConditionForEditor(s.sellCondition ?? null);
    const stored = loadStrategyFlowLayout(String(s.id));

    setSelectedId(s.id);
    setStratName(s.name);
    setStratDesc(s.description ?? '');
    setBuyCondition(buyDecoded.root);
    setSellCondition(sellDecoded.root);
    setBuyOrphans(stored?.buy.orphans ?? []);
    setSellOrphans(stored?.sell.orphans ?? []);
    setBuyLayout({
      positions: stored?.buy.positions ?? {},
      edgeHandles: stored?.buy.edgeHandles ?? {},
      startMeta: stored?.buy.startMeta ?? buyDecoded.startMeta,
      extraStartIds: stored?.buy.extraStartIds?.length ? stored.buy.extraStartIds : buyDecoded.extraStartIds,
      extraRoots: stored?.buy.extraRoots && Object.keys(stored.buy.extraRoots).length
        ? stored.buy.extraRoots
        : buyDecoded.extraRoots,
      startCombineOp: normalizeStartCombineOp(stored?.buy.startCombineOp ?? buyDecoded.startCombineOp),
    });
    setSellLayout({
      positions: stored?.sell.positions ?? {},
      edgeHandles: stored?.sell.edgeHandles ?? {},
      startMeta: stored?.sell.startMeta ?? sellDecoded.startMeta,
      extraStartIds: stored?.sell.extraStartIds?.length ? stored.sell.extraStartIds : sellDecoded.extraStartIds,
      extraRoots: stored?.sell.extraRoots && Object.keys(stored.sell.extraRoots).length
        ? stored.sell.extraRoots
        : sellDecoded.extraRoots,
      startCombineOp: normalizeStartCombineOp(stored?.sell.startCombineOp ?? sellDecoded.startCombineOp),
    });
    setSelectedNodeId(null);
    bumpLayoutSeed(String(s.id), signalTab);
  };

  const handleNew = () => {
    setSelectedId(null);
    setStratName('');
    setStratDesc('');
    setBuyCondition(null);
    setSellCondition(null);
    setSelectedNodeId(null);
    resetFlowLayout('draft', signalTab);
  };

  const handleSave = async () => {
    if (!stratName.trim()) { showSnack('전략 이름을 입력하세요', false); return; }
    const encodedBuy = encodeConditionForSave(buyEditorState);
    const encodedSell = encodeConditionForSave(sellEditorState);
    if (!encodedBuy && !encodedSell) { showSnack('조건을 최소 1개 추가하세요', false); return; }
    setIsSaving(true);
    try {
      const payload: ApiStrategyDto = {
        id: selectedId ?? undefined,
        name: stratName,
        description: stratDesc,
        buyCondition: encodedBuy,
        sellCondition: encodedSell,
        enabled: true,
      };
      const saved = await saveStrategy(payload);
      const now = new Date().toISOString();
      const dbId = saved?.id ?? selectedId ?? Date.now();
      const wasDraft = selectedId == null;
      setStrategies(prev => {
        const existing = prev.find(s => s.id === selectedId);
        if (existing && selectedId) {
          return prev.map(s => s.id === selectedId
            ? { ...s, id: dbId, name: stratName, description: stratDesc, buyCondition: encodedBuy, sellCondition: encodedSell, updatedAt: saved?.updatedAt ?? now }
            : s);
        }
        setSelectedId(dbId);
        return [...prev, { id: dbId, name: stratName, description: stratDesc, buyCondition: encodedBuy, sellCondition: encodedSell, enabled: true, createdAt: saved?.createdAt ?? now, updatedAt: saved?.updatedAt ?? now }];
      });
      if (!selectedId) setSelectedId(dbId);
      if (wasDraft) {
        migrateStrategyFlowLayout('draft', String(dbId));
      }
      persistFlowLayout(String(dbId));
      bumpLayoutSeed(String(dbId), signalTab);
      setSaveOpen(false);
      setSaveToast(true);
      setTimeout(() => setSaveToast(false), 2500);
      showSnack(selectedId ? '전략이 수정되었습니다' : '전략이 저장되었습니다');
    } catch (e) {
      showSnack(e instanceof Error ? e.message : '저장 실패', false);
    } finally {
      setIsSaving(false);
    }
  };

  const handleDeleteConfirm = async () => {
    if (!deleteId) return;
    try { await deleteStrategy(deleteId); } catch { /* local */ }
    deleteStrategyFlowLayout(String(deleteId));
    setStrategies(prev => prev.filter(s => s.id !== deleteId));
    if (selectedId === deleteId) handleNew();
    setDeleteOpen(false);
    setDeleteId(null);
    showSnack('전략이 삭제되었습니다');
  };

  const applyImportedFlowLayout = useCallback((layout: StrategyFlowLayoutStore | undefined, tab: 'buy' | 'sell' = signalTab) => {
    if (layout) {
      setBuyLayout({
        positions: layout.buy.positions ?? {},
        edgeHandles: layout.buy.edgeHandles ?? {},
        startMeta: layout.buy.startMeta ?? defaultStartMeta(),
        extraStartIds: layout.buy.extraStartIds ?? [],
        extraRoots: layout.buy.extraRoots ?? {},
        startCombineOp: normalizeStartCombineOp(layout.buy.startCombineOp),
      });
      setSellLayout({
        positions: layout.sell.positions ?? {},
        edgeHandles: layout.sell.edgeHandles ?? {},
        startMeta: layout.sell.startMeta ?? defaultStartMeta(),
        extraStartIds: layout.sell.extraStartIds ?? [],
        extraRoots: layout.sell.extraRoots ?? {},
        startCombineOp: normalizeStartCombineOp(layout.sell.startCombineOp),
      });
      setBuyOrphans(layout.buy.orphans ?? []);
      setSellOrphans(layout.sell.orphans ?? []);
      saveStrategyFlowLayout('draft', layout);
    } else {
      resetFlowLayout('draft', tab);
    }
    bumpLayoutSeed('draft', tab);
  }, [resetFlowLayout, bumpLayoutSeed, signalTab]);

  const handleExport = useCallback(() => {
    const encodedBuy = encodeConditionForSave(buyEditorState);
    const encodedSell = encodeConditionForSave(sellEditorState);
    if (!encodedBuy && !encodedSell) {
      showSnack('내보낼 조건이 없습니다', false);
      return;
    }
    if (editorMode === 'graph') layoutFlushRef.current?.();
    const payload = buildStrategyExportPayload({
      name: stratName,
      description: stratDesc,
      buyCondition: encodedBuy,
      sellCondition: encodedSell,
      flowLayout: {
        buy: { ...buyLayoutRef.current, orphans: buyOrphans },
        sell: { ...sellLayoutRef.current, orphans: sellOrphans },
      },
      editorMode,
    });
    const safeName = (stratName || '전략').replace(/[^\w\uAC00-\uD7A3-]+/g, '_');
    downloadStrategyJson(`${safeName}_${new Date().toISOString().slice(0, 10)}`, payload);
    showSnack('JSON으로 내보냈습니다');
  }, [buyEditorState, sellEditorState, stratName, stratDesc, buyOrphans, sellOrphans, editorMode]);

  const handleImport = useCallback(async () => {
    const text = await pickJsonFile();
    if (!text) return;
    try {
      const result = parseStrategyImportFile(text);
      if (result.kind === 'list') {
        showSnack('단일 전략 파일이 아닙니다. 전체 가져오기(⬆)를 사용하세요', false);
        return;
      }
      if (editorMode === 'graph') layoutFlushRef.current?.();
      const { data } = result;
      const buyDecoded = decodeConditionForEditor(data.buyCondition ?? null);
      const sellDecoded = decodeConditionForEditor(data.sellCondition ?? null);
      setBuyCondition(buyDecoded.root);
      setSellCondition(sellDecoded.root);
      setStratName(data.name ?? '가져온 전략');
      setStratDesc(data.description ?? '');
      setSelectedId(null);
      setSelectedNodeId(null);
      applyImportedFlowLayout(data.flowLayout);
      setBuyLayout(prev => ({
        ...prev,
        startCombineOp: normalizeStartCombineOp(data.flowLayout?.buy?.startCombineOp ?? buyDecoded.startCombineOp),
      }));
      setSellLayout(prev => ({
        ...prev,
        startCombineOp: normalizeStartCombineOp(data.flowLayout?.sell?.startCombineOp ?? sellDecoded.startCombineOp),
      }));
      if (data.editorMode === 'list' || data.editorMode === 'graph') {
        setEditorMode(data.editorMode);
        saveEditorMode(data.editorMode);
      }
      showSnack('전략을 가져왔습니다');
    } catch (e) {
      showSnack(e instanceof Error ? e.message : '파일 읽기 실패', false);
    }
  }, [editorMode, applyImportedFlowLayout]);

  const handleExportAll = useCallback(() => {
    if (strategies.length === 0) {
      showSnack('내보낼 전략이 없습니다', false);
      return;
    }
    const items = strategies.map(s => strategyDtoToListExportItem(
      s,
      loadStrategyFlowLayout(String(s.id)),
    ));
    downloadStrategyJson(
      `전략목록_${new Date().toISOString().slice(0, 10)}`,
      buildStrategyListExportPayload(items),
    );
    showSnack(`${strategies.length}개 전략을 내보냈습니다`);
  }, [strategies]);

  const handleImportAll = useCallback(async () => {
    const text = await pickJsonFile();
    if (!text) return;
    try {
      const result = parseStrategyImportFile(text);
      if (result.kind !== 'list') {
        showSnack('전략 목록 형식 파일이 필요합니다', false);
        return;
      }
      const baseId = Date.now();
      const imported = result.data.strategies.map((item, index) => {
        const id = baseId + index;
        if (item.flowLayout) saveStrategyFlowLayout(String(id), item.flowLayout);
        return listImportItemToStrategyDto(item, id);
      });
      setStrategies(prev => [...prev, ...imported]);
      showSnack(`${imported.length}개 전략을 가져왔습니다`);
    } catch (e) {
      showSnack(e instanceof Error ? e.message : '파일 읽기 실패', false);
    }
  }, []);

  const handleAddStartSection = useCallback(() => {
    handleEditorStateChange(addExtraStartSection(currentEditorState));
    bumpLayoutSeed(layoutStrategyKey, signalTab);
  }, [handleEditorStateChange, currentEditorState, bumpLayoutSeed, layoutStrategyKey, signalTab]);

  const applyPalette = useCallback((type: string, value: string, _label: string, composite = false) => {
    if (type === 'start') {
      handleAddStartSection();
      return;
    }
    const newNode = makeNode(type, value, signalTab, DEF, composite ? { composite: true } : undefined);
    const root = currentRoot;
    if (!root) setCurrentRoot(newNode);
    else if (type === 'operator') setCurrentRoot(mergeAtRoot(root, newNode, true));
    else setCurrentRoot(mergeAtRoot(root, newNode, false));
  }, [signalTab, DEF, currentRoot, setCurrentRoot, handleAddStartSection]);

  const templates = useMemo(() => getStrategyTemplates(DEF), [DEF]);

  const handleTemplate = useCallback((tmpl: StrategyTemplateDef) => {
    if (editorMode === 'graph') layoutFlushRef.current?.();

    const targetTab = tmpl.signal;
    const nextRoot = tmpl.kind === 'composite' ? tmpl.build() : simpleTemplateToNode(tmpl);

    setBuyCondition(null);
    setSellCondition(null);
    setSelectedNodeId(null);

    if (targetTab === 'buy') setBuyCondition(nextRoot);
    else setSellCondition(nextRoot);

    resetFlowLayout(layoutStrategyKey, targetTab);
    if (targetTab !== signalTab) setSignalTab(targetTab);

    showSnack(`"${tmpl.label}" 템플릿이 적용됐습니다`);
  }, [editorMode, layoutStrategyKey, resetFlowLayout, signalTab]);

  const operators = [
    { type: 'operator' as const, value: 'AND', label: 'AND', color: 'logic-and' },
    { type: 'operator' as const, value: 'OR', label: 'OR', color: 'logic-or' },
    { type: 'operator' as const, value: 'NOT', label: 'NOT', color: 'logic-not' },
  ];

  const maBandItems = [
    { type: 'indicator' as const, value: 'MA', label: 'MA', desc: '이동평균', color: 'band' },
    { type: 'indicator' as const, value: 'EMA', label: 'EMA', desc: '지수이동평균', color: 'band' },
    { type: 'indicator' as const, value: 'BOLLINGER', label: 'Bollinger', desc: '볼린저밴드', color: 'band' },
    { type: 'indicator' as const, value: 'DONCHIAN', label: 'Donchian', desc: '돈치안 채널', color: 'band' },
    { type: 'indicator' as const, value: 'ICHIMOKU', label: 'Ichimoku', desc: '일목균형표', color: 'band' },
  ];

  const indicatorItems = [
    { value: 'RSI', label: 'RSI', desc: '상대강도지수', color: 'ind' as const },
    { value: 'MACD', label: 'MACD', desc: '이동평균 수렴확산', color: 'ind' as const },
    { value: 'STOCHASTIC', label: 'Stochastic', desc: '스토캐스틱', color: 'ind' as const },
    { value: 'CCI', label: 'CCI', desc: '상품채널지수', color: 'ind' as const },
    { value: 'ADX', label: 'ADX', desc: '평균방향지수', color: 'ind' as const },
    { value: 'DMI', label: 'DMI', desc: '방향성 지표', color: 'ind' as const },
    { value: 'OBV', label: 'OBV', desc: '거래량 균형', color: 'ind' as const },
    { value: 'WILLIAMS_R', label: 'Williams %R', desc: '윌리엄스 %R', color: 'ind' as const },
    { value: 'TRIX', label: 'TRIX', desc: '삼중지수이동평균', color: 'ind' as const },
    { value: 'VOLUME_OSC', label: 'Volume OSC', desc: '거래량 오실레이터', color: 'ind' as const },
    { value: 'VR', label: 'VR', desc: '거래량 비율', color: 'ind' as const },
    { value: 'DISPARITY', label: '이격도', desc: 'Disparity', color: 'ind' as const },
    { value: 'PSYCHOLOGICAL', label: '심리도', desc: 'Psychological', color: 'ind' as const },
    { value: 'INVEST_PSYCHOLOGICAL', label: '투자심리도', desc: 'Invest PSY', color: 'ind' as const },
    { value: 'VOLUME', label: '거래량', desc: 'Volume', color: 'ind' as const },
  ];

  const q = paletteSearch.trim().toLowerCase();
  const match = (label: string, desc?: string) =>
    !q || label.toLowerCase().includes(q) || (desc?.toLowerCase().includes(q) ?? false);

  const paletteKey = (type: 'operator' | 'indicator' | 'composite', value: string) => `${type}:${value}`;
  const selectPalette = (type: 'operator' | 'indicator' | 'composite', value: string) => {
    setSelectedPaletteKey(paletteKey(type, value));
  };
  const isPaletteSelected = (type: 'operator' | 'indicator' | 'composite', value: string) =>
    selectedPaletteKey === paletteKey(type, value);

  return (
    <div className={`se-page se-page--${theme}`}>
      {saveToast && (
        <div className="se-save-toast">전략이 DB에 성공적으로 저장되었습니다.</div>
      )}

      <header className="se-header">
        <div className="se-header-left">
          <h1 className="se-title">전략 빌더</h1>
          <span className="se-subtitle">Strategy Builder</span>
        </div>
        <div className="se-header-actions">
          <div className="se-editor-mode" role="group" aria-label="편집 방식">
            <button
              type="button"
              className={`se-editor-mode-btn${editorMode === 'graph' ? ' se-editor-mode-btn--on' : ''}`}
              onClick={() => handleEditorModeChange('graph')}
            >
              그래프 방식
            </button>
            <button
              type="button"
              className={`se-editor-mode-btn${editorMode === 'list' ? ' se-editor-mode-btn--on' : ''}`}
              onClick={() => handleEditorModeChange('list')}
            >
              목록 방식
            </button>
          </div>
          <button type="button" className="se-btn se-btn--ghost" onClick={handleNew}>＋ 새 전략</button>
          <button
            type="button"
            className="se-btn se-btn--ghost se-btn--icon se-btn--desc"
            title="전략 설명 — 현재 조건을 서술형으로 보기"
            aria-label="전략 설명"
            onClick={() => setDescOpen(true)}
          >
            <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden className="se-desc-icon">
              <circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" strokeWidth="1.75" />
              <path
                fill="currentColor"
                d="M11.25 10.5h1.5V17h-1.5V10.5zm0-3.25h1.5V9h-1.5V6.25z"
              />
            </svg>
          </button>
          <button
            type="button"
            className="se-btn se-btn--ghost se-btn--icon"
            title="현재 전략 JSON 내보내기"
            onClick={handleExport}
            disabled={!buyCondition && !sellCondition}
          >
            ↓
          </button>
          <button
            type="button"
            className="se-btn se-btn--ghost se-btn--icon"
            title="JSON 파일에서 전략 가져오기"
            onClick={() => { void handleImport(); }}
          >
            ↑
          </button>
          <button
            type="button"
            className="se-btn se-btn--ghost se-btn--icon"
            title="전체 전략 목록 JSON 내보내기"
            onClick={handleExportAll}
            disabled={strategies.length === 0}
          >
            ⬇
          </button>
          <button
            type="button"
            className="se-btn se-btn--ghost se-btn--icon"
            title="전체 전략 목록 JSON 가져오기"
            onClick={() => { void handleImportAll(); }}
          >
            ⬆
          </button>
          <button type="button" className="se-btn se-btn--gold" onClick={() => setSaveOpen(true)}>저장하기</button>
        </div>
      </header>

      <div className="se-body" style={bodyStyle}>
        <aside className="se-left" style={{ width: leftWidth }}>
          <div className="se-strat-panel">
            <div className="se-strat-panel-head">
              <h2 className="se-panel-title">전략 목록</h2>
            </div>
            <button type="button" className="se-new-strat-btn" onClick={handleNew}>
              ＋ 새 전략 만들기
            </button>
            <div className="se-strat-list">
              {strategies.length === 0 ? (
                <p className="se-empty">저장된 전략이 없습니다</p>
              ) : (
                strategies.map(s => {
                  const isSel = selectedId === s.id;
                  return (
                    <div
                      key={s.id}
                      role="button"
                      tabIndex={0}
                      className={`se-strat-item${isSel ? ' se-strat-item--sel' : ''}`}
                      onClick={() => handleSelectStrategy(s)}
                      onKeyDown={e => {
                        if (e.key === 'Enter' || e.key === ' ') {
                          e.preventDefault();
                          handleSelectStrategy(s);
                        }
                      }}
                    >
                      <span className="se-strat-name" title={s.name}>{s.name}</span>
                      <div className="se-strat-item-actions">
                        <button
                          type="button"
                          className="se-strat-del"
                          title="전략 삭제"
                          onClick={e => {
                            e.stopPropagation();
                            setDeleteId(s.id);
                            setDeleteOpen(true);
                          }}
                        >
                          <svg viewBox="0 0 16 16" width="17" height="17" aria-hidden>
                            <path
                              fill="currentColor"
                              d="M5.5 2a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v.5H12a.5.5 0 0 1 0 1h-.55l-.62 8.07A1.5 1.5 0 0 1 9.83 13H6.17a1.5 1.5 0 0 1-1.49-1.43L4.05 3.5H4a.5.5 0 0 1 0-1h1.5V2zm1.5 0v.5h2V2H7zm-2.38 1.5l.58 7.53a.5.5 0 0 0 .5.47h3.66a.5.5 0 0 0 .5-.47l.58-7.53H4.62z"
                            />
                          </svg>
                        </button>
                        <span className={`se-strat-status${s.enabled ? ' se-strat-status--on' : ''}`}>
                          {s.enabled ? '활성' : '비활성'}
                        </span>
                      </div>
                    </div>
                  );
                })
              )}
            </div>
          </div>
        </aside>

        <div
          className="se-splitter se-splitter--v"
          role="separator"
          aria-orientation="vertical"
          aria-label="전략 목록 너비 조절"
          onPointerDown={onLeftSplitter}
        />

        <div className="se-main">
          <div className="se-main-row">
            <main className="se-center">
          <div className="se-center-panel">
            <div className={`se-center-work${editorMode === 'list' ? ' se-center-work--list' : ''}`}>
            <div className="se-center-head">
            <div className="se-signal-tabs">
              <button
                type="button"
                className={`se-signal-tab${signalTab === 'buy' ? ' se-signal-tab--buy-on' : ''}`}
                onClick={() => switchSignalTab('buy')}
              >
                매수 조건 (Entry)
              </button>
              <button
                type="button"
                className={`se-signal-tab${signalTab === 'sell' ? ' se-signal-tab--sell-on' : ''}`}
                onClick={() => switchSignalTab('sell')}
              >
                매도 조건 (Exit)
              </button>
            </div>
            {stratName && <span className="se-editing-name">{stratName}</span>}
            {hasMultipleStartSections(currentEditorState) && (
              <StartCombineOpControl
                value={normalizeStartCombineOp(currentEditorState.startCombineOp)}
                onChange={handleStartCombineOpChange}
              />
            )}
            </div>

          {editorMode === 'graph' ? (
            <>
              <ReactFlowProvider>
                <StrategyEditorCanvas
                  theme={theme}
                  root={currentRoot}
                  orphans={currentOrphans}
                  onOrphansChange={setCurrentOrphans}
                  def={DEF}
                  signalTab={signalTab}
                  onChange={setCurrentRoot}
                  selectedNodeId={selectedNodeId}
                  onSelectNode={setSelectedNodeId}
                  layoutSeed={layoutSeed}
                  onLayoutChange={handleLayoutChange}
                  startMeta={currentLayout.startMeta}
                  extraStartIds={currentLayout.extraStartIds}
                  extraRoots={currentLayout.extraRoots}
                  onStartMetaChange={handleStartMetaChange}
                  onExtraStartIdsChange={handleExtraStartIdsChange}
                  onExtraRootsChange={handleExtraRootsChange}
                />
              </ReactFlowProvider>

              {selectedLogicNode?.type === 'CONDITION' && selectedLogicNode.condition && (
                <div className="se-node-config-bar">
                  <span className="se-node-config-label">{selectedLogicNode.condition.indicatorType}</span>
                  <CondEditor
                    cond={selectedLogicNode.condition}
                    signalType={signalTab}
                    def={DEF}
                    onChange={c => {
                      if (!selectedNodeId) return;
                      const inOrphans = currentOrphans.some(o => o.id === selectedNodeId);
                      if (inOrphans) {
                        setCurrentOrphans(currentOrphans.map(o => (
                          o.id === selectedNodeId ? updateNode(o, selectedNodeId, n => ({ ...n, condition: c })) : o
                        )));
                        return;
                      }
                      if (currentRoot && findLogicNode(currentRoot, currentOrphans, selectedNodeId, currentLayout.extraRoots ?? {})) {
                        if (findLogicNode(currentRoot, [], selectedNodeId)) {
                          setCurrentRoot(updateNode(currentRoot, selectedNodeId, n => ({ ...n, condition: c })));
                          return;
                        }
                        for (const [startId, branch] of Object.entries(currentLayout.extraRoots ?? {})) {
                          if (branch && findLogicNode(branch, [], selectedNodeId)) {
                            handleExtraRootsChange({
                              ...(currentLayout.extraRoots ?? {}),
                              [startId]: updateNode(branch, selectedNodeId, n => ({ ...n, condition: c })),
                            });
                            return;
                          }
                        }
                      }
                    }}
                  />
                  <span className="se-sync-tip">전략 조건 전용 설정 · 차트 보조지표와 무관</span>
                </div>
              )}
            </>
          ) : (
            <StrategyListEditor
              editorState={currentEditorState}
              signalTab={signalTab}
              def={DEF}
              onEditorStateChange={handleEditorStateChange}
              onAddStart={handleAddStartSection}
              orphans={currentOrphans}
              onOrphansChange={setCurrentOrphans}
            />
          )}

          </div>

          <div
            className="se-splitter se-splitter--h"
            role="separator"
            aria-orientation="horizontal"
            aria-label="Logic Expression 높이 조절"
            onPointerDown={onTerminalSplitter}
          />

          <footer className="se-terminal" style={{ height: terminalHeight }}>
            <div className="se-terminal-label">LOGIC EXPRESSION</div>
            <LogicExpressionPreview
              buyCondition={buyCondition}
              sellCondition={sellCondition}
              buyEditorState={buyEditorState}
              sellEditorState={sellEditorState}
              orphanCount={orphanTotal}
              def={DEF}
            />
          </footer>
          </div>
        </main>

        <aside className="se-right">
          <div className="se-palette-panel">
          <div className="se-right-tabs">
            <button type="button" className={rightTab === 'indicators' ? 'se-right-tab se-right-tab--on' : 'se-right-tab'} onClick={() => setRightTab('indicators')}>지표</button>
            <button type="button" className={rightTab === 'templates' ? 'se-right-tab se-right-tab--on' : 'se-right-tab'} onClick={() => setRightTab('templates')}>템플릿</button>
          </div>
          <div className="se-right-body">
          {rightTab === 'indicators' && (
            <>
              <input
                className="se-palette-search"
                placeholder="지표 검색 (RSI, MACD…)"
                value={paletteSearch}
                onChange={e => setPaletteSearch(e.target.value)}
              />
              <div className="se-palette-section se-palette-section--logic">
                <h3>시작 · 논리</h3>
                <div className="se-palette-grid se-palette-grid--3">
                  <PaletteChip
                    type="start"
                    value="START"
                    label="START"
                    desc="시간봉 시작점"
                    color="logic-start"
                    selected={selectedPaletteKey === 'start:START'}
                    onSelect={() => setSelectedPaletteKey('start:START')}
                    onAdd={() => applyPalette('start', 'START', 'START')}
                  />
                  {operators.map(op => (
                    <PaletteChip
                      key={op.value}
                      {...op}
                      selected={isPaletteSelected(op.type, op.value)}
                      onSelect={() => selectPalette(op.type, op.value)}
                      onAdd={() => applyPalette(op.type, op.value, op.label)}
                    />
                  ))}
                </div>
              </div>
              <div className="se-palette-section se-palette-section--band">
                <h3>밴드 · 추세</h3>
                <div className="se-palette-grid se-palette-grid--3">
                  {maBandItems.filter(i => match(i.label, i.desc)).map(item => (
                    <PaletteChip
                      key={item.value}
                      {...item}
                      period={getIndicatorPeriodLabel(item.value, DEF)}
                      selected={isPaletteSelected(item.type, item.value)}
                      onSelect={() => selectPalette(item.type, item.value)}
                      onAdd={() => applyPalette(item.type, item.value, item.label)}
                    />
                  ))}
                </div>
              </div>
              <div className="se-palette-section se-palette-section--aux">
                <h3>보조지표</h3>
                <div className="se-palette-grid se-palette-grid--3">
                  {indicatorItems.filter(i => match(i.label, i.desc)).map(item => (
                    <PaletteChip
                      key={item.value}
                      type="indicator"
                      value={item.value}
                      label={item.label}
                      desc={item.desc}
                      color={item.color}
                      period={getIndicatorPeriodLabel(item.value, DEF)}
                      selected={isPaletteSelected('indicator', item.value)}
                      onSelect={() => selectPalette('indicator', item.value)}
                      onAdd={() => applyPalette('indicator', item.value, item.label)}
                    />
                  ))}
                </div>
              </div>
              <div className="se-palette-section se-palette-section--composite se-palette-section--scroll">
                <h3>복합지표</h3>
                <div className="se-palette-grid se-palette-grid--3">
                  {COMPOSITE_INDICATOR_ITEMS.filter(i => match(i.label, i.desc)).map(item => (
                    <PaletteChip
                      key={`composite-${item.value}`}
                      type="indicator"
                      value={item.value}
                      label={item.label}
                      desc={item.desc}
                      color="composite"
                      composite
                      period={compositePeriodLabel(item.value, DEF)}
                      selected={isPaletteSelected('composite', item.value)}
                      onSelect={() => selectPalette('composite', item.value)}
                      onAdd={() => applyPalette('indicator', item.value, item.label, true)}
                    />
                  ))}
                </div>
              </div>
            </>
          )}
          {rightTab === 'templates' && (
            <div className="se-template-list">
              {templates.map((t, i) => (
                <button key={i} type="button" className="se-template-item" onClick={() => handleTemplate(t)}>
                  <span className="se-template-label">{t.label}</span>
                  {'description' in t && t.description && (
                    <span className="se-template-desc">{t.description}</span>
                  )}
                  <span className={`se-template-signal se-template-signal--${t.signal}`}>{t.signal === 'buy' ? '매수' : '매도'}</span>
                </button>
              ))}
            </div>
          )}
          </div>
          </div>
        </aside>
          </div>
        </div>
      </div>

      {saveOpen && (
        <DraggableModalFrame onClose={() => setSaveOpen(false)} title="전략 저장">
          <div className="se-modal-body">
            <label className="se-field-lbl">전략명 *</label>
            <input className="se-field-inp" value={stratName} onChange={e => setStratName(e.target.value)} placeholder="예: Golden_RSI_V1" />
            <label className="se-field-lbl">설명</label>
            <textarea className="se-field-ta" value={stratDesc} onChange={e => setStratDesc(e.target.value)} rows={2} />
            <div className="se-modal-actions">
              <button type="button" className="se-btn se-btn--ghost" onClick={() => setSaveOpen(false)}>취소</button>
              <button type="button" className="se-btn se-btn--gold" disabled={isSaving} onClick={handleSave}>{isSaving ? '저장 중…' : '저장'}</button>
            </div>
          </div>
        </DraggableModalFrame>
      )}

      {deleteOpen && (
        <DraggableModalFrame onClose={() => setDeleteOpen(false)} title="전략 삭제">
          <p>이 전략을 삭제하시겠습니까?</p>
          <div className="se-modal-actions">
            <button type="button" className="se-btn se-btn--ghost" onClick={() => setDeleteOpen(false)}>취소</button>
            <button type="button" className="se-btn se-btn--danger" onClick={handleDeleteConfirm}>삭제</button>
          </div>
        </DraggableModalFrame>
      )}

      {descOpen && (
        <StrategyDescriptionModal
          onClose={() => setDescOpen(false)}
          name={stratName}
          description={stratDesc}
          buyEditorState={buyEditorState}
          sellEditorState={sellEditorState}
          buyCondition={buyCondition}
          sellCondition={sellCondition}
          orphanCount={orphanTotal}
          def={DEF}
        />
      )}

      {snack && <div className={`se-snack${snack.ok ? '' : ' se-snack--err'}`}>{snack.msg}</div>}
    </div>
  );
}
