import '../styles/paperDashboard.css';
import '../styles/tradeRightPanel.css';
import React from 'react';
import DrawingToolbar from '../components/DrawingToolbar';
import BottomBar from '../components/BottomBar';
import TradingChart from '../components/TradingChart';
import StatsPanel from '../components/StatsPanel';
import WatchList from '../components/WatchList';
import AlertManager from '../components/AlertManager';
import ObjectTree from '../components/ObjectTree';
import IndicatorContextToolbar from '../components/IndicatorContextToolbar';
import IndicatorSettingsModal from '../components/IndicatorSettingsModal';
import IndicatorBulkSettingsModal from '../components/IndicatorBulkSettingsModal';
import MainChartSettingsModal from '../components/MainChartSettingsModal';
import DrawingContextToolbar from '../components/DrawingContextToolbar';
import DrawingSettingsModal from '../components/DrawingSettingsModal';
import FibTZSettingsModal from '../components/FibTZSettingsModal';
import ChartSlot, { type ChartSlotHandle } from '../components/ChartSlot';
import { type LayoutDef, type SyncOptions } from '../utils/layoutTypes';
import type {
  OHLCVBar, ChartType, Theme, Timeframe, IndicatorConfig, LegendData, Drawing,
  ChartMode, MainChartStyle, TradeOrderFillRequest,
} from '../types';
import type { ChartManager } from '../utils/ChartManager';
import { LOAD_MORE_TRIGGER } from '../hooks/useHistoryLoader';
import type { WsStatus } from '../hooks/useChartRealtimeData';
import type { TickerData } from '../hooks/useMarketTicker';
import type { OrderbookState } from '../hooks/useUpbitOrderbook';
import type { BacktestStats } from '../utils/backendApi';
import type { BacktestAnalysisReportModel } from '../components/backtest/BacktestAnalysisReportModal';
import BacktestAnalysisReportModal from '../components/backtest/BacktestAnalysisReportModal';
import { type MenuPage } from '../components/TopMenuBar';
import BacktestPanel from '../components/BacktestPanel';
import LiveStrategyPanel from '../components/LiveStrategyPanel';
import BacktestSettingsModal from '../components/BacktestSettingsModal';
import MarketPanel from '../components/MarketPanel';
import ChartLegendBar from '../components/ChartLegendBar';
import RightSidePanel from '../components/RightSidePanel';
import ChartCustomOverlaySettingsModal from '../components/ChartCustomOverlaySettingsModal';
import MobileChartDock from '../components/MobileChartDock';
import { ChartToolbarNotify } from '../components/NotifyUiBindings';
import {
  buildMainIndicatorConfig,
  indicatorSettingsTemplateId,
  indicatorTypeFromSettingsId,
  isIndicatorSettingsTemplateId,
} from '../utils/indicatorMainConfig';
import { initializeIndicatorConfigForEditor } from '../utils/indicatorSettingsEditor';
import type { PriceStats } from '../utils/calculations';
import type { ChartOverlayToggleKey, ChartOverlayVisibility } from '../utils/chartOverlayVisibility';
import type { ChartCustomOverlaySelection, ChartCustomOverlaySlotId } from '../utils/chartCustomOverlay';
import type { ChartLegendVisibility } from '../types/chartLegend';
import type { ChartPaneSeparatorOptions } from '../types/chartPaneSeparator';
import type { LiveStrategySettingsDto } from '../utils/backendApi';
import type { BacktestSettingsDto } from '../utils/backendApi';
import { addWatchlistItem, removeWatchlistItem } from '../utils/backendApi';
import { fmtListTimestamp } from '../utils/backtestUiUtils';

