/**
 * 설정 페이지
 * macOS 시스템 환경설정 스타일 — 좌측 카테고리 목록 + 우측 설정 패널
 */
import React, { useState, useEffect, useCallback } from 'react';
import type { Theme } from '../types';
import {
  loadBacktestSettings,
  saveLiveStrategySettings,
  type LiveStrategySettingsDto,
} from '../utils/backendApi';
import { syncStrategyTimeframesFromLayoutIfNeeded } from '../utils/strategyTimeframeSync';
import { BacktestSettingsPanel } from './BacktestSettingsPanel';
import IndicatorMainDefaultsPanel, {
  type IndicatorSaveUiState,
} from './IndicatorMainDefaultsPanel';
import type { IndicatorConfig } from '../types';
import type { PlotDef, HLineDef } from '../utils/indicatorRegistry';
import type { IchimokuCloudColors } from '../utils/ichimokuConfig';
import ChartTimeFormatPicker from './ChartTimeFormatPicker';
import DesktopUpdatePanel from './DesktopUpdatePanel';
import {
  TRADE_ALERT_SOUND_OPTIONS,
  normalizeTradeAlertSoundId,
  previewTradeAlertSound,
} from '../utils/tradeAlertSound';
import {
  TRADE_ALERT_GRID_COL_OPTIONS,
  TRADE_ALERT_POPUP_LAYOUTS,
  TRADE_ALERT_POPUP_POSITIONS,
  normalizeTradeAlertGridCols,
  normalizeTradeAlertPopupLayout,
  normalizeTradeAlertPopupPosition,
} from '../utils/tradeAlertPopupLayout';
import TradeAlertPopupPreview from './TradeAlertPopupPreview';
import TrendSearchSettingsPanel from './trendSearch/TrendSearchSettingsPanel';
import LlmSettingsPanel from './settings/LlmSettingsPanel';
import type { TrendSearchAppSettings } from '../utils/trendSearchAppSettings';
import type { LlmAppSettings } from '../utils/llmSettings';
import { isTradingSettingsCategory } from '../utils/tradingAccess';
import {
  CHART_LEGEND_SETTING_ITEMS,
  type ChartLegendVisibility,
} from '../types/chartLegend';
import type { ChartPaneSeparatorOptions, PaneSeparatorStyle } from '../types/chartPaneSeparator';
import { cloneChartPaneSeparatorOptions } from '../types/chartPaneSeparator';
import ColorInput from './ColorInput';
import {
  LINE_STYLE_LABELS,
  LINE_STYLE_OPTIONS,
  LINE_WIDTH_OPTIONS,
} from '../utils/plotColorUtils';
import TimezonePicker from './TimezonePicker';
import AdminPasswordGate from './AdminPasswordGate';
import AdminSettingsPanel from './AdminSettingsPanel';
import {
  type SettingsCategoryId,
  settingsCategoryToMenuId,
  canAccessMenu,
} from '../utils/permissions';

interface SettingsPageProps {
  theme:            Theme;
  magnetMode?:      'off' | 'weak' | 'strong';
  onMagnetMode?:    (m: 'off' | 'weak' | 'strong') => void;
  btAutoPopup?:     boolean;
  onBtAutoPopup?:   (v: boolean) => void;
  btShowPrice?:     boolean;
  onBtShowPrice?:   (v: boolean) => void;
  /** 실시간 전략 체크 — 현재 차트 종목 */
  liveMarket?:      string;
  /** 실시간 전략 체크 — 선택 가능한 전략 목록 */
  liveStrategies?:  { id: number; name: string }[];
  /** 마커 초기화 콜백 */
  onClearLiveMarkers?: () => void;
  /** 매매 알림 팝업 ON/OFF */
  tradeAlertPopup?:    boolean;
  onTradeAlertPopup?:  (v: boolean) => void;
  /** 알림 설정 탭 */
  tradeAlertSoundEnabled?: boolean;
  onTradeAlertSoundEnabled?: (v: boolean) => void;
  tradeAlertSound?: string;
  onTradeAlertSound?: (v: string) => void;
  tradeAlertPopupPosition?: string;
  onTradeAlertPopupPosition?: (v: string) => void;
  tradeAlertPopupLayout?: string;
  onTradeAlertPopupLayout?: (v: string) => void;
  tradeAlertPopupGridCols?: number;
  onTradeAlertPopupGridCols?: (v: number) => void;
  liveSettings?:        LiveStrategySettingsDto;
  onLiveSettingsChange?: (settings: LiveStrategySettingsDto) => void;
  watchlistCount?:      number;
  monitoredMarkets?:    string[];
  /** 가상투자 세션 실행 중 — 전략 설정은 가상투자 화면에서 */
  virtualDriven?:       boolean;
  /** Main 탭 보조지표 기본 파라미터 (DB) */
  getIndicatorParams?:  (type: string, defaults: Record<string, number | string | boolean>) => Record<string, number | string | boolean>;
  saveIndicatorParams?: (type: string, params: Record<string, number | string | boolean>) => void;
  getIndicatorVisual?:  (type: string, defaultPlots?: PlotDef[], defaultHlines?: HLineDef[]) => {
    plots: PlotDef[];
    hlines: HLineDef[];
    cloudColors?: IchimokuCloudColors;
  };
  saveIndicatorVisual?: (
    type: string,
    plots?: PlotDef[],
    hlines?: HLineDef[],
    cloudColors?: IchimokuCloudColors,
  ) => void;
  /** 설정 화면 — 보조지표 기본값 일괄 DB 저장 */
  saveAllIndicatorDefaults?: (configs: Record<string, IndicatorConfig>) => Promise<void>;
  /** 보조지표 DB 캐시 갱신 revision */
  indicatorSettingsRevision?: number;
  /** 설정 화면에서 차트 on/off 연동 */
  chartIndicators?:     IndicatorConfig[];
  onAddIndicatorToChart?:    (type: string, config?: IndicatorConfig) => void;
  onRemoveIndicatorFromChart?: (type: string) => void;
  /** 보조지표 설정 목록 순서 변경 → 차트 pane 순서 */
  onIndicatorListOrderChange?: (orderedTypes: string[]) => void;
  chartCandleAreaPriceLabels?: boolean;
  onChartCandleAreaPriceLabels?: (v: boolean) => void;
  chartSeriesPriceLabels?: boolean;
  onChartSeriesPriceLabels?: (v: boolean) => void;
  chartVolumeVisible?: boolean;
  onChartVolumeVisible?: (v: boolean) => void;
  chartLiveReceiveHighlight?: boolean;
  onChartLiveReceiveHighlight?: (v: boolean) => void;
  chartLegendOptions?: ChartLegendVisibility;
  onChartLegendOptionsChange?: (patch: Partial<ChartLegendVisibility>) => void;
  chartCrosshairInfoVisible?: boolean;
  onChartCrosshairInfoVisible?: (v: boolean) => void;
  chartCrosshairMarkersVisible?: boolean;
  onChartCrosshairMarkersVisible?: (v: boolean) => void;
  chartHoverToolbarVisible?: boolean;
  onChartHoverToolbarVisible?: (v: boolean) => void;
  chartPaneSeparator?: ChartPaneSeparatorOptions;
  onChartPaneSeparatorChange?: (opts: ChartPaneSeparatorOptions) => void;
  paperTradingEnabled?: boolean;
  onPaperTradingEnabled?: (v: boolean) => void;
  paperInitialCapital?: number;
  onPaperInitialCapital?: (v: number) => void;
  paperFeeRatePct?: number;
  onPaperFeeRatePct?: (v: number) => void;
  paperSlippagePct?: number;
  onPaperSlippagePct?: (v: number) => void;
  paperMinOrderKrw?: number;
  onPaperMinOrderKrw?: (v: number) => void;
  paperAutoTradeEnabled?: boolean;
  onPaperAutoTradeEnabled?: (v: boolean) => void;
  paperAutoTradeBudgetPct?: number;
  onPaperAutoTradeBudgetPct?: (v: number) => void;
  paperOrderFillMode?: 'LIMIT_ONLY' | 'NEXT_TICK' | 'AUTO_CANCEL';
  onPaperOrderFillMode?: (v: 'LIMIT_ONLY' | 'NEXT_TICK' | 'AUTO_CANCEL') => void;
  paperOrderAutoCancelPct?: number;
  onPaperOrderAutoCancelPct?: (v: number) => void;
  virtualTargetMaxCount?: number;
  onVirtualTargetMaxCount?: (v: number) => void;
  onPaperAccountReset?: () => void;
  tradingMode?: string;
  onTradingMode?: (v: string) => void;
  liveAutoTradeEnabled?: boolean;
  onLiveAutoTradeEnabled?: (v: boolean) => void;
  hasUpbitKeys?: boolean;
  upbitAccessKeyMasked?: string | null;
  onUpbitKeys?: (access: string, secret: string) => void;
  chartRealtimeSource?: string;
  onChartRealtimeSource?: (v: string) => void;
  liveAutoTradeBudgetPct?: number;
  onLiveAutoTradeBudgetPct?: (v: number) => void;
  fcmPushEnabled?: boolean;
  onFcmPushEnabled?: (v: boolean) => void;
  onFcmTest?: () => void;
  displayTimezone?: string;
  onDisplayTimezoneChange?: (tz: string) => void;
  chartTimeFormat?: string;
  onChartTimeFormatChange?: (format: string) => void;
  tradeAlertTimeFormat?: string;
  onTradeAlertTimeFormatChange?: (format: string) => void;
  menuPermissions?: Record<string, boolean>;
  verificationIssueNotify?: boolean;
  onVerificationIssueNotify?: (v: boolean) => void;
  trendSearchSettings?: TrendSearchAppSettings;
  onTrendSearchSettingsChange?: (s: TrendSearchAppSettings) => void;
  llmSettings?: LlmAppSettings;
  onLlmSettingsChange?: (s: LlmAppSettings) => void;
  /** 설정 화면 진입 시 선택할 카테고리 (예: 모의투자 화면에서 링크) */
  initialCategory?: SettingsCategoryId;
  /** 정식 로그인 여부 (게스트는 매매·전략 설정 탭 차단) */
  isFormalLogin?: boolean;
  onRequireFormalLogin?: () => void;
}

// ── 카테고리 정의 ────────────────────────────────────────────────────────────
type CategoryId = SettingsCategoryId;

interface Category {
  id:    CategoryId;
  label: string;
  icon:  React.ReactNode;
  desc:  string;
}