export interface ChartWorkspaceViewProps {
  onOpenNotificationsPage: () => void;
  symbol: string;
  timeframe: Timeframe;
  chartType: ChartType;
  theme: Theme;
  mode: ChartMode;
  logScale: boolean;
  showGrid: boolean;
  toolbarActiveIndicators: IndicatorConfig[];
  layoutDef: LayoutDef;
  syncOptions: SyncOptions;
  activeSlot: number;
  activeSlotSymbol: string;
  activeSlotTf: Timeframe;
  slot0InitProps: { symbol: string; timeframe: Timeframe; indicators: IndicatorConfig[] } | null;
  slotInitProps: Record<number, {
    symbol?: string;
    timeframe?: Timeframe;
    indicators?: IndicatorConfig[];
    mainChartStyle?: MainChartStyle;
  }>;
  syncTime: number;
  syncVisibleRange: { from: number; to: number } | null;
  syncLogicalRange: { from: number; to: number } | null;
  slotRefs: React.MutableRefObject<(ChartSlotHandle | null)[]>;
  managerRef: React.MutableRefObject<ChartManager | null>;
  undoStack: React.MutableRefObject<Drawing[][]>;
  redoStack: React.MutableRefObject<Drawing[][]>;
  canUndo: boolean;
  canRedo: boolean;
  drawingTool: string;
  drawings: Drawing[];
  drawingsLocked: boolean;
  drawingsVisible: boolean;
  indicatorsVisible: boolean;
  magnetMode: 'off' | 'weak' | 'strong';
  snapToIndicator: boolean;
  keepLockedOnDelete: boolean;
  legend: LegendData | null;
  showStats: boolean;
  showWatch: boolean;
  showAlert: boolean;
  showObjectTree: boolean;
  percentScale: boolean;
  magnifierActive: boolean;
  showMarketPanel: boolean;
  mobileRightOpen: boolean;
  mobileRightTab: 'trade' | 'orderbook';
  marketTickers: Map<string, TickerData>;
  marketInfos: import('../hooks/useMarketTicker').MarketInfo[];
  marketLoading: boolean;
  usdRate: number;
  refreshAllTickers: () => Promise<void>;
  setMarketSearchOpen: React.Dispatch<React.SetStateAction<boolean>>;
  formalLogin: boolean;
  requireFormalLogin: () => void;
  chartVisible: boolean;
  menuPage: MenuPage;
  isMobile: boolean;
  indicators: IndicatorConfig[];
  effectiveIndicators: IndicatorConfig[];
  mainChartStyle: MainChartStyle;
  showMainChartModal: boolean;
  showBulkIndSettings: boolean;
  settingsModalId: string | null;
  ctxToolbar: { entryId: string; screenX: number; screenY: number } | null;
  drawingCtxToolbar: { id: string; screenX: number; screenY: number } | null;
  drawingSettingsId: string | null;
  selectedDrawingId: string | null;
  focusedIndicatorId: string | null;
  overlayVisibility: ChartOverlayVisibility;
  overlayVisibilityRef: React.MutableRefObject<ChartOverlayVisibility>;
  candleOverlayToggles: ReturnType<typeof import('../utils/chartOverlayVisibility').listChartOverlayToggleItems>;
  customOverlaySelection: ChartCustomOverlaySelection;
  custom1OverlaySelection: ChartCustomOverlaySelection;
  activeCustomOverlaySlot: ChartCustomOverlaySlotId | null;
  customOverlaySettingsSlot: ChartCustomOverlaySlotId | null;
  customOverlayActive: boolean;
  custom1OverlayActive: boolean;
  chartCandleAreaPriceLabels: boolean;
  chartSeriesPriceLabels: boolean;
  chartVolumeVisible: boolean;
  chartPriceExtremeLabelsVisible: boolean;
  chartPaneSeparator: ChartPaneSeparatorOptions;
  chartCrosshairInfoVisible: boolean;
  chartHoverToolbarVisible: boolean;
  chartLiveReceiveHighlight: boolean;
  chartRealtimeSource: 'BACKEND_STOMP' | 'UPBIT_DIRECT';
  displayTimezone: string;
  chartTimeFormat: string;
  chartLegendOptions: ChartLegendVisibility;
  useUpbit: boolean;
  wsStatus: WsStatus;
  bars: OHLCVBar[];
  barsMarket: string;
  barsMarketRef: React.MutableRefObject<string | null>;
  chartMountKey: number;
  chartLiveReadyRef: React.MutableRefObject<boolean>;
  pendingRealtimeBarRef: React.MutableRefObject<OHLCVBar | null>;
  isSingleLoadingMore: boolean;
  singleLoadMoreRef: React.MutableRefObject<() => void>;
  btMatchesChart: boolean;
  btRunning: boolean;
  btSettings: BacktestSettingsDto;
  btStats: BacktestStats | null;
  btSignals: import('../utils/backendApi').BacktestSignal[];
  btClear: () => void;
  btMarkerPayloadRef: React.MutableRefObject<{
    symbol: string;
    timeframe: string;
    signals: import('../utils/backendApi').BacktestSignal[];
    showPrice: boolean;
  } | null>;
  syncBacktestMarkersRef: React.MutableRefObject<() => void>;
  showBtSettings: boolean;
  showBtResult: boolean;
  showLivePanel: boolean;
  liveStrategies: { id: number; name: string }[];
  liveStrategySettings: LiveStrategySettingsDto;
  monitoredMarkets: string[];
  isVirtualActive: boolean;
  watchlist: string[];
  chartBtReportModel: BacktestAnalysisReportModel | null;
  chartSymbol: string;
  chartScreenActive: boolean;
  orderbook: OrderbookState;
  obWsStatus: WsStatus;
  spread: { bestAsk: number; bestBid: number; spread: number; pct: number };
  orderbookPrevClose: number;
  chartTicker: TickerData | undefined;
  tradeFill: TradeOrderFillRequest | null;
  paperCashBalance: number;
  paperCoinQty: number;
  paperTradingEnabled: boolean;
  paperAutoTradeEnabled: boolean;
  appDefaults: import('../utils/backendApi').AppSettingsDto;
  stats: PriceStats | null;
  priceMap: Record<string, { price: number; changePct: number }>;
  alerts: Array<{ price: number; label: string }>;
  hoverBar: LegendData | OHLCVBar | null;
  quoteBar: OHLCVBar | null;
  liveQuote: ReturnType<typeof import('../utils/chartLiveQuote').resolveChartLiveQuote>;
  isLoading: boolean;
  error: string | null;
  chartConfigReady: boolean;
  chartAreaLoading: boolean;
  chartDataLoading: boolean;
  bulkSettingsIndicators: IndicatorConfig[];
  chartIndicatorsForSettings: IndicatorConfig[];
  getParams: ReturnType<typeof import('../hooks/useIndicatorSettings').useIndicatorSettings>['getParams'];
  getVisualConfig: ReturnType<typeof import('../hooks/useIndicatorSettings').useIndicatorSettings>['getVisualConfig'];
  activeSlotIndicators: IndicatorConfig[];
  setSymbol: React.Dispatch<React.SetStateAction<string>>;
  setLegend: React.Dispatch<React.SetStateAction<LegendData | null>>;
  setChartType: React.Dispatch<React.SetStateAction<ChartType>>;
  setMode: React.Dispatch<React.SetStateAction<ChartMode>>;
  setLogScale: React.Dispatch<React.SetStateAction<boolean>>;
  setShowGrid: React.Dispatch<React.SetStateAction<boolean>>;
  setDrawingTool: React.Dispatch<React.SetStateAction<string>>;
  setDrawings: React.Dispatch<React.SetStateAction<Drawing[]>>;
  setDrawingsLocked: React.Dispatch<React.SetStateAction<boolean>>;
  setDrawingsVisible: React.Dispatch<React.SetStateAction<boolean>>;
  setIndicatorsVisible: React.Dispatch<React.SetStateAction<boolean>>;
  setMagnetMode: React.Dispatch<React.SetStateAction<'off' | 'weak' | 'strong'>>;
  setSnapToIndicator: React.Dispatch<React.SetStateAction<boolean>>;
  setKeepLockedOnDelete: React.Dispatch<React.SetStateAction<boolean>>;
  setShowStats: React.Dispatch<React.SetStateAction<boolean>>;
  setShowWatch: React.Dispatch<React.SetStateAction<boolean>>;
  setShowAlert: React.Dispatch<React.SetStateAction<boolean>>;
  setShowObjectTree: React.Dispatch<React.SetStateAction<boolean>>;
  setMagnifierActive: React.Dispatch<React.SetStateAction<boolean>>;
  setShowMarketPanel: React.Dispatch<React.SetStateAction<boolean>>;
  setMobileRightOpen: React.Dispatch<React.SetStateAction<boolean>>;
  setMobileRightTab: React.Dispatch<React.SetStateAction<'trade' | 'orderbook'>>;
  setSyncTime: React.Dispatch<React.SetStateAction<number>>;
  setSyncVisibleRange: React.Dispatch<React.SetStateAction<{ from: number; to: number } | null>>;
  setSyncLogicalRange: React.Dispatch<React.SetStateAction<{ from: number; to: number } | null>>;
  setActiveSlotSymbol: React.Dispatch<React.SetStateAction<string>>;
  setCanUndo: React.Dispatch<React.SetStateAction<boolean>>;
  setCanRedo: React.Dispatch<React.SetStateAction<boolean>>;
  setCtxToolbar: React.Dispatch<React.SetStateAction<{ entryId: string; screenX: number; screenY: number } | null>>;
  setDrawingCtxToolbar: React.Dispatch<React.SetStateAction<{ id: string; screenX: number; screenY: number } | null>>;
  setDrawingSettingsId: React.Dispatch<React.SetStateAction<string | null>>;
  setSelectedDrawingId: React.Dispatch<React.SetStateAction<string | null>>;
  setShowMainChartModal: React.Dispatch<React.SetStateAction<boolean>>;
  setShowBulkIndSettings: React.Dispatch<React.SetStateAction<boolean>>;
  setSettingsModalId: React.Dispatch<React.SetStateAction<string | null>>;
  setCustomOverlaySettingsSlot: React.Dispatch<React.SetStateAction<ChartCustomOverlaySlotId | null>>;
  setShowBtSettings: React.Dispatch<React.SetStateAction<boolean>>;
  setShowBtResult: React.Dispatch<React.SetStateAction<boolean>>;
  setShowLivePanel: React.Dispatch<React.SetStateAction<boolean>>;
  setWatchlist: React.Dispatch<React.SetStateAction<string[]>>;
  setAlerts: React.Dispatch<React.SetStateAction<Array<{ price: number; label: string }>>>;
  setIndicators: React.Dispatch<React.SetStateAction<IndicatorConfig[]>>;
  setSyncOptions: React.Dispatch<React.SetStateAction<SyncOptions>>;
  setPercentScale: React.Dispatch<React.SetStateAction<boolean>>;
  setBtStrategyName: React.Dispatch<React.SetStateAction<string>>;
  setBtReportCreatedAt: React.Dispatch<React.SetStateAction<string | undefined>>;
  setBtSettings: React.Dispatch<React.SetStateAction<BacktestSettingsDto>>;
  saveAppDef: (patch: Partial<import('../utils/backendApi').AppSettingsDto>) => void;
  applyChartIndicators: (chartIndicators: IndicatorConfig[]) => void;
  onThemeToggle: () => void;
  handleTimeframe: (tf: Timeframe) => void;
  handleLayoutSelect: (def: LayoutDef) => void;
  handleSetActiveSlot: (idx: number) => void;
  handleActiveSlotIndicatorsChange: (inds: IndicatorConfig[]) => void;
  handleActiveSlotSymbolChange: (sym: string) => void;
  handleVisibleRangeChange: (r: { from: number; to: number }) => void;
  handleLogicalRangeChange: (r: { from: number; to: number }) => void;
  handleSlotFullView: (slotSymbol: string, slotIdx: number) => void;
  applyTradeFill: (req: { market: string; price: number; side: 'buy' | 'sell' }, slotIndex?: number) => void;
  handleOrderbookPick: (price: number, rowType: 'ask' | 'bid') => void;
  handleTradeMarketSelect: (s: string) => void;
  pushDrawing: (newDrawings: Drawing[]) => void;
  handleUndo: () => void;
  handleRedo: () => void;
  handleHoverPaneLegend: (id: string, sx: number, sy: number) => void;
  handleLeavePaneLegend: () => void;
  clearHoverTimer: () => void;
  handleDrawingClick: (id: string | null, sx: number, sy: number) => void;
  handleDrawingDoubleClick: (id: string, sx: number, sy: number) => void;
  handleMainChartSave: (style: MainChartStyle) => void;
  handleAddIndicator: (type: string, fromConfig?: IndicatorConfig) => void;
  handleAddIndicators: (types: string[]) => void;
  handleRemoveByType: (type: string) => void;
  handleIndicatorListOrderChange: (orderedTypes: string[]) => void;
  handleOpenIndicatorSettings: (type: string) => void;
  handleIndicatorSave: (updated: IndicatorConfig) => void;
  handleBulkIndicatorApply: (result: { chartIndicators: IndicatorConfig[]; allConfigs: IndicatorConfig[] }) => void;
  handleToggleIndicatorHidden: (id: string) => void;
  handleDuplicateIndicator: (sourceId: string) => void;
  handleRemoveIndicatorById: (id: string) => void;
  handleScreenshot: () => void;
  handleReorderIndicators: (fromId: string, insertBeforeId: string | null) => void;
  handleMergeIndicators: (fromId: string, intoId: string) => void;
  handleSplitIndicatorPane: (hostId: string) => void;
  handleExpandIndicator: (id: string) => void;
  handleRestoreIndicators: () => void;
  handleToggleCandleOverlay: (key: ChartOverlayToggleKey) => void;
  handleTogglePriceExtremeLabels: () => void;
  handleCustomOverlayActiveChange: (active: boolean) => void;
  handleCustom1OverlayActiveChange: (active: boolean) => void;
  handleSaveCustomOverlaySelection: (slot: ChartCustomOverlaySlotId, selection: ChartCustomOverlaySelection) => void;
  syncCustomOverlaysToManager: (mgr: ChartManager) => void;
  handleCandlesReady: () => void;
  handleSingleChartPaintReadyChange: (ready: boolean) => void;
  handleSlotPaintReadyChange: (slotIndex: number, ready: boolean) => void;
  applyBacktestMarkersWhenReady: (payload: NonNullable<ChartWorkspaceViewProps['btMarkerPayloadRef']['current']>, attempt?: number) => void;
  btRun: ReturnType<typeof import('../hooks/useBacktest').useBacktest>['run'];
  refreshPaperAccount: (forMarket?: string) => Promise<void>;
  handlePaperOrderFilled: () => void;
  handleLiveSettingsChange: (saved: LiveStrategySettingsDto) => void;
  clearLiveMarkers: () => void;
  goToMarketChart: (market: string) => void;
  handleTimezoneChange: (tz: string) => void;
}