const IcGeneral = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <circle cx="11" cy="11" r="3"/>
    <path d="M11 2v2M11 18v2M2 11h2M18 11h2M4.93 4.93l1.41 1.41M15.66 15.66l1.41 1.41M4.93 17.07l1.41-1.41M15.66 6.34l1.41-1.41"/>
  </svg>
);
const IcChart = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <polyline points="2,16 7,10 11,13 16,6 20,6"/>
    <line x1="4" y1="19" x2="4" y2="13"/>
    <line x1="8.5" y1="19" x2="8.5" y2="15.5"/>
    <line x1="13" y1="19" x2="13" y2="10"/>
    <line x1="17.5" y1="19" x2="17.5" y2="6"/>
  </svg>
);
const IcStrategy = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <circle cx="11" cy="11" r="9"/>
    <circle cx="11" cy="11" r="3.5"/>
    <line x1="11" y1="2" x2="11" y2="7.5"/>
    <line x1="11" y1="14.5" x2="11" y2="20"/>
    <line x1="2" y1="11" x2="7.5" y2="11"/>
    <line x1="14.5" y1="11" x2="20" y2="11"/>
  </svg>
);
const IcAlert = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <path d="M18 8A7 7 0 0 0 4 8c0 7-3 9-3 9h20s-3-2-3-9"/>
    <path d="M13.73 21a2 2 0 0 1-3.46 0"/>
  </svg>
);
const IcNetwork = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <circle cx="11" cy="11" r="9"/>
    <path d="M11 2C11 2 7 6 7 11s4 9 4 9M11 2c0 0 4 4 4 9s-4 9-4 9"/>
    <line x1="2" y1="11" x2="20" y2="11"/>
  </svg>
);
const IcIndicators = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <polyline points="2,15 6,9 10,11 14,6 20,4"/>
    <line x1="4" y1="19" x2="4" y2="13"/>
    <line x1="8" y1="19" x2="8" y2="15"/>
    <line x1="12" y1="19" x2="12" y2="10"/>
    <line x1="16" y1="19" x2="16" y2="7"/>
  </svg>
);
const IcPaper = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <rect x="3" y="5" width="16" height="12" rx="2"/>
    <line x1="7" y1="9" x2="15" y2="9"/>
    <line x1="7" y1="12" x2="12" y2="12"/>
    <circle cx="16" cy="6" r="2" fill="currentColor" stroke="none"/>
  </svg>
);
const IcVirtual = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <rect x="2" y="6" width="8" height="10" rx="1.5"/>
    <rect x="12" y="4" width="8" height="12" rx="1.5"/>
    <path d="M10 11h2"/>
  </svg>
);
const IcTrading = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <rect x="2" y="5" width="16" height="12" rx="2"/>
    <line x1="7" y1="9" x2="15" y2="9"/>
    <line x1="7" y1="12" x2="12" y2="12"/>
    <path d="M16 2l2 2-2 2"/>
    <path d="M6 20l-2-2 2-2"/>
  </svg>
);
const IcLive = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <circle cx="11" cy="11" r="9"/>
    <path d="M8 11h6M11 8v6"/>
  </svg>
);
const IcTrendSearch = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <polyline points="4,17 9,11 13,14 18,6"/>
    <line x1="4" y1="19" x2="18" y2="19"/>
    <circle cx="18" cy="6" r="2" fill="currentColor" stroke="none"/>
  </svg>
);
const IcLlm = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <path d="M11 3l1.2 3.6L16 8l-3.8 1.4L11 13l-1.2-3.6L6 8l3.8-1.4L11 3z"/>
    <path d="M17 14l.8 2.2L20 17l-2.2.8L17 20l-.8-2.2L14 17l2.2-.8L17 14z"/>
    <rect x="3" y="15" width="6" height="4" rx="1"/>
  </svg>
);
const IcBacktest = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <polyline points="2,17 6,11 9,14 13,7 16,10 20,4"/>
    <circle cx="6"  cy="11" r="1.5" fill="currentColor" stroke="none"/>
    <circle cx="13" cy="7"  r="1.5" fill="currentColor" stroke="none"/>
    <line x1="6"  y1="11" x2="6"  y2="19"/>
    <line x1="13" y1="7"  x2="13" y2="19"/>
  </svg>
);

const IcAdmin = () => (
  <svg width="22" height="22" viewBox="0 0 22 22" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
    <path d="M11 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8z"/>
    <path d="M4 19c0-3.5 3-6 7-6s7 2.5 7 6"/>
    <path d="M16 4l2 2M18 4l-2 2"/>
  </svg>
);

const ALL_CATEGORIES: Category[] = [
  { id: 'general',  label: '일반 설정',  icon: <IcGeneral />,  desc: '언어, 테마, 날짜 형식 등 기본 설정' },
  { id: 'chart',      label: '차트 설정',    icon: <IcChart />,       desc: '캔들 색상, 격자선, 크로스헤어, 가격축 등' },
  { id: 'indicators', label: '보조지표 설정', icon: <IcIndicators />, desc: '지표 추가 Main 탭 보조지표 기본 파라미터·색상·기준선' },
  { id: 'backtest', label: '백테스팅',   icon: <IcBacktest />, desc: '백테스팅 실행 옵션, 자본·비용·리스크 관리 설정' },
  { id: 'strategy', label: '전략 설정',  icon: <IcStrategy />, desc: '투자 전략 기본 파라미터 및 조건 설정' },
  { id: 'trading',  label: '매매설정',   icon: <IcTrading />,  desc: '모의투자·가상매매 통합 설정 — 자동매매, 미체결 주문 처리, 계좌·비용 등' },
  { id: 'live',     label: '실거래',     icon: <IcLive />,     desc: '매매 운영 모드, 업비트 API 키, 실거래 자동매매' },
  { id: 'trend-search', label: '추세검색', icon: <IcTrendSearch />, desc: '상승추세 배점, 합격 점수, 결과 수, 자동갱신·투자대상 자동추가' },
  { id: 'llm',        label: 'LLM 설정', icon: <IcLlm />,        desc: 'AI 전략 검증용 LLM 서버 주소·포트·모델·타임아웃' },
  { id: 'alert',    label: '알림 설정',  icon: <IcAlert />,    desc: '가격 알림, 신호 알림, 알림 방식 설정' },
  { id: 'network',  label: '네트워크',   icon: <IcNetwork />,  desc: 'API 서버 주소, WebSocket, 연결 상태' },
  { id: 'admin',    label: '관리자 설정', icon: <IcAdmin />,    desc: '사용자·역할·메뉴 접근 권한 관리 (관리자 비밀번호 필요)' },
];

function filterCategories(permissions?: Record<string, boolean>): Category[] {
  return ALL_CATEGORIES.filter(c =>
    canAccessMenu(permissions, settingsCategoryToMenuId(c.id)),
  );
}

// ── 설정 행 공통 컴포넌트 ────────────────────────────────────────────────────
const SettingRow: React.FC<{
  label: string;
  desc?: string;
  className?: string;
  children: React.ReactNode;
}> = ({ label, desc, className, children }) => (
  <div className={`stg-row${className ? ` ${className}` : ''}`}>
    <div className="stg-row-label">
      <span className="stg-row-title">{label}</span>
      {desc && <span className="stg-row-desc">{desc}</span>}
    </div>
    <div className="stg-row-ctrl">{children}</div>
  </div>
);

const SettingSection: React.FC<{ title: string; children: React.ReactNode; grid?: boolean }> = ({
  title, children, grid,
}) => (
  <div className="stg-section">
    <div className="stg-section-title">{title}</div>
    <div className={`stg-section-body${grid ? ' stg-section-body--grid' : ''}`}>{children}</div>
  </div>
);

const StgCard: React.FC<{
  label: string;
  desc?: string;
  full?: boolean;
  children: React.ReactNode;
}> = ({ label, desc, full, children }) => (
  <div className={`stg-card${full ? ' stg-card--full' : ''}`}>
    <div className="stg-card-head">
      <span className="stg-card-label">{label}</span>
      {desc && <span className="stg-card-desc">{desc}</span>}
    </div>
    <div className="stg-card-ctrl">{children}</div>
  </div>
);

// ── 각 카테고리 패널 ─────────────────────────────────────────────────────────

interface PaperTradingSettingsPanelProps {
  paperTradingEnabled?: boolean;
  onPaperTradingEnabled?: (v: boolean) => void;
  paperInitialCapital?: number;
  onPaperInitialCapital?: (v: number) => void;
  paperFeeRatePct?: number;
  onPaperFeeRatePct?: (v: number) => void;
  paperSlippagePct?: number;
  onPaperSlippagePct?: (v: number) => void;
  paperMinOrderKrw?: number;
  onPaperMinOrderKrw?: (v: number) => void;
  paperAutoTradeEnabled?: boolean;
  onPaperAutoTradeEnabled?: (v: boolean) => void;
  paperAutoTradeBudgetPct?: number;
  onPaperAutoTradeBudgetPct?: (v: number) => void;
  onPaperAccountReset?: () => void;
}

const PaperTradingSettingsPanel: React.FC<PaperTradingSettingsPanelProps> = ({
  paperTradingEnabled = true,
  onPaperTradingEnabled,
  paperInitialCapital = 10_000_000,
  onPaperInitialCapital,
  paperFeeRatePct = 0.05,
  onPaperFeeRatePct,
  paperSlippagePct = 0,
  onPaperSlippagePct,
  paperMinOrderKrw = 5000,
  onPaperMinOrderKrw,
  paperAutoTradeEnabled = false,
  onPaperAutoTradeEnabled,
  paperAutoTradeBudgetPct = 95,
  onPaperAutoTradeBudgetPct,
  onPaperAccountReset,
}) => (
  <>
    <SettingSection title="모의투자">
      <SettingRow
        label="모의투자 활성화"
        desc="켜면 모의투자 화면·실시간 차트 매매 패널·전략 Match 자동매매가 모의 계좌로 체결됩니다."
      >
        <label className="stg-toggle">
          <input type="checkbox" checked={paperTradingEnabled} onChange={e => onPaperTradingEnabled?.(e.target.checked)} />
          <span className="stg-toggle-slider" />
        </label>
      </SettingRow>
    </SettingSection>
    <SettingSection title="자동매매">
      <SettingRow
        label="자동매매"
        desc="Match 충족 시 모의 계좌로 자동 매수·매도합니다. 모의투자·가상매매 화면 타이틀바에서도 변경할 수 있습니다."
      >
        <label className="stg-toggle">
          <input type="checkbox" checked={paperAutoTradeEnabled} onChange={e => onPaperAutoTradeEnabled?.(e.target.checked)} />
          <span className="stg-toggle-slider" />
        </label>
      </SettingRow>
      <SettingRow
        label="자동 매수 예산 (%)"
        desc="Match 매수 시 주문가능 현금 중 사용 비율. Match 매도는 해당 종목 보유 수량 전량 매도."
      >
        <input
          type="number"
          className="stg-input stg-input--num"
          value={paperAutoTradeBudgetPct}
          min={1}
          max={100}
          step={1}
          disabled={!paperAutoTradeEnabled}
          onChange={e => onPaperAutoTradeBudgetPct?.(Number(e.target.value))}
        />
      </SettingRow>
    </SettingSection>
    <SettingSection title="계좌·비용">
      <SettingRow label="초기 자본 (KRW)" desc="계좌 초기화 시 적용되는 시작 현금입니다.">
        <input
          type="number"
          className="stg-input stg-input--num"
          value={paperInitialCapital}
          min={100000}
          step={100000}
          onChange={e => onPaperInitialCapital?.(Number(e.target.value))}
        />
      </SettingRow>
      <SettingRow label="수수료율 (%)" desc="체결 금액 기준 수수료 (업비트 KRW 마켓 기본 0.05% 참고).">
        <input
          type="number"
          className="stg-input stg-input--num"
          value={paperFeeRatePct}
          min={0}
          max={1}
          step={0.01}
          onChange={e => onPaperFeeRatePct?.(Number(e.target.value))}
        />
      </SettingRow>
      <SettingRow label="슬리피지 (%)" desc="매수는 가격 상승, 매도는 가격 하락으로 체결가를 조정합니다.">
        <input
          type="number"
          className="stg-input stg-input--num"
          value={paperSlippagePct}
          min={0}
          max={5}
          step={0.01}
          onChange={e => onPaperSlippagePct?.(Number(e.target.value))}
        />
      </SettingRow>
      <SettingRow label="최소 주문 금액 (KRW)" desc="이 금액 미만 주문은 거부됩니다.">
        <input
          type="number"
          className="stg-input stg-input--num"
          value={paperMinOrderKrw}
          min={1000}
          step={1000}
          onChange={e => onPaperMinOrderKrw?.(Number(e.target.value))}
        />
      </SettingRow>
    </SettingSection>
    <SettingSection title="계좌 관리">
      <SettingRow
        label="계좌 초기화"
        desc="보유 종목·체결·원장·투자금 한도·미체결 주문을 삭제하고 초기 자본으로 현금을 되돌립니다."
      >
        <button type="button" className="stg-btn stg-btn--danger" onClick={onPaperAccountReset}>
          모의 계좌 초기화
        </button>
      </SettingRow>
    </SettingSection>
  </>
);

// ── 통합 매매설정 패널 ────────────────────────────────────────────────────────

interface TradingSettingsPanelProps {
  paperTradingEnabled?: boolean;
  onPaperTradingEnabled?: (v: boolean) => void;
  paperInitialCapital?: number;
  onPaperInitialCapital?: (v: number) => void;
  paperFeeRatePct?: number;
  onPaperFeeRatePct?: (v: number) => void;
  paperSlippagePct?: number;
  onPaperSlippagePct?: (v: number) => void;
  paperMinOrderKrw?: number;
  onPaperMinOrderKrw?: (v: number) => void;
  paperAutoTradeEnabled?: boolean;
  onPaperAutoTradeEnabled?: (v: boolean) => void;
  paperAutoTradeBudgetPct?: number;
  onPaperAutoTradeBudgetPct?: (v: number) => void;
  paperOrderFillMode?: 'LIMIT_ONLY' | 'NEXT_TICK' | 'AUTO_CANCEL';
  onPaperOrderFillMode?: (v: 'LIMIT_ONLY' | 'NEXT_TICK' | 'AUTO_CANCEL') => void;
  paperOrderAutoCancelPct?: number;
  onPaperOrderAutoCancelPct?: (v: number) => void;
  virtualTargetMaxCount?: number;
  onVirtualTargetMaxCount?: (v: number) => void;
  onPaperAccountReset?: () => void;
}

const TradingSettingsPanel: React.FC<TradingSettingsPanelProps> = ({
  paperTradingEnabled = true,
  onPaperTradingEnabled,
  paperInitialCapital = 10_000_000,
  onPaperInitialCapital,
  paperFeeRatePct = 0.05,
  onPaperFeeRatePct,
  paperSlippagePct = 0,
  onPaperSlippagePct,
  paperMinOrderKrw = 5000,
  onPaperMinOrderKrw,
  paperAutoTradeEnabled = false,
  onPaperAutoTradeEnabled,
  paperAutoTradeBudgetPct = 95,
  onPaperAutoTradeBudgetPct,
  paperOrderFillMode = 'LIMIT_ONLY',
  onPaperOrderFillMode,
  paperOrderAutoCancelPct = 1.0,
  onPaperOrderAutoCancelPct,
  virtualTargetMaxCount = 20,
  onVirtualTargetMaxCount,
  onPaperAccountReset,
}) => (
  <>
    {/* ── 모의투자 ────────────────────────────────────────── */}
    <SettingSection title="모의투자">
      <SettingRow
        label="모의투자 활성화"
        desc="켜면 모의투자 화면·실시간 차트 매매 패널·전략 Match 자동매매가 모의 계좌로 체결됩니다."
      >
        <label className="stg-toggle">
          <input type="checkbox" checked={paperTradingEnabled} onChange={e => onPaperTradingEnabled?.(e.target.checked)} />
          <span className="stg-toggle-slider" />
        </label>
      </SettingRow>
    </SettingSection>

    {/* ── 자동매매 ─────────────────────────────────────────── */}
    <SettingSection title="자동매매">
      <SettingRow
        label="자동매매"
        desc="Match 충족 시 모의 계좌로 자동 매수·매도합니다. 모의투자·가상매매 화면 타이틀바에서도 변경할 수 있습니다."
      >
        <label className="stg-toggle">
          <input type="checkbox" checked={paperAutoTradeEnabled} onChange={e => onPaperAutoTradeEnabled?.(e.target.checked)} />
          <span className="stg-toggle-slider" />
        </label>
      </SettingRow>
      <SettingRow
        label="자동 매수 예산 (%)"
        desc="Match 매수 시 주문가능 현금 중 사용 비율. Match 매도는 해당 종목 보유 수량 전량 매도."
      >
        <input
          type="number"
          className="stg-input stg-input--num"
          value={paperAutoTradeBudgetPct}
          min={1} max={100} step={1}
          disabled={!paperAutoTradeEnabled}
          onChange={e => onPaperAutoTradeBudgetPct?.(Number(e.target.value))}
        />
      </SettingRow>
    </SettingSection>

    {/* ── 미체결 주문 처리 ─────────────────────────────────── */}
    <SettingSection title="미체결 주문 처리">
      <SettingRow
        label="체결 모드"
        desc="수동·자동매매 시 생성된 미체결 주문이 시세 변동 시 어떻게 처리될지 결정합니다."
        className="stg-row--block"
      >
        <div className="stg-radio-row">
          <label className="stg-radio-option stg-radio-option--cyan">
            <input
              type="radio"
              name="stg-fill-mode"
              value="LIMIT_ONLY"
              checked={paperOrderFillMode === 'LIMIT_ONLY'}
              onChange={() => onPaperOrderFillMode?.('LIMIT_ONLY')}
            />
            <span className="stg-radio-option-text">
              <strong>A. 지정가 유지 (기본값)</strong>
              <span>매수: 시세 ≤ 주문가일 때만 체결 / 매도: 시세 ≥ 주문가일 때만 체결. 반대 방향으로 가격이 벗어나면 미체결 유지</span>
            </span>
          </label>
          <label className="stg-radio-option stg-radio-option--blue">
            <input
              type="radio"
              name="stg-fill-mode"
              value="NEXT_TICK"
              checked={paperOrderFillMode === 'NEXT_TICK'}
              onChange={() => onPaperOrderFillMode?.('NEXT_TICK')}
            />
            <span className="stg-radio-option-text">
              <strong>B. 다음 틱 강제 체결</strong>
              <span>주문 생성 직후 첫 번째 시세 틱에서 가격 방향 무관하게 즉시 체결 (시장가와 동일한 효과)</span>
            </span>
          </label>
          <label className="stg-radio-option stg-radio-option--orange">
            <input
              type="radio"
              name="stg-fill-mode"
              value="AUTO_CANCEL"
              checked={paperOrderFillMode === 'AUTO_CANCEL'}
              onChange={() => onPaperOrderFillMode?.('AUTO_CANCEL')}
            />
            <span className="stg-radio-option-text">
              <strong>C. 허용폭 초과 시 자동 취소</strong>
              <span>주문가 대비 설정한 % 이상 가격이 벗어나면 미체결 주문 자동 취소. 허용폭 내이면 지정가 조건으로 체결</span>
            </span>
          </label>
        </div>
      </SettingRow>
      {paperOrderFillMode === 'AUTO_CANCEL' && (
        <SettingRow label="자동 취소 허용 이탈폭 (%)" desc="주문가 대비 이 % 이상 가격이 벗어나면 미체결 주문이 자동 취소됩니다.">
          <input
            type="number"
            className="stg-input stg-input--num"
            value={paperOrderAutoCancelPct}
            min={0.1} max={50} step={0.1}
            onChange={e => onPaperOrderAutoCancelPct?.(Number(e.target.value))}
          />
          <span className="stg-unit">%</span>
        </SettingRow>
      )}
    </SettingSection>

    {/* ── 계좌·비용 ─────────────────────────────────────────── */}
    <SettingSection title="계좌·비용">
      <SettingRow label="초기 자본 (KRW)" desc="계좌 초기화 시 적용되는 시작 현금입니다.">
        <input
          type="number"
          className="stg-input stg-input--num"
          value={paperInitialCapital}
          min={100000} step={100000}
          onChange={e => onPaperInitialCapital?.(Number(e.target.value))}
        />
      </SettingRow>
      <SettingRow label="수수료율 (%)" desc="체결 금액 기준 수수료 (업비트 KRW 마켓 기본 0.05% 참고).">
        <input
          type="number"
          className="stg-input stg-input--num"
          value={paperFeeRatePct}
          min={0} max={1} step={0.01}
          onChange={e => onPaperFeeRatePct?.(Number(e.target.value))}
        />
      </SettingRow>
      <SettingRow label="슬리피지 (%)" desc="매수는 가격 상승, 매도는 가격 하락으로 체결가를 조정합니다.">
        <input
          type="number"
          className="stg-input stg-input--num"
          value={paperSlippagePct}
          min={0} max={5} step={0.01}
          onChange={e => onPaperSlippagePct?.(Number(e.target.value))}
        />
      </SettingRow>
      <SettingRow label="최소 주문 금액 (KRW)" desc="이 금액 미만 주문은 거부됩니다.">
        <input
          type="number"
          className="stg-input stg-input--num"
          value={paperMinOrderKrw}
          min={1000} step={1000}
          onChange={e => onPaperMinOrderKrw?.(Number(e.target.value))}
        />
      </SettingRow>
    </SettingSection>

    {/* ── 가상매매 ─────────────────────────────────────────── */}
    <SettingSection title="가상매매">
      <SettingRow
        label="투자대상 목록 최대 개수"
        desc="가상매매 화면·추세검색에서 등록할 수 있는 투자대상 종목 수 상한입니다."
      >
        <input
          type="number"
          className="stg-input stg-input--num"
          value={virtualTargetMaxCount}
          min={1} max={100} step={1}
          onChange={e => onVirtualTargetMaxCount?.(Number(e.target.value))}
        />
      </SettingRow>
    </SettingSection>

    {/* ── 계좌 관리 ─────────────────────────────────────────── */}
    <SettingSection title="계좌 관리">
      <SettingRow
        label="계좌 초기화"
        desc="보유 종목·체결·원장·투자금 한도·미체결 주문을 삭제하고 초기 자본으로 현금을 되돌립니다."
      >
        <button type="button" className="stg-btn stg-btn--danger" onClick={onPaperAccountReset}>
          모의 계좌 초기화
        </button>
      </SettingRow>
    </SettingSection>
  </>
);

const LiveTradingSettingsPanel: React.FC<{
  tradingMode?: string;
  onTradingMode?: (v: string) => void;
  liveAutoTradeEnabled?: boolean;
  onLiveAutoTradeEnabled?: (v: boolean) => void;
  hasUpbitKeys?: boolean;
  upbitAccessKeyMasked?: string | null;
  onUpbitKeys?: (access: string, secret: string) => void;
  liveAutoTradeBudgetPct?: number;
  onLiveAutoTradeBudgetPct?: (v: number) => void;
}> = ({
  tradingMode = 'PAPER',
  onTradingMode,
  liveAutoTradeEnabled = false,
  onLiveAutoTradeEnabled,
  hasUpbitKeys = false,
  upbitAccessKeyMasked = null,
  onUpbitKeys,
  liveAutoTradeBudgetPct = 95,
  onLiveAutoTradeBudgetPct,
}) => {
  const [accessKey, setAccessKey] = useState('');
  const [secretKey, setSecretKey] = useState('');

  return (
    <>
      <SettingSection title="매매 운영 모드">
        <SettingRow label="실행 대상" desc="모의투자만, 실거래(업비트 API)만, 또는 둘 다 병행할 수 있습니다.">
          <select className="stg-select" value={tradingMode} onChange={e => onTradingMode?.(e.target.value)}>
            <option value="PAPER">모의투자만</option>
            <option value="LIVE">실거래만</option>
            <option value="BOTH">모의 + 실거래 병행</option>
          </select>
        </SettingRow>
      </SettingSection>
      <SettingSection title="실거래 (업비트 Open API)">
        <SettingRow label="실거래 자동매매" desc="ON이면 전략 시그널·손절/익절 시 업비트에 실제 주문합니다. API 키에 주문 권한이 필요합니다.">
          <label className="stg-toggle">
            <input type="checkbox" checked={liveAutoTradeEnabled} onChange={e => onLiveAutoTradeEnabled?.(e.target.checked)} />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        <SettingRow label="API 키 상태" desc={hasUpbitKeys ? `등록됨 (${upbitAccessKeyMasked})` : 'Access / Secret 키를 입력 후 저장하세요.'}>
          <span style={{ fontSize: 12, color: hasUpbitKeys ? 'var(--accent)' : 'var(--text3)' }}>
            {hasUpbitKeys ? '연결됨' : '미등록'}
          </span>
        </SettingRow>
        <SettingRow label="Access Key" desc="입력 내용이 그대로 표시됩니다. 저장 시 DB에 AES 암호화되어 보관됩니다.">
          <input
            type="text"
            className="stg-input stg-input--sensitive"
            placeholder={hasUpbitKeys ? '변경 시 새 Access Key 입력' : 'Access Key'}
            value={accessKey}
            onChange={e => setAccessKey(e.target.value)}
            autoComplete="off"
            spellCheck={false}
          />
        </SettingRow>
        <SettingRow label="Secret Key" desc="입력 내용이 그대로 표시됩니다. 저장 시 암호화되며 API로 전체 키는 반환되지 않습니다.">
          <input
            type="text"
            className="stg-input stg-input--sensitive"
            placeholder={hasUpbitKeys ? '변경 시 새 Secret Key 입력' : 'Secret Key'}
            value={secretKey}
            onChange={e => setSecretKey(e.target.value)}
            autoComplete="off"
            spellCheck={false}
          />
        </SettingRow>
        {(accessKey.trim() || secretKey.trim()) && (
          <SettingRow label="">
            <button
              type="button"
              className="stg-btn stg-btn--primary"
              onClick={() => {
                onUpbitKeys?.(accessKey.trim(), secretKey.trim());
                setAccessKey('');
                setSecretKey('');
              }}
            >
              API 키 저장
            </button>
          </SettingRow>
        )}
        <SettingRow label="실거래 자동매수 예산 (%)" desc="시장가 매수 시 KRW 잔고 대비 사용 비율 (모의투자 예산과 별도)">
          <input type="number" className="stg-input stg-input--narrow" min={1} max={100} step={1}
            value={liveAutoTradeBudgetPct}
            onChange={e => onLiveAutoTradeBudgetPct?.(Number(e.target.value))} />
          <span className="stg-unit">%</span>
        </SettingRow>
      </SettingSection>
    </>
  );
};