function ChartWorkspaceViewInner(props: ChartWorkspaceViewProps) {
  const {
    onOpenNotificationsPage,
    symbol, timeframe, chartType, theme, mode, logScale, showGrid,
    toolbarActiveIndicators, layoutDef, syncOptions, activeSlot,
    activeSlotSymbol, activeSlotTf, slot0InitProps, slotInitProps,
    syncTime, syncVisibleRange, syncLogicalRange, slotRefs, managerRef,
    undoStack, redoStack, canUndo, canRedo, drawingTool, drawings,
    drawingsLocked, drawingsVisible, indicatorsVisible, magnetMode,
    snapToIndicator, keepLockedOnDelete, legend, showStats, showWatch,
    showAlert, showObjectTree, percentScale, magnifierActive, showMarketPanel,
    mobileRightOpen, mobileRightTab, marketTickers, marketInfos, marketLoading, usdRate,
    refreshAllTickers, setMarketSearchOpen,
    formalLogin, requireFormalLogin, chartVisible, menuPage, isMobile,
    indicators, effectiveIndicators, mainChartStyle, showMainChartModal,
    showBulkIndSettings, settingsModalId, ctxToolbar, drawingCtxToolbar,
    drawingSettingsId, selectedDrawingId, focusedIndicatorId,
    overlayVisibilityRef, candleOverlayToggles, customOverlaySelection,
    custom1OverlaySelection, customOverlaySettingsSlot, customOverlayActive,
    custom1OverlayActive, chartCandleAreaPriceLabels, chartSeriesPriceLabels,
    chartVolumeVisible, chartPriceExtremeLabelsVisible, chartPaneSeparator, chartCrosshairInfoVisible,
    chartHoverToolbarVisible, chartLiveReceiveHighlight, chartRealtimeSource,
    displayTimezone, chartTimeFormat, chartLegendOptions, useUpbit, wsStatus, bars, barsMarket,
    barsMarketRef, chartMountKey, chartLiveReadyRef, pendingRealtimeBarRef,
    isSingleLoadingMore, singleLoadMoreRef, btMatchesChart, btRunning,
    btSettings, btStats, btSignals, btClear, btMarkerPayloadRef,
    syncBacktestMarkersRef, showBtSettings, showBtResult, showLivePanel,
    liveStrategies, liveStrategySettings, monitoredMarkets, isVirtualActive,
    watchlist, chartBtReportModel, chartSymbol, chartScreenActive, orderbook,
    obWsStatus, spread, orderbookPrevClose, chartTicker, tradeFill,
    paperCashBalance, paperCoinQty, paperTradingEnabled, paperAutoTradeEnabled,
    appDefaults, stats, priceMap, alerts, hoverBar, quoteBar, liveQuote,
    isLoading, error, chartConfigReady, chartAreaLoading, chartDataLoading, bulkSettingsIndicators, chartIndicatorsForSettings,
    getParams, getVisualConfig, activeSlotIndicators,
    setSymbol, setLegend, setChartType, setMode, setLogScale, setShowGrid,
    setDrawingTool, setDrawings, setDrawingsLocked, setDrawingsVisible,
    setIndicatorsVisible, setMagnetMode, setSnapToIndicator, setKeepLockedOnDelete,
    setShowStats, setShowWatch, setShowAlert, setShowObjectTree, setMagnifierActive,
    setShowMarketPanel, setMobileRightOpen, setMobileRightTab, setSyncTime,
    setSyncVisibleRange, setSyncLogicalRange, setActiveSlotSymbol, setCanUndo,
    setCanRedo, setCtxToolbar, setDrawingCtxToolbar, setDrawingSettingsId,
    setSelectedDrawingId, setShowMainChartModal, setShowBulkIndSettings,
    setSettingsModalId, setCustomOverlaySettingsSlot, setShowBtSettings,
    setShowBtResult, setShowLivePanel, setWatchlist, setAlerts, setIndicators,
    setSyncOptions, setPercentScale, setBtStrategyName, setBtReportCreatedAt, setBtSettings,
    saveAppDef, applyChartIndicators,
    onThemeToggle, handleTimeframe, handleLayoutSelect, handleSetActiveSlot,
    handleActiveSlotIndicatorsChange, handleActiveSlotSymbolChange,
    handleVisibleRangeChange, handleLogicalRangeChange, handleSlotFullView,
    applyTradeFill, handleOrderbookPick, handleTradeMarketSelect, pushDrawing,
    handleUndo, handleRedo, handleHoverPaneLegend, handleLeavePaneLegend,
    clearHoverTimer, handleDrawingClick, handleDrawingDoubleClick,
    handleMainChartSave, handleAddIndicator, handleAddIndicators,
    handleRemoveByType, handleIndicatorListOrderChange, handleOpenIndicatorSettings,
    handleIndicatorSave, handleBulkIndicatorApply, handleToggleIndicatorHidden,
    handleDuplicateIndicator, handleRemoveIndicatorById, handleScreenshot,
    handleReorderIndicators, handleMergeIndicators, handleSplitIndicatorPane,
    handleExpandIndicator, handleRestoreIndicators, handleToggleCandleOverlay,
    handleTogglePriceExtremeLabels,
    handleCustomOverlayActiveChange, handleCustom1OverlayActiveChange,
    handleSaveCustomOverlaySelection, syncCustomOverlaysToManager,
    handleCandlesReady, handleSingleChartPaintReadyChange, handleSlotPaintReadyChange,
    applyBacktestMarkersWhenReady, btRun, refreshPaperAccount,
    handlePaperOrderFilled, handleLiveSettingsChange, clearLiveMarkers,
    handleTimezoneChange,
  } = props;

  return (
    <>
      <ChartToolbarNotify
        onOpenNotifyList={onOpenNotificationsPage}
        symbol={symbol} timeframe={layoutDef.count > 1 ? activeSlotTf : timeframe} chartType={chartType}
        theme={theme} mode={mode} logScale={logScale} showGrid={showGrid}
        activeIndicators={toolbarActiveIndicators}
        onSymbol={s => {
          if (layoutDef.count > 1) {
            // 멀티 레이아웃: 활성 슬롯의 심볼 변경
            slotRefs.current[activeSlot]?.setSymbol(s);
            // symbol sync 활성화된 경우 모든 슬롯 일괄 변경
            if (syncOptions.symbol) {
              slotRefs.current.slice(0, layoutDef.count).forEach(r => r?.setSymbol(s));
            }
          } else {
            setSymbol(s); setLegend(null);
          }
        }}
        onTimeframe={handleTimeframe}
        onChartType={setChartType}
        onTheme={onThemeToggle}
        onMode={() => setMode(m => m === 'chart' ? 'trading' : 'chart')}
        onLogScale={() => setLogScale(v => !v)}
        onToggleGrid={() => setShowGrid(v => !v)}
        onFitContent={() => managerRef.current?.fitContent()}
        onScrollToNow={() => managerRef.current?.scrollToRealTime()}
        onAddIndicator={handleAddIndicator}
        onAddIndicators={handleAddIndicators}
        onIndicatorOrderChange={handleIndicatorListOrderChange}
        onRemoveByType={handleRemoveByType}
        onOpenBulkIndicatorSettings={() => setShowBulkIndSettings(true)}
        onOpenIndicatorSettings={handleOpenIndicatorSettings}
        onAutoFib={() => managerRef.current?.drawAutoFib(80)}
        onClearFib={() => managerRef.current?.clearFib()}
        onScreenshot={handleScreenshot}
        onToggleStats={() => setShowStats(v => !v)}
        onToggleWatch={() => setShowWatch(v => !v)}
        onToggleAlert={() => setShowAlert(v => !v)}
        magnetMode={magnetMode}
        onToggleMagnet={() => setMagnetMode(m => m === 'off' ? 'strong' : 'off')}
        onUndo={handleUndo}
        onRedo={handleRedo}
        canUndo={canUndo}
        canRedo={canRedo}
        onClearDrawings={() => {
          // 모든 드로잉 삭제 + 피보나치 삭제 + undo/redo 스택 초기화
          undoStack.current = [];
          redoStack.current = [];
          setCanUndo(false);
          setCanRedo(false);
          setDrawings([]);
          managerRef.current?.clearFib();
          managerRef.current?.clearDrawings();
        }}
        layoutId={layoutDef.id}
        syncOptions={syncOptions}
        onLayoutSelect={handleLayoutSelect}
        onSyncChange={(key, val) => {
          setSyncOptions(prev => {
            const next = { ...prev, [key]: val };
            saveAppDef({ syncOptions: next as unknown as Record<string, boolean> });
            return next;
          });
        }}
        magnifierActive={magnifierActive}
        onToggleMagnifier={() => setMagnifierActive(v => !v)}
        marketPanelActive={showMarketPanel}
        onToggleMarketPanel={() => {
          setShowMarketPanel(v => {
            const next = !v;
            if (isMobile && next) setMobileRightOpen(false);
            return next;
          });
        }}
        marketTickers={marketTickers}
        marketInfos={marketInfos}
        marketLoading={marketLoading}
        refreshAllTickers={refreshAllTickers}
        onMarketSearchOpenChange={setMarketSearchOpen}
        backtestActive={btMatchesChart}
        backtestRunning={btRunning}
        backtestRunningId={undefined}
        onRunBacktest={menuPage === 'chart' && layoutDef.count === 1 && formalLogin
          ? async (opts) => {
              if (opts.strategyName) setBtStrategyName(opts.strategyName);
              if (useUpbit && (isLoading || bars.length === 0)) return;
              const runSymbol = symbol;
              const runTf = timeframe;
              const r = await btRun({
                ...opts,
                bars,
                timeframe: runTf,
                settings: btSettings,
                symbol: runSymbol,
              });
              if (
                r &&
                r.symbol === runSymbol &&
                r.timeframe === runTf &&
                r.signals.length > 0
              ) {
                const showPrice = appDefaults.btShowPrice ?? false;
                const payload = {
                  symbol:    runSymbol,
                  timeframe: runTf,
                  signals:   r.signals,
                  showPrice,
                };
                btMarkerPayloadRef.current = payload;
                applyBacktestMarkersWhenReady(payload);
                setBtReportCreatedAt(fmtListTimestamp(new Date().toISOString()));
              }
              if (appDefaults.btAutoPopup) setShowBtResult(true);
            }
          : undefined}
        onOpenBtSettings={() => {
          if (!formalLogin) { requireFormalLogin(); return; }
          setShowBtSettings(true);
        }}
        onOpenBtResults={() => {
          if (!formalLogin) { requireFormalLogin(); return; }
          setShowBtResult(true);
        }}
        hasBtResult={btMatchesChart}
        onClearBtMarkers={() => {
          btMarkerPayloadRef.current = null;
          btClear();
          managerRef.current?.clearBacktestMarkers();
        }}
        onToggleLiveStrategy={() => {
          if (!formalLogin) { requireFormalLogin(); return; }
          setShowLivePanel(v => !v);
        }}
        liveStrategyActive={formalLogin && (showLivePanel || monitoredMarkets.length > 0)}
        showChartQuote={useUpbit}
        chartLiveQuote={liveQuote}
        chartQuoteLoading={isLoading}
        chartQuoteError={error}
        priceExtremeLabelsVisible={chartPriceExtremeLabelsVisible}
        onTogglePriceExtremeLabels={handleTogglePriceExtremeLabels}
        tradeAlertPopupPosition={appDefaults.tradeAlertPopupPosition}
        tradeAlertPopupLayout={appDefaults.tradeAlertPopupLayout}
        tradeAlertPopupGridCols={appDefaults.tradeAlertPopupGridCols}
        onTradeAlertPopupPosition={v => saveAppDef({ tradeAlertPopupPosition: v })}
        onTradeAlertPopupLayout={v => saveAppDef({ tradeAlertPopupLayout: v })}
        onTradeAlertPopupGridCols={v => saveAppDef({ tradeAlertPopupGridCols: v })}
      />

      {isMobile && (showMarketPanel || mobileRightOpen) && (
        <button
          type="button"
          className="mobile-panel-backdrop"
          aria-label="패널 닫기"
          onClick={() => { setShowMarketPanel(false); setMobileRightOpen(false); }}
        />
      )}

      <div className="tv-main">
        {/* ── 좌측 마켓 패널 ─────────────────────────────────────────── */}
        <MarketPanel
          open={showMarketPanel}
          tickers={marketTickers}
          marketInfos={marketInfos}
          loading={marketLoading}
          usdRate={usdRate}
          refreshAllTickers={refreshAllTickers}
          activeSymbol={layoutDef.count > 1 ? activeSlotSymbol : symbol}
          onSymbolSelect={s => {
            if (layoutDef.count > 1) {
              // 다중차트: 현재 활성 슬롯에만 적용, 호가창 심볼도 즉시 갱신
              slotRefs.current[activeSlot]?.setSymbol(s);
              setActiveSlotSymbol(s);
            } else {
              setSymbol(s);
              setLegend(null);
            }
          }}
        />

        {/* 마켓 패널 접기/펼치기 핸들 – tv-main flex 자식으로 항상 표시 */}
        <button
          className={`mp-panel-handle ${showMarketPanel ? 'mp-panel-handle--open' : ''}`}
          onClick={() => setShowMarketPanel(v => !v)}
          title={showMarketPanel ? '마켓 패널 닫기' : '마켓 패널 열기'}
        >
          <svg width="8" height="14" viewBox="0 0 8 14" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
            {showMarketPanel
              ? <polyline points="6,1 2,7 6,13" />
              : <polyline points="2,1 6,7 2,13" />}
          </svg>
        </button>

        <DrawingToolbar
          activeTool={drawingTool} mode={mode}
          onTool={setDrawingTool}
          onClearAll={() => { setDrawings([]); managerRef.current?.clearDrawings(); }}
          onUndo={() => setDrawings(prev => prev.slice(0, -1))}
          locked={drawingsLocked}
          visible={drawingsVisible}
          onToggleLock={() => setDrawingsLocked(v => !v)}
          onToggleEye={() => setDrawingsVisible(v => !v)}
          indicatorsVisible={indicatorsVisible}
          magnetMode={magnetMode}
          snapToIndicator={snapToIndicator}
          drawingCount={drawings.length}
          indicatorCount={indicators.length}
          onMagnetMode={setMagnetMode}
          onSnapToIndicator={setSnapToIndicator}
          onHideDrawings={() => setDrawingsVisible(v => !v)}
          onHideIndicators={() => setIndicatorsVisible(v => !v)}
          onHideAll={() => { setDrawingsVisible(false); setIndicatorsVisible(false); }}
          onClearIndicators={() => setIndicators([])}
          keepLockedOnDelete={keepLockedOnDelete}
          onToggleKeepLocked={() => setKeepLockedOnDelete(v => !v)}
        />

        {/* ── 멀티 레이아웃: 슬롯 2개 이상일 때 별도 그리드 렌더 ─────────── */}
        {layoutDef.count > 1 && (
          <div
            className="multi-chart-grid"
            style={{
              gridTemplateColumns: layoutDef.cols,
              gridTemplateRows:    layoutDef.rows,
              gridTemplateAreas:   layoutDef.areas,
              position: 'relative',
            }}
          >
            {chartAreaLoading && (
              <div className="chart-loading chart-loading--chart-area chart-loading--workspace" role="status" aria-live="polite">
                <div className="loading-spinner" />
                <span>차트 로딩중...</span>
              </div>
            )}
            {/* 슬롯 0 = 메인 차트 영역 (a) */}
            <div className="multi-slot-wrap" style={{ gridArea: 'a', minHeight: 0, minWidth: 0 }}>
              <ChartSlot
                ref={el => { slotRefs.current[0] = el; }}
                slotIndex={0}
                theme={theme}
                active={activeSlot === 0}
                onActivate={() => handleSetActiveSlot(0)}
                initialSymbol={slot0InitProps?.symbol}
                initialTimeframe={slot0InitProps?.timeframe}
                initialIndicators={slot0InitProps?.indicators}
                forcedSymbol={syncOptions.symbol ? symbol : undefined}
                forcedTf={syncOptions.interval ? timeframe : undefined}
                syncTime={syncOptions.crosshair ? syncTime : undefined}
                onCrosshairTime={t => { if (syncOptions.crosshair) setSyncTime(t); }}
                syncVisibleRange={syncOptions.time ? syncVisibleRange : undefined}
                onVisibleRangeChange={handleVisibleRangeChange}
                syncLogicalRange={syncOptions.dateRange ? syncLogicalRange : undefined}
                onLogicalRangeChange={handleLogicalRangeChange}
                onFullView={sym => handleSlotFullView(sym, 0)}
                magnifierEnabled={magnifierActive && activeSlot === 0}
                onMagnifierClose={() => setMagnifierActive(false)}
                onIndicatorsChange={activeSlot === 0 ? handleActiveSlotIndicatorsChange : undefined}
                onSymbolChange={activeSlot === 0 ? handleActiveSlotSymbolChange : undefined}
                magnetMode={magnetMode}
                onTradeOrderRequest={(req, idx) => applyTradeFill(req, idx)}
                chartCandleAreaPriceLabels={chartCandleAreaPriceLabels}
                chartSeriesPriceLabels={chartSeriesPriceLabels}
                chartVolumeVisible={chartVolumeVisible}
                chartPaneSeparator={chartPaneSeparator}
                chartCrosshairInfoVisible={chartCrosshairInfoVisible}
                chartHoverToolbarVisible={chartHoverToolbarVisible}
                chartPriceExtremeLabelsVisible={chartPriceExtremeLabelsVisible}
                onTogglePriceExtremeLabels={handleTogglePriceExtremeLabels}
                chartLiveReceiveHighlight={chartLiveReceiveHighlight}
                chartRealtimeSource={chartRealtimeSource as 'BACKEND_STOMP' | 'UPBIT_DIRECT'}
                displayTimezone={displayTimezone}
                marketTickers={marketTickers}
                marketInfos={marketInfos}
                marketLoading={marketLoading}
                refreshAllTickers={refreshAllTickers}
                onMarketSearchOpenChange={setMarketSearchOpen}
                compactMode
                chartVisible={chartVisible}
                chartConfigReady={chartConfigReady}
                onPaintReadyChange={handleSlotPaintReadyChange}
              />
            </div>
            {/* 슬롯 1~7 */}
            {(['b','c','d','e','f','g','h'] as const)
              .slice(0, layoutDef.count - 1)
              .map((area, i) => (
                <div key={area} className="multi-slot-wrap" style={{ gridArea: area, minHeight: 0, minWidth: 0 }}>
                  <ChartSlot
                    ref={el => { slotRefs.current[i + 1] = el; }}
                    slotIndex={i + 1}
                    theme={theme}
                    active={activeSlot === i + 1}
                    onActivate={() => handleSetActiveSlot(i + 1)}
                    initialSymbol={slotInitProps[i + 1]?.symbol}
                    initialTimeframe={slotInitProps[i + 1]?.timeframe}
                    initialIndicators={slotInitProps[i + 1]?.indicators}
                    forcedSymbol={syncOptions.symbol ? symbol : undefined}
                    forcedTf={syncOptions.interval ? timeframe : undefined}
                    syncTime={syncOptions.crosshair ? syncTime : undefined}
                    onCrosshairTime={t => { if (syncOptions.crosshair) setSyncTime(t); }}
                    syncVisibleRange={syncOptions.time ? syncVisibleRange : undefined}
                    onVisibleRangeChange={handleVisibleRangeChange}
                    syncLogicalRange={syncOptions.dateRange ? syncLogicalRange : undefined}
                    onLogicalRangeChange={handleLogicalRangeChange}
                    onFullView={sym => handleSlotFullView(sym, i + 1)}
                    magnifierEnabled={magnifierActive && activeSlot === i + 1}
                    onMagnifierClose={() => setMagnifierActive(false)}
                    onIndicatorsChange={activeSlot === i + 1 ? handleActiveSlotIndicatorsChange : undefined}
                    onSymbolChange={activeSlot === i + 1 ? handleActiveSlotSymbolChange : undefined}
                    magnetMode={magnetMode}
                    onTradeOrderRequest={(req, idx) => applyTradeFill(req, idx)}
                    chartCandleAreaPriceLabels={chartCandleAreaPriceLabels}
                    chartSeriesPriceLabels={chartSeriesPriceLabels}
                    chartVolumeVisible={chartVolumeVisible}
                    chartPaneSeparator={chartPaneSeparator}
                    chartCrosshairInfoVisible={chartCrosshairInfoVisible}
                    chartHoverToolbarVisible={chartHoverToolbarVisible}
                    chartPriceExtremeLabelsVisible={chartPriceExtremeLabelsVisible}
                    chartLiveReceiveHighlight={chartLiveReceiveHighlight}
                    chartRealtimeSource={chartRealtimeSource as 'BACKEND_STOMP' | 'UPBIT_DIRECT'}
                    displayTimezone={displayTimezone}
                    marketTickers={marketTickers}
                    marketInfos={marketInfos}
                    marketLoading={marketLoading}
                    refreshAllTickers={refreshAllTickers}
                    onMarketSearchOpenChange={setMarketSearchOpen}
                    compactMode
                    chartVisible={chartVisible}
                    chartConfigReady={chartConfigReady}
                    onPaintReadyChange={handleSlotPaintReadyChange}
                  />
                </div>
              ))}
          </div>
        )}

        {/* ── 싱글 레이아웃 (기존) ─────────────────────────────────────── */}
        <div className="tv-chart-col" style={{ display: layoutDef.count > 1 ? 'none' : undefined, position: 'relative' }}>
          {chartAreaLoading && (
            <div className="chart-loading chart-loading--chart-area chart-loading--workspace" role="status" aria-live="polite">
              <div className="loading-spinner" />
              <span>차트 로딩중...</span>
            </div>
          )}
          <ChartLegendBar
            visibility={chartLegendOptions}
            crosshairInfoVisible={chartCrosshairInfoVisible}
            symbol={symbol}
            timeframe={timeframe}
            useUpbit={useUpbit}
            wsStatus={wsStatus}
            mode={mode}
            currentBar={hoverBar}
            liveQuote={liveQuote}
            indicators={indicators}
            legend={legend}
          />

          <TradingChart
            key={chartMountKey}
            chartVisible={chartVisible}
            dataLoading={chartDataLoading}
            onChartPaintReadyChange={handleSingleChartPaintReadyChange}
            bars={bars} barsMarket={barsMarket} market={symbol} timeframe={timeframe} displayTimezone={displayTimezone}
            chartType={chartType} theme={theme} mode={mode}
            indicators={effectiveIndicators} drawingTool={drawingTool}
            drawings={drawings} logScale={logScale}
            drawingsLocked={drawingsLocked}
            drawingsVisible={drawingsVisible}
            crosshairInfoVisible={chartCrosshairInfoVisible}
            onCrosshair={setLegend}
            onTradeOrderRequest={applyTradeFill}
            onDataLoaded={() => {
              const mgr = managerRef.current;
              mgr?.setChartOverlayVisibility(overlayVisibilityRef.current);
              if (mgr) syncCustomOverlaysToManager(mgr);
              syncBacktestMarkersRef.current();
            }}
            onCandlesReady={handleCandlesReady}
            onManagerReady={mgr => {
              managerRef.current = mgr;
              const pending = pendingRealtimeBarRef.current;
              if (pending && chartLiveReadyRef.current && barsMarketRef.current === symbol && bars.length > 0) {
                pendingRealtimeBarRef.current = null;
                const last = bars[bars.length - 1];
                if (last && pending.time === last.time) mgr.updateBar(pending);
                else if (!last || pending.time > last.time) void mgr.appendBar(pending);
                else mgr.updateBar({ ...pending, time: last.time });
              } else if (chartLiveReadyRef.current) {
                pendingRealtimeBarRef.current = null;
              }
              mgr.setCandleAreaPriceLabelsEnabled(chartCandleAreaPriceLabels);
              mgr.setIndicatorAreaPriceLabelsEnabled(chartSeriesPriceLabels);
              const wrapper = document.querySelector('.chart-wrapper') as HTMLElement | null;
              mgr.setVolumeVisible(chartVolumeVisible, wrapper?.clientHeight);
              mgr.setPriceExtremeLabelsVisible(chartPriceExtremeLabelsVisible);
              mgr.setPaneSeparatorOptions(chartPaneSeparator);
              mgr.setDisplayTimezone(displayTimezone, timeframe, chartTimeFormat);
              mgr.setChartOverlayVisibility(overlayVisibilityRef.current);
              syncCustomOverlaysToManager(mgr);
              syncBacktestMarkersRef.current();
              // 왼쪽 스크롤 감지 → 과거 데이터 추가 로드
              mgr.subscribeVisibleLogicalRange(r => {
                if (r && r.from < LOAD_MORE_TRIGGER) {
                  singleLoadMoreRef.current();
                }
              });
            }}
            onAddDrawing={d => pushDrawing([...drawings, d])}
            onSeriesClick={(entryId, point) => {
              if (!entryId) { setCtxToolbar(null); return; }
              setCtxToolbar({ entryId, screenX: point.x, screenY: point.y });
              setDrawingCtxToolbar(null); // 드로잉 툴바 닫기
            }}
            onSeriesDoubleClick={(entryId) => {
              setCtxToolbar(null);
              if (!entryId) return;
              if (entryId === '__main__') {
                setShowMainChartModal(true);
              } else {
                setSettingsModalId(entryId);
              }
            }}
            onHoverPaneLegend={handleHoverPaneLegend}
            onLeavePaneLegend={handleLeavePaneLegend}
            selectedDrawingId={selectedDrawingId}
            onDrawingClick={handleDrawingClick}
            onDrawingDoubleClick={handleDrawingDoubleClick}
            magnifierEnabled={magnifierActive}
            onMagnifierClose={() => setMagnifierActive(false)}
            onReorderIndicators={handleReorderIndicators}
            onMergeIndicators={handleMergeIndicators}
            onSplitIndicatorPane={handleSplitIndicatorPane}
            onExpandIndicator={handleExpandIndicator}
            onRemoveIndicator={handleRemoveIndicatorById}
            onDuplicateIndicator={handleDuplicateIndicator}
            focusedIndicatorId={focusedIndicatorId}
            onRestoreIndicators={handleRestoreIndicators}
            onToggleIndicatorHidden={handleToggleIndicatorHidden}
            onOpenIndicatorSettings={id => setSettingsModalId(id)}
            volumeVisible={chartVolumeVisible}
            candleAreaPriceLabelsEnabled={chartCandleAreaPriceLabels}
            indicatorAreaPriceLabelsEnabled={chartSeriesPriceLabels}
            paneSeparatorOptions={chartPaneSeparator}
            candleOverlayToggles={candleOverlayToggles}
            onToggleCandleOverlay={handleToggleCandleOverlay}
            priceExtremeLabelsVisible={chartPriceExtremeLabelsVisible}
            onTogglePriceExtremeLabels={handleTogglePriceExtremeLabels}
            customOverlayActive={customOverlayActive}
            onCustomOverlayActiveChange={handleCustomOverlayActiveChange}
            onOpenCustomOverlaySettings={() => setCustomOverlaySettingsSlot('custom')}
            custom1OverlayActive={custom1OverlayActive}
            onCustom1OverlayActiveChange={handleCustom1OverlayActiveChange}
            onOpenCustom1OverlaySettings={() => setCustomOverlaySettingsSlot('custom1')}
            showHoverToolbar={chartHoverToolbarVisible}
          />
          {customOverlaySettingsSlot === 'custom' && (
            <ChartCustomOverlaySettingsModal
              title="Custom 표시 설정"
              selection={customOverlaySelection}
              onSave={sel => handleSaveCustomOverlaySelection('custom', sel)}
              onCancel={() => setCustomOverlaySettingsSlot(null)}
              smaParams={indicators.find(i => i.type === 'SMA')?.params}
            />
          )}
          {customOverlaySettingsSlot === 'custom1' && (
            <ChartCustomOverlaySettingsModal
              title="Custom1 표시 설정"
              selection={custom1OverlaySelection}
              onSave={sel => handleSaveCustomOverlaySelection('custom1', sel)}
              onCancel={() => setCustomOverlaySettingsSlot(null)}
              smaParams={indicators.find(i => i.type === 'SMA')?.params}
            />
          )}
          {isSingleLoadingMore && (
            <div className="chart-history-loading">
              <div className="loading-spinner" style={{ width: 14, height: 14 }} />
              <span>과거 데이터 로딩...</span>
            </div>
          )}

          {/* 백테스팅 결과 통계 배지 */}
          {btMatchesChart && layoutDef.count === 1 && (
            <BacktestPanel
              stats={btStats}
              signalCount={btSignals.length}
              onClear={() => {
                btMarkerPayloadRef.current = null;
                btClear();
                managerRef.current?.clearBacktestMarkers();
              }}
            />
          )}

          {/* 실시간 전략 체크 패널 */}
          {showLivePanel && menuPage === 'chart' && layoutDef.count === 1 && (
            <LiveStrategyPanel
              theme={theme}
              market={symbol}
              strategies={liveStrategies}
              settings={liveStrategySettings}
              watchlistCount={isVirtualActive ? monitoredMarkets.length : watchlist.length}
              monitoredMarkets={monitoredMarkets}
              virtualDriven={isVirtualActive}
              onClearMarkers={() => { clearLiveMarkers(); managerRef.current?.clearLiveStrategyMarkers(); }}
              onSettingsChange={handleLiveSettingsChange}
              onClose={() => setShowLivePanel(false)}
            />
          )}

          {/* 백테스팅 설정 모달 */}
          {showBtSettings && (
            <BacktestSettingsModal
              onClose={() => setShowBtSettings(false)}
              onSettingsChange={s => { setBtSettings(s); setShowBtSettings(false); }}
            />
          )}

          {/* 백테스팅 결과 — 투자분석 상세 레포트 (인쇄·PDF) */}
          <BacktestAnalysisReportModal
            open={showBtResult && chartBtReportModel != null}
            onClose={() => setShowBtResult(false)}
            model={chartBtReportModel}
          />

          {/* 시리즈 클릭/호버 컨텍스트 툴바 */}
          {ctxToolbar && (() => {
            const cfg = ctxToolbar.entryId !== '__main__'
              ? indicators.find(i => i.id === ctxToolbar.entryId) ?? null
              : null;
            return (
              <IndicatorContextToolbar
                indicatorId={ctxToolbar.entryId}
                config={cfg}
                chartType={chartType}
                screenX={ctxToolbar.screenX}
                screenY={ctxToolbar.screenY}
                onMouseEnterToolbar={clearHoverTimer}
                onMouseLeaveToolbar={handleLeavePaneLegend}
                onSettings={() => {
                  const id = ctxToolbar.entryId;
                  setCtxToolbar(null);
                  if (id === '__main__') {
                    setShowMainChartModal(true);
                  } else {
                    setSettingsModalId(id);
                  }
                }}
                onToggleHidden={() => {
                  handleToggleIndicatorHidden(ctxToolbar.entryId);
                }}
                onRemove={() => handleRemoveIndicatorById(ctxToolbar.entryId)}
                onClose={() => setCtxToolbar(null)}
              />
            );
          })()}

          {/* 드로잉 컨텍스트 툴바 */}
          {drawingCtxToolbar && (() => {
            const d = drawings.find(dr => dr.id === drawingCtxToolbar.id);
            if (!d) return null;
            return (
              <DrawingContextToolbar
                drawing={d}
                screenX={drawingCtxToolbar.screenX}
                screenY={drawingCtxToolbar.screenY}
                onSettings={() => {
                  setDrawingCtxToolbar(null);
                  setDrawingSettingsId(d.id);
                }}
                onToggleVisible={() => {
                  setDrawings(prev =>
                    prev.map(dr =>
                      dr.id === d.id
                        ? { ...dr, visible: dr.visible !== false ? false : true }
                        : dr
                    )
                  );
                  setDrawingCtxToolbar(null);
                }}
                onRemove={() => {
                  pushDrawing(drawings.filter(dr => dr.id !== d.id));
                  setDrawingCtxToolbar(null);
                  setSelectedDrawingId(null);
                }}
                onClose={() => {
                  setDrawingCtxToolbar(null);
                  setSelectedDrawingId(null);
                }}
              />
            );
          })()}

          {/* 드로잉 설정 모달 */}
          {drawingSettingsId && (() => {
            const d = drawings.find(dr => dr.id === drawingSettingsId);
            if (!d) return null;
            const handleSaveDrawing = (updated: typeof d) => {
              setDrawings(prev => prev.map(dr => dr.id === updated!.id ? updated! : dr));
              setDrawingSettingsId(null);
              setSelectedDrawingId(updated!.id);
            };
            const handleCancelDrawing = () => { setDrawingSettingsId(null); };
            if (d.type === 'fibtz') {
              return (
                <FibTZSettingsModal
                  drawing={d}
                  onSave={handleSaveDrawing}
                  onCancel={handleCancelDrawing}
                />
              );
            }
            return (
              <DrawingSettingsModal
                drawing={d}
                onSave={updated => {
                  setDrawings(prev => prev.map(dr => dr.id === updated.id ? updated : dr));
                  setDrawingSettingsId(null);
                  setSelectedDrawingId(updated.id);
                }}
                onCancel={() => {
                  setDrawingSettingsId(null);
                }}
              />
            );
          })()}

          {/* 메인 차트 설정 모달 */}
          {showMainChartModal && (
            <MainChartSettingsModal
              style={mainChartStyle}
              onSave={handleMainChartSave}
              onCancel={() => setShowMainChartModal(false)}
            />
          )}

          {/* 보조지표 일괄 설정 */}
          {showBulkIndSettings && (
            <IndicatorBulkSettingsModal
              open={showBulkIndSettings}
              indicators={bulkSettingsIndicators}
              chartMarket={symbol}
              getParams={getParams}
              getVisualConfig={getVisualConfig}
              onApply={handleBulkIndicatorApply}
              onLiveChartChange={applyChartIndicators}
              onClose={() => setShowBulkIndSettings(false)}
            />
          )}

          {/* 인디케이터 설정 모달 */}
          {settingsModalId && settingsModalId !== '__main__' && (() => {
            let cfg: IndicatorConfig | null = null;
            if (isIndicatorSettingsTemplateId(settingsModalId)) {
              const type = indicatorTypeFromSettingsId(settingsModalId);
              if (!type) return null;
              cfg = buildMainIndicatorConfig(
                type,
                chartIndicatorsForSettings,
                getParams,
                getVisualConfig,
              );
            } else {
              const onChart = chartIndicatorsForSettings.find(i => i.id === settingsModalId);
              if (onChart) {
                cfg = initializeIndicatorConfigForEditor(onChart, getParams, getVisualConfig);
              }
            }
            if (!cfg) return null;
            return (
              <IndicatorSettingsModal
                config={cfg}
                chartMarket={symbol}
                onSave={handleIndicatorSave}
                onCancel={() => setSettingsModalId(null)}
              />
            );
          })()}

        </div>

        {/* ── 우측 슬라이드 패널 (매수/매도 + 호가) ───────────────────── */}
        <RightSidePanel
          open={isMobile ? mobileRightOpen : undefined}
          onOpenChange={isMobile ? setMobileRightOpen : undefined}
          activeTab={isMobile ? mobileRightTab : undefined}
          onTabChange={isMobile ? setMobileRightTab : undefined}
          wsFeedEnabled={chartScreenActive && useUpbit}
          market={chartSymbol}
          tradePrice={chartTicker?.tradePrice ?? null}
          asks={orderbook.asks}
          bids={orderbook.bids}
          totalAskSize={orderbook.totalAskSize}
          totalBidSize={orderbook.totalBidSize}
          wsStatus={obWsStatus}
          bestAsk={spread.bestAsk}
          bestBid={spread.bestBid}
          spread={spread.spread}
          spreadPct={spread.pct}
          prevClose={orderbookPrevClose}
          tickerInfo={chartTicker ? {
            tradePrice: chartTicker.tradePrice,
            changeRate: chartTicker.changeRate,
            changePrice: chartTicker.changePrice,
            accTradePrice24: chartTicker.accTradePrice24,
            accTradeVolume24: chartTicker.accTradeVolume24,
            highPrice: chartTicker.highPrice,
            lowPrice: chartTicker.lowPrice,
            openingPrice: chartTicker.openingPrice,
          } : undefined}
          fillRequest={tradeFill}
          onOrderbookPick={handleOrderbookPick}
          onMarketSelect={handleTradeMarketSelect}
          availableKrw={paperCashBalance}
          availableCoinQty={paperCoinQty}
          paperTradingEnabled={formalLogin && paperTradingEnabled}
          paperAutoTradeEnabled={formalLogin && paperAutoTradeEnabled}
          onPaperOrderFilled={() => {
            refreshPaperAccount(symbol);
            handlePaperOrderFilled();
          }}
        />

      </div>

      {/* ── 플로팅 패널: position:fixed로 항상 표시 (싱글/멀티 모드 공통) ─── */}
      {showObjectTree && (
        <ObjectTree
          drawings={drawings}
          onToggleVisible={id => setDrawings(prev =>
            prev.map(d => d.id === id ? { ...d, visible: d.visible !== false ? false : true } : d)
          )}
          onRemove={id => setDrawings(prev => prev.filter(d => d.id !== id))}
          onClose={() => setShowObjectTree(false)}
        />
      )}
      {showStats && <StatsPanel stats={stats} symbol={symbol} onClose={() => setShowStats(false)} />}
      {showWatch && (
        <WatchList
          watchlist={watchlist} currentSymbol={symbol} priceMap={priceMap}
          onSelect={s => { setSymbol(s); setShowWatch(false); }}
          onAdd={s => {
            setWatchlist(prev => [...new Set([...prev, s])]);
            addWatchlistItem({ symbol: s }).catch(() => {});
          }}
          onRemove={s => {
            setWatchlist(prev => prev.filter(x => x !== s));
            removeWatchlistItem(s).catch(() => {});
          }}
          onClose={() => setShowWatch(false)}
        />
      )}
      {showAlert && (
        <AlertManager
          alerts={alerts} currentPrice={liveQuote.price ?? quoteBar?.close ?? 0}
          onAdd={(price, label) => { managerRef.current?.addAlert(price, label); setAlerts(prev => [...prev, { price, label }]); }}
          onRemove={price => { managerRef.current?.removeAlert(price); setAlerts(prev => prev.filter(a => Math.abs(a.price - price) > 0.0001)); }}
          onClose={() => setShowAlert(false)}
        />
      )}

      {isMobile && (
        <MobileChartDock
          marketOpen={showMarketPanel}
          rightOpen={mobileRightOpen}
          rightTab={mobileRightTab}
          drawingTool={drawingTool}
          onToggleMarket={() => {
            setShowMarketPanel(v => {
              const next = !v;
              if (next) setMobileRightOpen(false);
              return next;
            });
          }}
          onOpenTrade={() => {
            setShowMarketPanel(false);
            setMobileRightTab('trade');
            setMobileRightOpen(true);
          }}
          onOpenOrderbook={() => {
            setShowMarketPanel(false);
            setMobileRightTab('orderbook');
            setMobileRightOpen(true);
          }}
          onClosePanels={() => {
            setShowMarketPanel(false);
            setMobileRightOpen(false);
          }}
          onTool={setDrawingTool}
        />
      )}

      <BottomBar
        timeframe={layoutDef.count > 1 ? activeSlotTf : timeframe}
        logScale={logScale}
        percentScale={percentScale} showGrid={showGrid}
        lastTime={bars[bars.length - 1]?.time ?? 0}
        displayTimezone={displayTimezone}
        onTimezoneChange={handleTimezoneChange}
        onTimeframe={handleTimeframe}
        onLogScale={() => setLogScale(v => !v)}
        onPercentScale={() => setPercentScale(v => !v)}
        onAutoScale={() => managerRef.current?.autoScale()}
        onFitContent={() => managerRef.current?.fitContent()}
        onToggleGrid={() => setShowGrid(v => !v)}
        onAddIndicators={handleAddIndicators}
        onAddIndicator={handleAddIndicator}
      />

    </>
  );

}

export const ChartWorkspaceView = React.memo(ChartWorkspaceViewInner);