// ── 일반 설정 패널 ────────────────────────────────────────────────────────────

const GeneralPanel: React.FC<{
  displayTimezone: string;
  onDisplayTimezoneChange: (tz: string) => void;
  chartTimeFormat?: string;
  onChartTimeFormatChange?: (format: string) => void;
  tradeAlertTimeFormat?: string;
  onTradeAlertTimeFormatChange?: (format: string) => void;
  verificationIssueNotify?: boolean;
  onVerificationIssueNotify?: (v: boolean) => void;
  showVerificationNotify?: boolean;
}> = ({
  displayTimezone,
  onDisplayTimezoneChange,
  chartTimeFormat = 'MM-dd HH:mm',
  onChartTimeFormatChange,
  tradeAlertTimeFormat = 'MM-dd HH:mm',
  onTradeAlertTimeFormatChange,
  verificationIssueNotify = true,
  onVerificationIssueNotify,
  showVerificationNotify = true,
}) => {
  const [lang, setLang] = useState('ko');
  const [startPage, setStart]   = useState('chart');
  const [animations, setAnim]   = useState(true);
  const [tooltips, setTool]     = useState(true);

  return (
    <>
      <SettingSection title="언어 및 지역">
        <SettingRow label="언어" desc="UI 표시 언어를 선택합니다.">
          <select className="stg-select" value={lang} onChange={e => setLang(e.target.value)}>
            <option value="ko">한국어</option>
            <option value="en">English</option>
            <option value="ja">日本語</option>
          </select>
        </SettingRow>
        <SettingRow
          label="차트 날짜, 시간형식"
          desc="캔들·보조지표 차트 하단 시간축·크로스헤어에 적용됩니다. 프리셋 선택 또는 직접 입력(토큰: yyyy, yy, MM, dd, HH, mm, ss)이 가능합니다."
        >
          <ChartTimeFormatPicker
            value={chartTimeFormat}
            onChange={fmt => onChartTimeFormatChange?.(fmt)}
          />
        </SettingRow>
        <SettingRow
          label="매매 시그널 알림 시간 형식"
          desc="매매 시그널 알림 팝업·상세에 표시되는 캔들·수신 시각 형식입니다. 프리셋 선택 또는 직접 입력이 가능합니다."
        >
          <ChartTimeFormatPicker
            value={tradeAlertTimeFormat}
            onChange={fmt => onTradeAlertTimeFormatChange?.(fmt)}
          />
        </SettingRow>
        <SettingRow label="시간대" desc="차트·하단 시계·알림에 사용되는 표시 시간대입니다.">
          <TimezonePicker
            variant="select"
            value={displayTimezone}
            onChange={onDisplayTimezoneChange}
          />
        </SettingRow>
      </SettingSection>

      {showVerificationNotify && (
        <SettingSection title="검증게시판">
          <SettingRow
            label="검증 이슈 등록 알림"
            desc="검증 이슈가 등록되거나 단계가 변경될 때 팝업 알림을 표시합니다."
          >
            <label className="stg-toggle">
              <input
                type="checkbox"
                checked={verificationIssueNotify}
                onChange={e => onVerificationIssueNotify?.(e.target.checked)}
              />
              <span className="stg-toggle-slider" />
            </label>
          </SettingRow>
        </SettingSection>
      )}

      <SettingSection title="시작 화면">
        <SettingRow label="시작 페이지" desc="앱 시작 시 표시될 기본 화면입니다.">
          <select className="stg-select" value={startPage} onChange={e => setStart(e.target.value)}>
            <option value="dashboard">대시보드</option>
            <option value="chart">실시간 차트</option>
            <option value="strategy">투자 전략</option>
            <option value="backtest">백테스팅</option>
          </select>
        </SettingRow>
      </SettingSection>

      <SettingSection title="UI 동작">
        <SettingRow label="애니메이션" desc="화면 전환 및 차트 업데이트 시 애니메이션을 사용합니다.">
          <label className="stg-toggle">
            <input type="checkbox" checked={animations} onChange={e => setAnim(e.target.checked)} />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        <SettingRow label="툴팁 표시" desc="버튼 및 지표에 마우스를 올렸을 때 설명을 표시합니다.">
          <label className="stg-toggle">
            <input type="checkbox" checked={tooltips} onChange={e => setTool(e.target.checked)} />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
      </SettingSection>

      <DesktopUpdatePanel />
    </>
  );
};

function PaneSeparatorStyleFields({
  style,
  onChange,
}: {
  style: PaneSeparatorStyle;
  onChange: (next: PaneSeparatorStyle) => void;
}) {
  return (
    <>
      <SettingRow label="구분선 표시">
        <label className="stg-toggle">
          <input
            type="checkbox"
            checked={style.visible}
            onChange={e => onChange({ ...style, visible: e.target.checked })}
          />
          <span className="stg-toggle-slider" />
        </label>
      </SettingRow>
      {style.visible && (
        <>
          <SettingRow label="선 색상">
            <ColorInput
              value={style.color}
              onChange={color => onChange({ ...style, color })}
              showCompactNative={false}
            />
          </SettingRow>
          <SettingRow label="선 굵기">
            <select
              className="stg-select"
              value={style.width}
              onChange={e => onChange({
                ...style,
                width: Number(e.target.value) as PaneSeparatorStyle['width'],
              })}
            >
              {LINE_WIDTH_OPTIONS.map(w => (
                <option key={w} value={w}>{w}px</option>
              ))}
            </select>
          </SettingRow>
          <SettingRow label="선 유형">
            <select
              className="stg-select"
              value={style.lineStyle}
              onChange={e => onChange({
                ...style,
                lineStyle: e.target.value as PaneSeparatorStyle['lineStyle'],
              })}
            >
              {LINE_STYLE_OPTIONS.map(s => (
                <option key={s} value={s}>{LINE_STYLE_LABELS[s]}</option>
              ))}
            </select>
          </SettingRow>
        </>
      )}
    </>
  );
}

interface ChartPanelProps {
  chartRealtimeSource?: string;
  onChartRealtimeSource?: (v: string) => void;
  magnetMode?:    'off' | 'weak' | 'strong';
  onMagnetMode?:  (m: 'off' | 'weak' | 'strong') => void;
  chartCandleAreaPriceLabels?: boolean;
  onChartCandleAreaPriceLabels?: (v: boolean) => void;
  chartSeriesPriceLabels?: boolean;
  onChartSeriesPriceLabels?: (v: boolean) => void;
  chartVolumeVisible?: boolean;
  onChartVolumeVisible?: (v: boolean) => void;
  chartLiveReceiveHighlight?: boolean;
  onChartLiveReceiveHighlight?: (v: boolean) => void;
  chartLegendOptions?: ChartLegendVisibility;
  onChartLegendOptionsChange?: (patch: Partial<ChartLegendVisibility>) => void;
  chartCrosshairInfoVisible?: boolean;
  onChartCrosshairInfoVisible?: (v: boolean) => void;
  chartCrosshairMarkersVisible?: boolean;
  onChartCrosshairMarkersVisible?: (v: boolean) => void;
  chartHoverToolbarVisible?: boolean;
  onChartHoverToolbarVisible?: (v: boolean) => void;
  chartPaneSeparator?: ChartPaneSeparatorOptions;
  onChartPaneSeparatorChange?: (opts: ChartPaneSeparatorOptions) => void;
  chartTimeFormat?: string;
  onChartTimeFormatChange?: (format: string) => void;
  btShowPrice?: boolean;
  onBtShowPrice?: (v: boolean) => void;
}

const ChartPanel: React.FC<ChartPanelProps> = ({
  chartRealtimeSource = 'BACKEND_STOMP',
  onChartRealtimeSource,
  magnetMode = 'off', onMagnetMode,
  chartCandleAreaPriceLabels = true,
  onChartCandleAreaPriceLabels,
  chartSeriesPriceLabels = true,
  onChartSeriesPriceLabels,
  chartVolumeVisible = true,
  onChartVolumeVisible,
  chartLiveReceiveHighlight = true,
  onChartLiveReceiveHighlight,
  chartLegendOptions,
  onChartLegendOptionsChange,
  chartCrosshairInfoVisible = true,
  onChartCrosshairInfoVisible,
  chartCrosshairMarkersVisible = true,
  onChartCrosshairMarkersVisible,
  chartHoverToolbarVisible = true,
  onChartHoverToolbarVisible,
  chartPaneSeparator,
  onChartPaneSeparatorChange,
  chartTimeFormat = 'MM-dd HH:mm',
  onChartTimeFormatChange,
  btShowPrice = true,
  onBtShowPrice,
}) => {
  const [upColor,   setUp]   = useState('#26a69a');
  const [downColor, setDown] = useState('#ef5350');
  const [bgColor,   setBg]   = useState('#131722');
  const [gridLines, setGrid] = useState(true);
  const [crosshair, setCh]   = useState('normal');
  const [priceScale, setPs]  = useState('right');
  const [precision, setPrec] = useState('2');

  return (
    <>
      <SettingSection title="시간축 표시">
        <SettingRow
          label="시간 포맷"
          desc="차트 하단 시간축·크로스헤어에 표시되는 날짜·시간 형식입니다. 프리셋을 선택하거나 직접 입력할 수 있습니다."
        >
          <ChartTimeFormatPicker
            value={chartTimeFormat}
            onChange={fmt => onChartTimeFormatChange?.(fmt)}
          />
        </SettingRow>
      </SettingSection>

      <SettingSection title="실시간 차트 데이터">
        <SettingRow label="데이터 소스" desc="Blueprint 권장: 백엔드 STOMP. 직연결은 업비트 WebSocket을 프론트가 직접 수신합니다.">
          <select className="stg-select" value={chartRealtimeSource}
            onChange={e => onChartRealtimeSource?.(e.target.value)}>
            <option value="BACKEND_STOMP">백엔드 STOMP (권장)</option>
            <option value="UPBIT_DIRECT">업비트 직연결 (기존)</option>
          </select>
        </SettingRow>
        <SettingRow
          label="실시간 수신 음영처리"
          desc="켜면 실시간 데이터 수신 순간 멀티차트·가상투자 카드 아웃라인에 형광 연두색 glow가 표시됩니다. 끄면 아웃라인 음영 효과가 비활성화됩니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={chartLiveReceiveHighlight}
              onChange={e => onChartLiveReceiveHighlight?.(e.target.checked)}
            />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
      </SettingSection>
      <SettingSection title="캔들 색상">
        <SettingRow label="상승 캔들 색상" desc="양봉(상승) 캔들의 기본 색상입니다.">
          <div className="stg-color-row">
            <input type="color" className="stg-color" value={upColor}   onChange={e => setUp(e.target.value)} />
            <span className="stg-color-label">{upColor}</span>
          </div>
        </SettingRow>
        <SettingRow label="하락 캔들 색상" desc="음봉(하락) 캔들의 기본 색상입니다.">
          <div className="stg-color-row">
            <input type="color" className="stg-color" value={downColor} onChange={e => setDown(e.target.value)} />
            <span className="stg-color-label">{downColor}</span>
          </div>
        </SettingRow>
        <SettingRow label="배경 색상" desc="차트 영역의 배경 색상입니다.">
          <div className="stg-color-row">
            <input type="color" className="stg-color" value={bgColor} onChange={e => setBg(e.target.value)} />
            <span className="stg-color-label">{bgColor}</span>
          </div>
        </SettingRow>
      </SettingSection>

      <SettingSection title="차트 표시 옵션">
        <SettingRow label="격자선 표시" desc="차트 배경에 격자선을 표시합니다.">
          <label className="stg-toggle">
            <input type="checkbox" checked={gridLines} onChange={e => setGrid(e.target.checked)} />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        <SettingRow label="거래량 표시" desc="차트 하단에 거래량 바를 표시합니다.">
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={chartVolumeVisible}
              onChange={e => onChartVolumeVisible?.(e.target.checked)}
            />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        <SettingRow label="크로스헤어 모드" desc="마우스 이동 시 표시되는 십자선 형식입니다.">
          <select className="stg-select" value={crosshair} onChange={e => setCh(e.target.value)}>
            <option value="normal">기본</option>
            <option value="magnet">마그넷</option>
            <option value="hidden">숨기기</option>
          </select>
        </SettingRow>
        <SettingRow
          label="자석모드 (Magnet Mode)"
          desc="켜면 십자선 커서가 가장 가까운 캔들 고점·저점·시가·종가에 자동으로 달라붙습니다. 정확한 지지/저항 선 작도에 유용합니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={magnetMode !== 'off'}
              onChange={e => onMagnetMode?.(e.target.checked ? 'strong' : 'off')}
            />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
      </SettingSection>

      <SettingSection title="지표 가격축 라벨·설명">
        <div className="stg-subsection-label">
          캔들 차트 영역
          <span className="stg-subsection-desc">캔들·거래량 pane 위에 겹쳐 표시되는 지표(이동평균·일목 등)</span>
        </div>
        <SettingRow
          label="가격축 라벨·설명"
          desc="켜면 오버레이 지표 선마다 우측 가격축에 금액과 설명(전환선, 기준선 등)이 표시됩니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={chartCandleAreaPriceLabels}
              onChange={e => onChartCandleAreaPriceLabels?.(e.target.checked)}
            />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        <div className="stg-subsection-label stg-subsection-label--spaced">
          보조지표 영역
          <span className="stg-subsection-desc">캔들 아래 별도 pane에 표시되는 RSI·MACD 등</span>
        </div>
        <SettingRow
          label="가격축 라벨·설명"
          desc="켜면 하단 보조지표 pane의 선마다 우측 가격축에 금액과 설명이 표시됩니다. 끄면 선만 표시됩니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={chartSeriesPriceLabels}
              onChange={e => onChartSeriesPriceLabels?.(e.target.checked)}
            />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
      </SettingSection>

      <SettingSection title="매매 시그널">
        <SettingRow
          label="매수·매도 금액 표시"
          desc="백테스트·실시간 전략 시그널 마커에 체결 금액을 함께 표시합니다. OFF이면 '매수'·'매도'만 표시합니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={btShowPrice}
              onChange={e => onBtShowPrice?.(e.target.checked)}
            />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
      </SettingSection>

      {chartPaneSeparator && onChartPaneSeparatorChange && (
        <SettingSection title="차트 영역 구분선">
          <div className="stg-subsection-label">
            캔들·보조지표
            <span className="stg-subsection-desc">캔들(·거래량) 영역과 첫 보조지표 pane 사이</span>
          </div>
          <PaneSeparatorStyleFields
            style={chartPaneSeparator.mainToIndicator}
            onChange={mainToIndicator => onChartPaneSeparatorChange(
              cloneChartPaneSeparatorOptions({
                ...chartPaneSeparator,
                mainToIndicator,
              }),
            )}
          />
          <div className="stg-subsection-label stg-subsection-label--spaced">
            보조지표 간
            <span className="stg-subsection-desc">보조지표 pane 사이</span>
          </div>
          <PaneSeparatorStyleFields
            style={chartPaneSeparator.indicatorToIndicator}
            onChange={indicatorToIndicator => onChartPaneSeparatorChange(
              cloneChartPaneSeparatorOptions({
                ...chartPaneSeparator,
                indicatorToIndicator,
              }),
            )}
          />
        </SettingSection>
      )}

      <SettingSection title="크로스헤어 정보">
        <SettingRow
          label="크로스헤어 정보 표시"
          desc="마우스로 차트를 가리킬 때 표시되는 시가·고가·저가·종가와 보조지표 수치를 켜거나 끕니다. 종목·현재가·보조지표 이름은 유지됩니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={chartCrosshairInfoVisible}
              onChange={e => onChartCrosshairInfoVisible?.(e.target.checked)}
            />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        <SettingRow
          label="교차점 점 표시"
          desc="켜면 세로 크로스헤어가 캔들·보조지표 선과 만나는 위치에 색 점이 표시됩니다. 끄면 크로스헤어 선만 표시됩니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={chartCrosshairMarkersVisible}
              onChange={e => onChartCrosshairMarkersVisible?.(e.target.checked)}
            />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
      </SettingSection>

      {chartLegendOptions && onChartLegendOptionsChange && (
        <SettingSection title="상단 정보 표시">
          {CHART_LEGEND_SETTING_ITEMS.map(({ key, label, desc }) => (
            <SettingRow key={key} label={label} desc={desc}>
              <label className="stg-toggle">
                <input
                  type="checkbox"
                  checked={chartLegendOptions[key]}
                  onChange={e => onChartLegendOptionsChange({ [key]: e.target.checked })}
                />
                <span className="stg-toggle-slider" />
              </label>
            </SettingRow>
          ))}
        </SettingSection>
      )}

      <SettingSection title="가격축">
        <SettingRow label="가격축 위치" desc="가격 눈금자의 표시 위치입니다.">
          <select className="stg-select" value={priceScale} onChange={e => setPs(e.target.value)}>
            <option value="right">우측</option>
            <option value="left">좌측</option>
            <option value="both">양쪽</option>
          </select>
        </SettingRow>
        <SettingRow label="소수점 자리수" desc="가격 표시 시 소수점 이하 자리수입니다.">
          <select className="stg-select" value={precision} onChange={e => setPrec(e.target.value)}>
            <option value="0">0자리</option>
            <option value="2">2자리</option>
            <option value="4">4자리</option>
            <option value="8">8자리</option>
          </select>
        </SettingRow>
      </SettingSection>

      <SettingSection title="차트 조작 툴바">
        <SettingRow
          label="줌·이동 플로팅 툴바"
          desc="켜면 차트에 마우스를 올렸을 때 하단에 축소(−)·확대(+)·전체 보기·좌우 이동(‹ ›) 버튼이 표시됩니다. 끄면 툴바가 나타나지 않습니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={chartHoverToolbarVisible}
              onChange={e => onChartHoverToolbarVisible?.(e.target.checked)}
            />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
      </SettingSection>
    </>
  );
};

interface StrategyPanelProps {
  liveMarket?:         string;
  liveStrategies?:     { id: number; name: string }[];
  liveSettings?:       LiveStrategySettingsDto;
  watchlistCount?:     number;
  monitoredMarkets?:   string[];
  virtualDriven?:      boolean;
  onClearLiveMarkers?: () => void;
  tradeAlertPopup?:    boolean;
  onTradeAlertPopup?:  (v: boolean) => void;
  onLiveSettingsChange?: (settings: LiveStrategySettingsDto) => void;
}

const StrategyPanel: React.FC<StrategyPanelProps> = ({
  liveMarket = 'KRW-BTC',
  liveStrategies = [],
  liveSettings: liveSettingsProp,
  watchlistCount = 0,
  monitoredMarkets = [],
  virtualDriven = false,
  onClearLiveMarkers,
  tradeAlertPopup = true,
  onTradeAlertPopup,
  onLiveSettingsChange,
}) => {
  const [defaultRisk,  setRisk]  = useState('1');
  const [maxPos,       setMax]   = useState('5');
  const [trailingStop, setTrail] = useState(false);
  const [reentry,      setRe]    = useState(false);
  const [backtestFee,  setFee]   = useState('0.05');
  const [slippage,     setSlip]  = useState('0.01');
  const [evalMetric,   setEval]  = useState('sharpe');

  // ── 실시간 전략 체크 설정 상태 ──────────────────────────────────────────────
  const [liveSettings, setLiveSettings] = useState<LiveStrategySettingsDto>(
    liveSettingsProp ?? {
      market:        liveMarket,
      strategyId:    null,
      isLiveCheck:   false,
      executionType: 'CANDLE_CLOSE',
    },
  );
  const [liveSaving, setLiveSaving] = useState(false);
  const [backtestPositionMode, setBacktestPositionMode] = useState<'LONG_ONLY' | 'SIGNAL_ONLY'>('LONG_ONLY');

  useEffect(() => {
    void loadBacktestSettings().then(s => {
      if (s?.positionMode === 'SIGNAL_ONLY' || s?.positionMode === 'LONG_ONLY') {
        setBacktestPositionMode(s.positionMode);
      }
    });
  }, []);

  useEffect(() => {
    if (liveSettingsProp) setLiveSettings(liveSettingsProp);
  }, [liveSettingsProp]);

  const persistLive = useCallback(async (patch: Partial<LiveStrategySettingsDto>) => {
    if (virtualDriven) return;
    const next: LiveStrategySettingsDto = {
      ...liveSettings,
      ...patch,
      market: liveMarket,
      positionMode: backtestPositionMode,
    };
    if (next.isLiveCheck && next.strategyId != null) {
      const synced = await syncStrategyTimeframesFromLayoutIfNeeded(next.strategyId);
      if (!synced) return;
    }
    setLiveSaving(true);
    try {
      const saved = await saveLiveStrategySettings(next);
      if (saved) {
        setLiveSettings(saved);
        onLiveSettingsChange?.(saved);
      }
      onClearLiveMarkers?.();
    } finally {
      setLiveSaving(false);
    }
  }, [liveSettings, liveMarket, onClearLiveMarkers, onLiveSettingsChange, virtualDriven, backtestPositionMode]);

  const isOn     = liveSettings.isLiveCheck;
  const execType = liveSettings.executionType;
  const stratId  = liveSettings.strategyId;
  const monCount = monitoredMarkets.length;

  return (
    <>
      {/* ── 실시간 전략 체크 ─────────────────────────────────────────────── */}
      <SettingSection title="실시간 전략 체크">
        {virtualDriven && (
          <p className="stg-hint" style={{ marginBottom: 12 }}>
            가상투자 세션이 실행 중입니다. 전략·실행방식·시그널 모드는 <strong>가상투자</strong> 화면에서 변경하세요.
          </p>
        )}
        <SettingRow
          label="모니터링 현황"
          desc={virtualDriven
            ? "가상투자 투자대상 종목이 백엔드에서 실시간 전략 체크 대상입니다."
            : "★ 관심종목으로 등록한 종목이 자동으로 전략 체크 대상입니다. 실시간 체크를 켜고 전략을 선택하세요."}
        >
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'flex-end' }}>
            <span style={{ fontSize: 12, fontWeight: 600 }}>
              관심 <span style={{ color: 'var(--accent)' }}>{watchlistCount}</span>개
              {' · '}체크 대상 <span style={{ color: monCount > 0 ? '#00BCD4' : 'var(--text3)' }}>{monCount}</span>개
            </span>
            {monCount > 0 && (
              <span style={{ fontSize: 10, color: 'var(--text3)', maxWidth: 220, textAlign: 'right' }}>
                {monitoredMarkets.map(m => m.replace('KRW-', '')).join(', ')}
              </span>
            )}
          </div>
        </SettingRow>
        <SettingRow
          label="실시간 체크"
          desc="캔들 차트 위에 매수·매도 시그널을 실시간으로 표시합니다. 설정한 전략 조건을 백엔드에서 지속적으로 평가합니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={isOn}
              disabled={virtualDriven}
              onChange={e => persistLive({ isLiveCheck: e.target.checked })}
            />
            <span className="stg-toggle-slider" />
          </label>
          {liveSaving && <span style={{ fontSize: 10, color: 'var(--text3)', marginLeft: 6 }}>저장 중…</span>}
        </SettingRow>

        <SettingRow
          label="모니터링 종목"
          desc="현재 실시간 차트에서 선택된 종목입니다."
        >
          <span style={{ fontSize: 12, fontWeight: 600, color: 'var(--text)' }}>{liveMarket}</span>
        </SettingRow>

        <SettingRow
          label="전략 선택"
          desc="실시간으로 체크할 투자 전략을 선택합니다. 선택된 전략의 매수·매도 조건을 백엔드에서 실시간으로 평가합니다."
        >
          <select
            className="stg-select"
            disabled={virtualDriven || !isOn}
            value={stratId ?? ''}
            onChange={e => persistLive({ strategyId: e.target.value ? Number(e.target.value) : null })}
            style={{ opacity: isOn ? 1 : 0.45 }}
          >
            <option value="">전략 선택…</option>
            {liveStrategies.map(s => (
              <option key={s.id} value={s.id}>{s.name}</option>
            ))}
          </select>
        </SettingRow>

        <SettingRow
          className="stg-row--block"
          label="체크 방식"
          desc="봉 마감 직후: 완성된 캔들 기준으로 1회 판정합니다. 실시간 틱: 3초 주기로 진행 중인 캔들을 기준으로 판정합니다."
        >
          <div className={`stg-radio-row${!isOn ? ' stg-radio-row--disabled' : ''}`}>
            <label className="stg-radio-option stg-radio-option--cyan">
              <input
                type="radio"
                name="stg-exec-type"
                value="CANDLE_CLOSE"
                checked={execType === 'CANDLE_CLOSE'}
                disabled={virtualDriven || !isOn}
                onChange={() => persistLive({ executionType: 'CANDLE_CLOSE' })}
              />
              <span className="stg-radio-option-text">
                <strong>봉 마감 직후</strong>
                <span>완성된 캔들 기준으로 1회 판정</span>
              </span>
            </label>
            <label className="stg-radio-option stg-radio-option--cyan">
              <input
                type="radio"
                name="stg-exec-type"
                value="REALTIME_TICK"
                checked={execType === 'REALTIME_TICK'}
                disabled={virtualDriven || !isOn}
                onChange={() => persistLive({ executionType: 'REALTIME_TICK' })}
              />
              <span className="stg-radio-option-text">
                <strong>실시간 틱 (3초)</strong>
                <span>진행 중인 캔들 기준, 3초 주기 판정</span>
              </span>
            </label>
          </div>
        </SettingRow>

        {isOn && (
          <div style={{
            display: 'flex', alignItems: 'center', gap: 8,
            padding: '8px 10px', borderRadius: 6,
            background: 'rgba(0,188,212,0.1)', border: '1px solid rgba(0,188,212,0.25)',
            fontSize: 11.5, color: 'var(--text2)',
          }}>
            <span style={{
              width: 7, height: 7, borderRadius: '50%', flexShrink: 0,
              background: '#00BCD4', boxShadow: '0 0 5px rgba(0,188,212,0.6)',
              animation: 'lsp-pulse 1.5s ease-in-out infinite',
            }} />
            {stratId
              ? `${liveStrategies.find(s => s.id === stratId)?.name ?? '전략'} 모니터링 중`
              : '전략을 선택하면 모니터링이 시작됩니다'}
          </div>
        )}

        <SettingRow
          className="stg-row--block"
          label="시그널 모드"
          desc="백테스트 설정의 포지션 종속성 모드와 동일하게 적용됩니다. 변경은 설정 → 백테스트 설정에서 하세요."
        >
          <div style={{
            padding: '8px 10px', borderRadius: 6,
            background: 'rgba(63,126,245,0.08)', border: '1px solid rgba(63,126,245,0.2)',
            fontSize: 12, color: 'var(--text2)',
            opacity: isOn ? 1 : 0.55,
          }}>
            {backtestPositionMode === 'LONG_ONLY'
              ? '보유 자산 기준 (Long-Only) — 매수 이력 있을 때만 매도 허용'
              : '순수 지표 기준 (Signal-Only) — 포지션 무관, 지표 충족 시 즉시 매도'}
          </div>
        </SettingRow>
      </SettingSection>

      {/* ── 매매 알림 팝업 ────────────────────────────────────────────────── */}
      <SettingSection title="매매 알림 팝업">
        <SettingRow
          label="알림 팝업"
          desc="실시간 전략 체크에서 매수·매도 시그널 발생 시 우측 상단 알림 카드를 표시합니다. 차트·전략·설정 등 모든 화면에서 동일하게 표시됩니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={tradeAlertPopup}
              onChange={e => onTradeAlertPopup?.(e.target.checked)}
            />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        {tradeAlertPopup && (
          <div style={{
            display: 'flex', alignItems: 'center', gap: 8,
            padding: '8px 10px', borderRadius: 6,
            background: 'rgba(255,193,7,0.1)', border: '1px solid rgba(255,193,7,0.25)',
            fontSize: 11.5, color: 'var(--text2)',
          }}>
            <span style={{
              width: 7, height: 7, borderRadius: '50%', flexShrink: 0,
              background: '#FFC107', boxShadow: '0 0 5px rgba(255,193,7,0.6)',
              animation: 'lsp-pulse 1.5s ease-in-out infinite',
            }} />
            시그널 발생 시 알림 팝업이 자동으로 표시됩니다
          </div>
        )}
      </SettingSection>

      {/* ── 리스크 관리 ──────────────────────────────────────────────────── */}
      <SettingSection title="리스크 관리" grid>
        <StgCard label="기본 리스크 비율 (%)" desc="진입 시 포지션당 기본 리스크 비율 (자본 대비).">
          <input className="stg-input stg-input--wide" type="number" min="0" max="100" step="0.1"
            value={defaultRisk} onChange={e => setRisk(e.target.value)} />
        </StgCard>
        <StgCard label="최대 동시 포지션" desc="동시에 보유할 수 있는 최대 포지션 수입니다.">
          <input className="stg-input stg-input--wide" type="number" min="1" max="100"
            value={maxPos} onChange={e => setMax(e.target.value)} />
        </StgCard>
        <StgCard label="트레일링 스탑" desc="수익 발생 시 손절선을 자동으로 상향 조정합니다.">
          <label className="stg-toggle">
            <input type="checkbox" checked={trailingStop} onChange={e => setTrail(e.target.checked)} />
            <span className="stg-toggle-slider" />
          </label>
        </StgCard>
        <StgCard label="재진입 허용" desc="청산 후 동일 조건으로 재진입을 허용합니다.">
          <label className="stg-toggle">
            <input type="checkbox" checked={reentry} onChange={e => setRe(e.target.checked)} />
            <span className="stg-toggle-slider" />
          </label>
        </StgCard>
      </SettingSection>

      {/* ── 백테스팅 기본값 ──────────────────────────────────────────────── */}
      <SettingSection title="백테스팅 기본값" grid>
        <StgCard label="수수료율 (%)" desc="백테스팅 시 적용할 매수/매도 수수료율입니다.">
          <input className="stg-input stg-input--wide" type="number" min="0" step="0.001"
            value={backtestFee} onChange={e => setFee(e.target.value)} />
        </StgCard>
        <StgCard label="슬리피지 (%)" desc="백테스팅 시 적용할 슬리피지 비율입니다.">
          <input className="stg-input stg-input--wide" type="number" min="0" step="0.001"
            value={slippage} onChange={e => setSlip(e.target.value)} />
        </StgCard>
        <StgCard label="성과 지표 기준" desc="전략 비교 시 사용할 기본 성과 지표입니다.">
          <select className="stg-select stg-select--wide" value={evalMetric} onChange={e => setEval(e.target.value)}>
            <option value="sharpe">샤프 비율</option>
            <option value="pnl">총 손익률</option>
            <option value="winrate">승률</option>
            <option value="maxdd">최대 낙폭</option>
          </select>
        </StgCard>
      </SettingSection>
    </>
  );
};

interface AlertPanelProps {
  tradeAlertPopup?: boolean;
  onTradeAlertPopup?: (v: boolean) => void;
  tradeAlertSoundEnabled?: boolean;
  onTradeAlertSoundEnabled?: (v: boolean) => void;
  tradeAlertSound?: string;
  onTradeAlertSound?: (v: string) => void;
  tradeAlertPopupPosition?: string;
  onTradeAlertPopupPosition?: (v: string) => void;
  tradeAlertPopupLayout?: string;
  onTradeAlertPopupLayout?: (v: string) => void;
  tradeAlertPopupGridCols?: number;
  onTradeAlertPopupGridCols?: (v: number) => void;
  fcmPushEnabled?: boolean;
  onFcmPushEnabled?: (v: boolean) => void;
  onFcmTest?: () => void;
}

const AlertPanel: React.FC<AlertPanelProps> = ({
  tradeAlertPopup = true,
  onTradeAlertPopup,
  tradeAlertSoundEnabled = true,
  onTradeAlertSoundEnabled,
  tradeAlertSound = 'bell',
  onTradeAlertSound,
  tradeAlertPopupPosition = 'right',
  onTradeAlertPopupPosition,
  tradeAlertPopupLayout = 'stack',
  onTradeAlertPopupLayout,
  tradeAlertPopupGridCols = 2,
  onTradeAlertPopupGridCols,
  fcmPushEnabled = false,
  onFcmPushEnabled,
  onFcmTest,
}) => {
  const [browserNotif, setBn]     = useState(false);
  const [emailAlert,   setEmail]  = useState(false);
  const [emailAddr,    setAddr]   = useState('');
  const [priceAlert,   setPa]     = useState(true);
  const [signalAlert,  setSig]    = useState(true);
  const [cooldown,     setCool]   = useState('60');

  const soundId = normalizeTradeAlertSoundId(tradeAlertSound);
  const popupPosition = normalizeTradeAlertPopupPosition(tradeAlertPopupPosition);
  const popupLayout = normalizeTradeAlertPopupLayout(tradeAlertPopupLayout);
  const gridCols = normalizeTradeAlertGridCols(tradeAlertPopupGridCols);

  const handlePreviewSound = () => {
    if (!tradeAlertSoundEnabled) {
      window.alert('알림 사운드가 꺼져 있습니다. 먼저 사운드를 켜 주세요.');
      return;
    }
    if (soundId === 'silent') {
      window.alert('무음이 선택되었습니다. 알림 발생 시 소리가 재생되지 않습니다.');
      return;
    }
    previewTradeAlertSound(soundId);
  };

  return (
    <>
      <SettingSection title="매매 시그널 알림">
        <SettingRow
          label="알림 팝업"
          desc="시그널 발생 시 알림 카드가 표시됩니다. 차트·전략·설정 등 어떤 화면에 있어도 동일하게 나타납니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={tradeAlertPopup}
              onChange={e => onTradeAlertPopup?.(e.target.checked)}
            />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>

        {tradeAlertPopup && (
          <>
            <SettingRow
              label="표시 위치"
              desc="알림 팝업 목록이 나타날 화면 위치입니다. 변경 즉시 현재 알림에 반영됩니다."
            >
              <div className="stg-alert-layout-options">
                {TRADE_ALERT_POPUP_POSITIONS.map(opt => (
                  <button
                    key={opt.id}
                    type="button"
                    className={`stg-alert-layout-option${popupPosition === opt.id ? ' stg-alert-layout-option--active' : ''}`}
                    onClick={() => onTradeAlertPopupPosition?.(opt.id)}
                  >
                    <strong>{opt.label}</strong>
                    <span>{opt.desc}</span>
                  </button>
                ))}
              </div>
            </SettingRow>

            <SettingRow
              label="표시 방식"
              desc="알림 목록의 배치 방식입니다. 스택·그리드·가로 나열·최신 1건 중 선택할 수 있습니다."
            >
              <div className="stg-alert-layout-options">
                {TRADE_ALERT_POPUP_LAYOUTS.map(opt => (
                  <button
                    key={opt.id}
                    type="button"
                    className={`stg-alert-layout-option${popupLayout === opt.id ? ' stg-alert-layout-option--active' : ''}`}
                    onClick={() => onTradeAlertPopupLayout?.(opt.id)}
                  >
                    <strong>{opt.label}</strong>
                    <span>{opt.desc}</span>
                  </button>
                ))}
              </div>
            </SettingRow>

            {popupLayout === 'grid' && (
              <SettingRow
                label="그리드 열 개수"
                desc="N×N 그리드의 열(가로) 개수입니다. 2~4열까지 설정할 수 있습니다."
              >
                <select
                  className="stg-select stg-select--wide"
                  value={gridCols}
                  onChange={e => onTradeAlertPopupGridCols?.(Number(e.target.value))}
                >
                  {TRADE_ALERT_GRID_COL_OPTIONS.map(n => (
                    <option key={n} value={n}>{n}열 ({n}×{n} 격자)</option>
                  ))}
                </select>
              </SettingRow>
            )}

            <SettingRow
              label="배치 미리보기"
              desc="선택한 위치·방식으로 알림이 표시되는 모습입니다. 현재 표시 중인 알림에도 즉시 적용됩니다."
            >
              <TradeAlertPopupPreview
                position={popupPosition}
                layout={popupLayout}
                gridCols={gridCols}
                enabled={tradeAlertPopup}
              />
            </SettingRow>
          </>
        )}

        <SettingRow
          label="알림 사운드"
          desc="실시간 전략에서 매수·매도 시그널이 발생하면 선택한 알림음을 재생합니다. 팝업 표시 여부와 별도로 동작합니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={tradeAlertSoundEnabled}
              onChange={e => onTradeAlertSoundEnabled?.(e.target.checked)}
            />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        <SettingRow
          label="알림음 선택"
          desc="시그널 발생 시 재생할 알림음을 선택합니다."
        >
          <div className="stg-sound-row">
            <select
              className="stg-select stg-select--wide"
              value={soundId}
              disabled={!tradeAlertSoundEnabled}
              onChange={e => onTradeAlertSound?.(e.target.value)}
            >
              {TRADE_ALERT_SOUND_OPTIONS.map(opt => (
                <option key={opt.id} value={opt.id}>{opt.label}</option>
              ))}
            </select>
            <button
              type="button"
              className="stg-btn-secondary"
              disabled={!tradeAlertSoundEnabled}
              onClick={handlePreviewSound}
            >
              미리듣기
            </button>
          </div>
        </SettingRow>
      </SettingSection>

      <SettingSection title="FCM 푸시 (모바일·브라우저)">
        <SettingRow label="FCM 푸시" desc="Firebase 설정(VITE_FIREBASE_*) 및 서버 firebase-service-account.json 필요">
          <label className="stg-toggle">
            <input type="checkbox" checked={fcmPushEnabled}
              onChange={e => onFcmPushEnabled?.(e.target.checked)} />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        <SettingRow label="테스트 발송" desc="등록된 FCM 토큰으로 테스트 알림을 보냅니다.">
          <button type="button" className="stg-btn-secondary" onClick={onFcmTest}>FCM 테스트</button>
        </SettingRow>
      </SettingSection>

      <SettingSection title="알림 방식">
        <SettingRow label="브라우저 알림 (예정)" desc="OS 푸시 알림을 통해 백그라운드에서도 알림을 받습니다.">
          <label className="stg-toggle">
            <input type="checkbox" checked={browserNotif} onChange={e => setBn(e.target.checked)} disabled />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        <SettingRow label="이메일 알림" desc="지정한 이메일 주소로 알림 메시지를 발송합니다.">
          <label className="stg-toggle">
            <input type="checkbox" checked={emailAlert} onChange={e => setEmail(e.target.checked)} />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        {emailAlert && (
          <SettingRow label="이메일 주소" desc="알림을 수신할 이메일 주소를 입력하세요.">
            <input className="stg-input stg-input--wide" type="email" placeholder="user@example.com"
              value={emailAddr} onChange={e => setAddr(e.target.value)} />
          </SettingRow>
        )}
      </SettingSection>

      <SettingSection title="알림 조건">
        <SettingRow label="가격 알림" desc="설정된 가격 도달 시 알림을 발생시킵니다.">
          <label className="stg-toggle">
            <input type="checkbox" checked={priceAlert} onChange={e => setPa(e.target.checked)} />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        <SettingRow label="매매 신호 알림" desc="전략 조건에 의한 매수/매도 신호 발생 시 알림을 줍니다.">
          <label className="stg-toggle">
            <input type="checkbox" checked={signalAlert} onChange={e => setSig(e.target.checked)} />
            <span className="stg-toggle-slider" />
          </label>
        </SettingRow>
        <SettingRow label="알림 재발송 대기 (초)" desc="동일 조건의 알림이 반복 발송되지 않도록 대기하는 시간입니다.">
          <input className="stg-input" type="number" min="0" max="3600"
            value={cooldown} onChange={e => setCool(e.target.value)} />
        </SettingRow>
      </SettingSection>
    </>
  );
};

const NetworkPanel: React.FC = () => {
  const [apiHost,   setApi]  = useState('http://localhost:8080');
  const [wsHost,    setWs]   = useState('ws://localhost:8080/ws');
  const [timeout,   setTo]   = useState('10000');
  const [retries,   setRt]   = useState('3');
  const [proxyUrl,  setProxy] = useState('');
  const [connected, _]       = useState(true);

  const handleTest = () => {
    fetch(apiHost + '/actuator/health')
      .then(() => alert('✅ 서버 연결 성공'))
      .catch(() => alert('❌ 서버 연결 실패'));
  };

  return (
    <>
      <SettingSection title="API 서버">
        <SettingRow label="서버 주소" desc="백엔드 REST API 서버의 호스트 주소입니다. 포트 포함.">
          <input className="stg-input stg-input--wide" type="text"
            value={apiHost} onChange={e => setApi(e.target.value)}
            placeholder="http://localhost:8080" />
        </SettingRow>
        <SettingRow label="연결 상태" desc="현재 API 서버와의 연결 상태입니다.">
          <span className={`stg-badge ${connected ? 'stg-badge--ok' : 'stg-badge--err'}`}>
            {connected ? '● 연결됨' : '● 연결 끊김'}
          </span>
        </SettingRow>
        <SettingRow label="" desc="">
          <button className="stg-btn-test" onClick={handleTest}>연결 테스트</button>
        </SettingRow>
      </SettingSection>

      <SettingSection title="WebSocket">
        <SettingRow label="WebSocket 주소" desc="실시간 시세 수신에 사용되는 WebSocket 서버 주소입니다.">
          <input className="stg-input stg-input--wide" type="text"
            value={wsHost} onChange={e => setWs(e.target.value)}
            placeholder="ws://localhost:8080/ws" />
        </SettingRow>
      </SettingSection>

      <SettingSection title="연결 설정">
        <SettingRow label="연결 타임아웃 (ms)" desc="요청 후 응답이 없을 때 연결을 종료하는 시간(밀리초)입니다.">
          <input className="stg-input" type="number" min="1000" step="1000"
            value={timeout} onChange={e => setTo(e.target.value)} />
        </SettingRow>
        <SettingRow label="재시도 횟수" desc="연결 실패 시 자동 재시도 횟수입니다.">
          <input className="stg-input" type="number" min="0" max="10"
            value={retries} onChange={e => setRt(e.target.value)} />
        </SettingRow>
        <SettingRow label="프록시 URL" desc="프록시 서버를 통해 API 요청을 라우팅합니다. 비어 있으면 직접 연결합니다.">
          <input className="stg-input stg-input--wide" type="text"
            value={proxyUrl} onChange={e => setProxy(e.target.value)}
            placeholder="http://proxy:8888 (선택)" />
        </SettingRow>
      </SettingSection>
    </>
  );
};

// ── 메인 컴포넌트 ─────────────────────────────────────────────────────────────
const SettingsPage: React.FC<SettingsPageProps> = ({
  theme,
  magnetMode = 'off',
  onMagnetMode,
  btAutoPopup = true,
  onBtAutoPopup,
  btShowPrice = true,
  onBtShowPrice,
  liveMarket = 'KRW-BTC',
  liveStrategies = [],
  onClearLiveMarkers,
  tradeAlertPopup = true,
  onTradeAlertPopup,
  liveSettings,
  onLiveSettingsChange,
  watchlistCount = 0,
  monitoredMarkets = [],
  virtualDriven = false,
  getIndicatorParams,
  saveIndicatorParams,
  getIndicatorVisual,
  saveIndicatorVisual,
  saveAllIndicatorDefaults,
  indicatorSettingsRevision = 0,
  chartIndicators = [],
  onAddIndicatorToChart,
  onRemoveIndicatorFromChart,
  onIndicatorListOrderChange,
  chartCandleAreaPriceLabels = true,
  onChartCandleAreaPriceLabels,
  chartSeriesPriceLabels = true,
  onChartSeriesPriceLabels,
  chartVolumeVisible = true,
  onChartVolumeVisible,
  chartLiveReceiveHighlight = true,
  onChartLiveReceiveHighlight,
  chartLegendOptions,
  onChartLegendOptionsChange,
  chartCrosshairInfoVisible = true,
  onChartCrosshairInfoVisible,
  chartCrosshairMarkersVisible = true,
  onChartCrosshairMarkersVisible,
  chartHoverToolbarVisible = true,
  onChartHoverToolbarVisible,
  chartPaneSeparator,
  onChartPaneSeparatorChange,
  tradeAlertSoundEnabled = true,
  onTradeAlertSoundEnabled,
  tradeAlertSound = 'bell',
  onTradeAlertSound,
  tradeAlertPopupPosition = 'right',
  onTradeAlertPopupPosition,
  tradeAlertPopupLayout = 'stack',
  onTradeAlertPopupLayout,
  tradeAlertPopupGridCols = 2,
  onTradeAlertPopupGridCols,
  paperTradingEnabled = true,
  onPaperTradingEnabled,
  paperInitialCapital = 10_000_000,
  onPaperInitialCapital,
  paperFeeRatePct = 0.05,
  onPaperFeeRatePct,
  paperSlippagePct = 0,
  onPaperSlippagePct,
  paperMinOrderKrw = 5000,
  onPaperMinOrderKrw,
  paperAutoTradeEnabled = false,
  onPaperAutoTradeEnabled,
  paperAutoTradeBudgetPct = 95,
  onPaperAutoTradeBudgetPct,
  paperOrderFillMode = 'LIMIT_ONLY',
  onPaperOrderFillMode,
  paperOrderAutoCancelPct = 1.0,
  onPaperOrderAutoCancelPct,
  virtualTargetMaxCount = 20,
  onVirtualTargetMaxCount,
  onPaperAccountReset,
  tradingMode = 'PAPER',
  onTradingMode,
  liveAutoTradeEnabled = false,
  onLiveAutoTradeEnabled,
  hasUpbitKeys = false,
  upbitAccessKeyMasked = null,
  onUpbitKeys,
  chartRealtimeSource = 'BACKEND_STOMP',
  onChartRealtimeSource,
  liveAutoTradeBudgetPct = 95,
  onLiveAutoTradeBudgetPct,
  fcmPushEnabled = false,
  onFcmPushEnabled,
  onFcmTest,
  displayTimezone = 'Asia/Seoul',
  onDisplayTimezoneChange,
  chartTimeFormat = 'MM-dd HH:mm',
  onChartTimeFormatChange,
  tradeAlertTimeFormat = 'MM-dd HH:mm',
  onTradeAlertTimeFormatChange,
  menuPermissions,
  verificationIssueNotify = true,
  onVerificationIssueNotify,
  trendSearchSettings,
  onTrendSearchSettingsChange,
  llmSettings,
  onLlmSettingsChange,
  initialCategory,
  isFormalLogin = true,
  onRequireFormalLogin,
}) => {
  const categories = filterCategories(menuPermissions);
  const [active, setActive] = useState<CategoryId>(initialCategory ?? 'general');
  const [indicatorSaveUi, setIndicatorSaveUi] = useState<IndicatorSaveUiState | null>(null);

  useEffect(() => {
    if (categories.length === 0) return;
    if (!categories.some(c => c.id === active)) {
      setActive(categories[0].id);
    }
  }, [categories, active]);

  useEffect(() => {
    if (!initialCategory) return;
    if (!isFormalLogin && isTradingSettingsCategory(initialCategory)) {
      onRequireFormalLogin?.();
      return;
    }
    if (categories.some(c => c.id === initialCategory)) {
      setActive(initialCategory);
    }
  }, [initialCategory, categories, isFormalLogin, onRequireFormalLogin]);

  useEffect(() => {
    if (active !== 'indicators') setIndicatorSaveUi(null);
  }, [active]);

  const activeCat = categories.find(c => c.id === active) ?? categories[0];

  if (!activeCat) {
    return (
      <div className="settings-page">
        <p className="stg-ind-unavailable">접근 가능한 설정 메뉴가 없습니다.</p>
      </div>
    );
  }

  const renderPanel = () => {
    switch (active) {
      case 'chart':    return (
        <ChartPanel
          chartRealtimeSource={chartRealtimeSource}
          onChartRealtimeSource={onChartRealtimeSource}
          magnetMode={magnetMode}
          onMagnetMode={onMagnetMode}
          chartCandleAreaPriceLabels={chartCandleAreaPriceLabels}
          onChartCandleAreaPriceLabels={onChartCandleAreaPriceLabels}
          chartSeriesPriceLabels={chartSeriesPriceLabels}
          onChartSeriesPriceLabels={onChartSeriesPriceLabels}
          chartVolumeVisible={chartVolumeVisible}
          onChartVolumeVisible={onChartVolumeVisible}
          chartLiveReceiveHighlight={chartLiveReceiveHighlight}
          onChartLiveReceiveHighlight={onChartLiveReceiveHighlight}
          chartLegendOptions={chartLegendOptions}
          onChartLegendOptionsChange={onChartLegendOptionsChange}
          chartCrosshairInfoVisible={chartCrosshairInfoVisible}
          onChartCrosshairInfoVisible={onChartCrosshairInfoVisible}
          chartCrosshairMarkersVisible={chartCrosshairMarkersVisible}
          onChartCrosshairMarkersVisible={onChartCrosshairMarkersVisible}
          chartHoverToolbarVisible={chartHoverToolbarVisible}
          onChartHoverToolbarVisible={onChartHoverToolbarVisible}
          chartPaneSeparator={chartPaneSeparator}
          onChartPaneSeparatorChange={onChartPaneSeparatorChange}
          chartTimeFormat={chartTimeFormat}
          onChartTimeFormatChange={onChartTimeFormatChange}
          btShowPrice={btShowPrice}
          onBtShowPrice={onBtShowPrice}
        />
      );
      case 'indicators':
        if (
          !getIndicatorParams || !saveIndicatorParams || !getIndicatorVisual || !saveIndicatorVisual
          || !saveAllIndicatorDefaults
          || !onAddIndicatorToChart || !onRemoveIndicatorFromChart
        ) {
          return (
            <p className="stg-ind-unavailable">보조지표 설정을 불러올 수 없습니다. 차트 화면에서 다시 시도해 주세요.</p>
          );
        }
        return (
          <IndicatorMainDefaultsPanel
            chartIndicators={chartIndicators}
            onAddToChart={onAddIndicatorToChart}
            onRemoveFromChart={onRemoveIndicatorFromChart}
            getParams={getIndicatorParams}
            saveParams={saveIndicatorParams}
            getVisualConfig={getIndicatorVisual}
            saveVisual={saveIndicatorVisual}
            saveAllDefaults={saveAllIndicatorDefaults}
            onSaveUiState={setIndicatorSaveUi}
            settingsRevision={indicatorSettingsRevision}
            onListOrderChange={onIndicatorListOrderChange}
          />
        );
      case 'backtest': return (
        <BacktestSettingsPanel
          btAutoPopup={btAutoPopup}
          onBtAutoPopup={onBtAutoPopup}
          btShowPrice={btShowPrice}
          onBtShowPrice={onBtShowPrice}
        />
      );
      case 'general':  return (
        <GeneralPanel
          displayTimezone={displayTimezone}
          onDisplayTimezoneChange={onDisplayTimezoneChange ?? (() => {})}
          chartTimeFormat={chartTimeFormat}
          onChartTimeFormatChange={onChartTimeFormatChange}
          tradeAlertTimeFormat={tradeAlertTimeFormat}
          onTradeAlertTimeFormatChange={onTradeAlertTimeFormatChange}
          verificationIssueNotify={verificationIssueNotify}
          onVerificationIssueNotify={onVerificationIssueNotify}
          showVerificationNotify={menuPermissions == null || menuPermissions['verification-board'] === true}
        />
      );
      case 'admin': return (
        <AdminPasswordGate>
          <AdminSettingsPanel />
        </AdminPasswordGate>
      );
      case 'trading':
      case 'paper':
      case 'virtual':
        return (
          <TradingSettingsPanel
            paperTradingEnabled={paperTradingEnabled}
            onPaperTradingEnabled={onPaperTradingEnabled}
            paperInitialCapital={paperInitialCapital}
            onPaperInitialCapital={onPaperInitialCapital}
            paperFeeRatePct={paperFeeRatePct}
            onPaperFeeRatePct={onPaperFeeRatePct}
            paperSlippagePct={paperSlippagePct}
            onPaperSlippagePct={onPaperSlippagePct}
            paperMinOrderKrw={paperMinOrderKrw}
            onPaperMinOrderKrw={onPaperMinOrderKrw}
            paperAutoTradeEnabled={paperAutoTradeEnabled}
            onPaperAutoTradeEnabled={onPaperAutoTradeEnabled}
            paperAutoTradeBudgetPct={paperAutoTradeBudgetPct}
            onPaperAutoTradeBudgetPct={onPaperAutoTradeBudgetPct}
            paperOrderFillMode={paperOrderFillMode}
            onPaperOrderFillMode={onPaperOrderFillMode}
            paperOrderAutoCancelPct={paperOrderAutoCancelPct}
            onPaperOrderAutoCancelPct={onPaperOrderAutoCancelPct}
            virtualTargetMaxCount={virtualTargetMaxCount}
            onVirtualTargetMaxCount={onVirtualTargetMaxCount}
            onPaperAccountReset={onPaperAccountReset}
          />
        );
      case 'live': return (
        <LiveTradingSettingsPanel
          tradingMode={tradingMode}
          onTradingMode={onTradingMode}
          liveAutoTradeEnabled={liveAutoTradeEnabled}
          onLiveAutoTradeEnabled={onLiveAutoTradeEnabled}
          hasUpbitKeys={hasUpbitKeys}
          upbitAccessKeyMasked={upbitAccessKeyMasked}
          onUpbitKeys={onUpbitKeys}
          liveAutoTradeBudgetPct={liveAutoTradeBudgetPct}
          onLiveAutoTradeBudgetPct={onLiveAutoTradeBudgetPct}
        />
      );
      case 'trend-search': return (
        trendSearchSettings && onTrendSearchSettingsChange ? (
          <TrendSearchSettingsPanel
            settings={trendSearchSettings}
            onChange={onTrendSearchSettingsChange}
          />
        ) : (
          <p className="stg-ind-unavailable">추세검색 설정을 불러올 수 없습니다.</p>
        )
      );
      case 'llm': return (
        llmSettings && onLlmSettingsChange ? (
          <LlmSettingsPanel
            settings={llmSettings}
            onChange={onLlmSettingsChange}
          />
        ) : (
          <p className="stg-ind-unavailable">LLM 설정을 불러올 수 없습니다.</p>
        )
      );
      case 'strategy': return (
        <StrategyPanel
          liveMarket={liveMarket}
          liveStrategies={liveStrategies}
          liveSettings={liveSettings}
          watchlistCount={watchlistCount}
          monitoredMarkets={monitoredMarkets}
          virtualDriven={virtualDriven}
          onClearLiveMarkers={onClearLiveMarkers}
          tradeAlertPopup={tradeAlertPopup}
          onTradeAlertPopup={onTradeAlertPopup}
          onLiveSettingsChange={onLiveSettingsChange}
        />
      );
      case 'alert':    return (
        <AlertPanel
          tradeAlertPopup={tradeAlertPopup}
          onTradeAlertPopup={onTradeAlertPopup}
          tradeAlertSoundEnabled={tradeAlertSoundEnabled}
          onTradeAlertSoundEnabled={onTradeAlertSoundEnabled}
          tradeAlertSound={tradeAlertSound}
          onTradeAlertSound={onTradeAlertSound}
          tradeAlertPopupPosition={tradeAlertPopupPosition}
          onTradeAlertPopupPosition={onTradeAlertPopupPosition}
          tradeAlertPopupLayout={tradeAlertPopupLayout}
          onTradeAlertPopupLayout={onTradeAlertPopupLayout}
          tradeAlertPopupGridCols={tradeAlertPopupGridCols}
          onTradeAlertPopupGridCols={onTradeAlertPopupGridCols}
          fcmPushEnabled={fcmPushEnabled}
          onFcmPushEnabled={onFcmPushEnabled}
          onFcmTest={onFcmTest}
        />
      );
      case 'network':  return <NetworkPanel />;
    }
  };

  return (
    <div className={`stg-page stg-page--${theme}`}>
      {/* ── 좌측 사이드바 ── */}
      <aside className="stg-sidebar">
        <div className="stg-sidebar-title">설정</div>
        <nav className="stg-sidebar-nav">
          {categories.map(cat => (
            <button
              key={cat.id}
              className={`stg-sidebar-item ${active === cat.id ? 'stg-sidebar-item--active' : ''}`}
              onClick={() => {
                if (!isFormalLogin && isTradingSettingsCategory(cat.id)) {
                  onRequireFormalLogin?.();
                  return;
                }
                setActive(cat.id);
              }}
            >
              <span className="stg-sidebar-icon">{cat.icon}</span>
              <span className="stg-sidebar-label" style={{ fontSize: 13.5, fontWeight: 'inherit' }}>{cat.label}</span>
            </button>
          ))}
        </nav>
      </aside>

      {/* ── 우측 콘텐츠 ── */}
      <main className="stg-content">
        {/* 헤더 */}
        <div className="stg-content-header">
          <div className="stg-content-icon">{activeCat.icon}</div>
          <div className="stg-content-header-text">
            <h2 className="stg-content-title">{activeCat.label}</h2>
            <p className="stg-content-desc">{activeCat.desc}</p>
          </div>
          {active === 'indicators' && indicatorSaveUi && (
            <div className="stg-content-header-actions">
              {indicatorSaveUi.saveMessage && (
                <span
                  className={`stg-ind-save-msg stg-ind-save-msg--inline${
                    indicatorSaveUi.saveMessage.includes('실패') ? ' stg-ind-save-msg--err' : ''
                  }`}
                >
                  {indicatorSaveUi.saveMessage}
                </span>
              )}
              <button
                type="button"
                className="stg-btn-save"
                disabled={!indicatorSaveUi.dirty || indicatorSaveUi.saving}
                onClick={() => void indicatorSaveUi.save()}
              >
                {indicatorSaveUi.saving ? '저장 중…' : '저장'}
              </button>
            </div>
          )}
        </div>

        {/* 설정 패널 */}
        <div className="stg-content-body">
          {renderPanel()}
        </div>

        {/* 저장 버튼 (보조지표 설정은 변경 시 DB 자동 저장) */}
        {active !== 'indicators' && (
          <div className="stg-footer">
            <button className="stg-btn-reset">초기화</button>
            <button className="stg-btn-save">저장</button>
          </div>
        )}
      </main>
    </div>
  );
};

export default SettingsPage;
