/**
 * 설정 페이지
 * macOS 시스템 환경설정 스타일 — 좌측 카테고리 목록 + 우측 설정 패널
 */
import React, { useState, useEffect, useCallback } from 'react';
import type { Theme } from '../types';
import {
  saveLiveStrategySettings,
  type LiveStrategySettingsDto,
} from '../utils/backendApi';
import { BacktestSettingsPanel } from './BacktestSettingsPanel';
import IndicatorMainDefaultsPanel from './IndicatorMainDefaultsPanel';
import type { IndicatorConfig } from '../types';
import type { PlotDef, HLineDef } from '../utils/indicatorRegistry';
import type { IchimokuCloudColors } from '../utils/ichimokuConfig';
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 {
  CHART_LEGEND_SETTING_ITEMS,
  type ChartLegendVisibility,
} from '../types/chartLegend';
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[];
  /** 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;
  /** 설정 화면에서 차트 on/off 연동 */
  chartIndicators?:     IndicatorConfig[];
  onAddIndicatorToChart?:    (type: string, config?: IndicatorConfig) => void;
  onRemoveIndicatorFromChart?: (type: string) => void;
  chartSeriesPriceLabels?: boolean;
  onChartSeriesPriceLabels?: (v: boolean) => void;
  chartVolumeVisible?: boolean;
  onChartVolumeVisible?: (v: boolean) => void;
  chartLegendOptions?: ChartLegendVisibility;
  onChartLegendOptionsChange?: (patch: Partial<ChartLegendVisibility>) => 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;
  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;
  menuPermissions?: Record<string, boolean>;
}

// ── 카테고리 정의 ────────────────────────────────────────────────────────────
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 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: 'paper',    label: '모의투자',   icon: <IcPaper />,    desc: '가상 자본, 수수료, 자동매매 ON/OFF 등 모의투자 설정' },
  { 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 PaperPanelProps {
  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;
  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;
}

const PaperPanel: React.FC<PaperPanelProps> = ({
  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,
  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="모의투자 사용">
      <SettingRow label="모의투자 활성화" desc="켜면 차트 매수·매도와 전략 시그널이 가상 계좌로 체결됩니다.">
        <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={
          paperAutoTradeEnabled
            ? 'ON: 실시간 전략이 켜진 상태에서 BUY/SELL 시그널 발생 시 아래 조건으로 모의 계좌에 자동 체결됩니다.'
            : 'OFF: 시그널은 알림·차트 표시만 하며, 매매는 차트 우측 매수·매도 패널에서 직접 주문합니다.'
        }
      >
        <label className="stg-toggle">
          <input type="checkbox" checked={paperAutoTradeEnabled} onChange={e => onPaperAutoTradeEnabled?.(e.target.checked)} />
          <span className="stg-toggle-slider" />
        </label>
        <span className={`stg-badge ${paperAutoTradeEnabled ? 'stg-badge--on' : 'stg-badge--off'}`}>
          {paperAutoTradeEnabled ? 'ON' : 'OFF'}
        </span>
      </SettingRow>
      <SettingRow
        label="자동 매수 예산 (%)"
        desc="BUY 시그널 시 주문가능 현금 중 사용 비율. SELL 시그널은 해당 종목 보유 수량 전량 매도."
      >
        <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>
      {!paperAutoTradeEnabled && (
        <p className="stg-hint">자동매매가 꺼져 있으면 전략 시그널로는 주문되지 않습니다. 수동 주문만 모의 계좌에 반영됩니다.</p>
      )}
    </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>
    <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;
}> = ({ displayTimezone, onDisplayTimezoneChange }) => {
  const [lang, setLang]         = useState('ko');
  const [dateFormat, setDateFmt] = useState('YYYY-MM-DD');
  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="차트 및 UI에서 날짜를 표시하는 방식입니다.">
          <select className="stg-select" value={dateFormat} onChange={e => setDateFmt(e.target.value)}>
            <option value="YYYY-MM-DD">YYYY-MM-DD</option>
            <option value="DD/MM/YYYY">DD/MM/YYYY</option>
            <option value="MM/DD/YYYY">MM/DD/YYYY</option>
          </select>
        </SettingRow>
        <SettingRow label="시간대" desc="차트·하단 시계·알림에 사용되는 표시 시간대입니다.">
          <TimezonePicker
            variant="select"
            value={displayTimezone}
            onChange={onDisplayTimezoneChange}
          />
        </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>
    </>
  );
};

interface ChartPanelProps {
  chartRealtimeSource?: string;
  onChartRealtimeSource?: (v: string) => void;
  magnetMode?:    'off' | 'weak' | 'strong';
  onMagnetMode?:  (m: 'off' | 'weak' | 'strong') => void;
  chartSeriesPriceLabels?: boolean;
  onChartSeriesPriceLabels?: (v: boolean) => void;
  chartVolumeVisible?: boolean;
  onChartVolumeVisible?: (v: boolean) => void;
  chartLegendOptions?: ChartLegendVisibility;
  onChartLegendOptionsChange?: (patch: Partial<ChartLegendVisibility>) => void;
}

const ChartPanel: React.FC<ChartPanelProps> = ({
  chartRealtimeSource = 'BACKEND_STOMP',
  onChartRealtimeSource,
  magnetMode = 'off', onMagnetMode,
  chartSeriesPriceLabels = true,
  onChartSeriesPriceLabels,
  chartVolumeVisible = true,
  onChartVolumeVisible,
  chartLegendOptions,
  onChartLegendOptionsChange,
}) => {
  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="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>
      </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>
        <SettingRow
          label="지표 가격축 라벨·설명"
          desc="켜면 보조지표 선마다 우측 가격축에 금액 하이라이트와 설명(전환선, 기준선 등)이 표시됩니다. 끄면 선만 표시됩니다."
        >
          <label className="stg-toggle">
            <input
              type="checkbox"
              checked={chartSeriesPriceLabels}
              onChange={e => onChartSeriesPriceLabels?.(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>
    </>
  );
};

interface StrategyPanelProps {
  liveMarket?:         string;
  liveStrategies?:     { id: number; name: string }[];
  liveSettings?:       LiveStrategySettingsDto;
  watchlistCount?:     number;
  monitoredMarkets?:   string[];
  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 = [],
  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);

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

  const persistLive = useCallback(async (patch: Partial<LiveStrategySettingsDto>) => {
    const next: LiveStrategySettingsDto = { ...liveSettings, ...patch, market: liveMarket };
    setLiveSaving(true);
    try {
      const saved = await saveLiveStrategySettings(next);
      if (saved) {
        setLiveSettings(saved);
        onLiveSettingsChange?.(saved);
      }
      onClearLiveMarkers?.();
    } finally {
      setLiveSaving(false);
    }
  }, [liveSettings, liveMarket, onClearLiveMarkers, onLiveSettingsChange]);

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

  return (
    <>
      {/* ── 실시간 전략 체크 ─────────────────────────────────────────────── */}
      <SettingSection title="실시간 전략 체크">
        <SettingRow
          label="모니터링 현황"
          desc="★ 관심종목으로 등록한 종목이 자동으로 전략 체크 대상입니다. 실시간 체크를 켜고 전략을 선택하세요."
        >
          <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}
              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={!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={!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={!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="매도 시그널 발생 조건을 선택합니다. LONG_ONLY: 매수 이력이 있을 때만 매도 허용. SIGNAL_ONLY: 포지션 없이도 지표 규칙 충족 시 즉시 매도 시그널."
        >
          <div className={`stg-radio-row${!isOn ? ' stg-radio-row--disabled' : ''}`}>
            <label className="stg-radio-option stg-radio-option--blue">
              <input
                type="radio"
                name="stg-pos-mode"
                value="LONG_ONLY"
                checked={(liveSettings.positionMode ?? 'LONG_ONLY') === 'LONG_ONLY'}
                disabled={!isOn}
                onChange={() => persistLive({ positionMode: 'LONG_ONLY' })}
              />
              <span className="stg-radio-option-text">
                <strong>보유 자산 기준 (Long-Only)</strong>
                <span>매수 이력 있을 때만 매도 허용</span>
              </span>
            </label>
            <label className="stg-radio-option stg-radio-option--orange">
              <input
                type="radio"
                name="stg-pos-mode"
                value="SIGNAL_ONLY"
                checked={liveSettings.positionMode === 'SIGNAL_ONLY'}
                disabled={!isOn}
                onChange={() => persistLive({ positionMode: 'SIGNAL_ONLY' })}
              />
              <span className="stg-radio-option-text">
                <strong>순수 지표 기준 (Signal-Only)</strong>
                <span>포지션 무관, 지표 충족 시 즉시 매도</span>
              </span>
            </label>
          </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 = [],
  getIndicatorParams,
  saveIndicatorParams,
  getIndicatorVisual,
  saveIndicatorVisual,
  chartIndicators = [],
  onAddIndicatorToChart,
  onRemoveIndicatorFromChart,
  chartSeriesPriceLabels = true,
  onChartSeriesPriceLabels,
  chartVolumeVisible = true,
  onChartVolumeVisible,
  chartLegendOptions,
  onChartLegendOptionsChange,
  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,
  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,
  menuPermissions,
}) => {
  const categories = filterCategories(menuPermissions);
  const [active, setActive] = useState<CategoryId>('general');

  useEffect(() => {
    if (categories.length === 0) return;
    if (!categories.some(c => c.id === active)) {
      setActive(categories[0].id);
    }
  }, [categories, 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}
          chartSeriesPriceLabels={chartSeriesPriceLabels}
          onChartSeriesPriceLabels={onChartSeriesPriceLabels}
          chartVolumeVisible={chartVolumeVisible}
          onChartVolumeVisible={onChartVolumeVisible}
          chartLegendOptions={chartLegendOptions}
          onChartLegendOptionsChange={onChartLegendOptionsChange}
        />
      );
      case 'indicators':
        if (
          !getIndicatorParams || !saveIndicatorParams || !getIndicatorVisual || !saveIndicatorVisual
          || !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}
          />
        );
      case 'backtest': return (
        <BacktestSettingsPanel
          btAutoPopup={btAutoPopup}
          onBtAutoPopup={onBtAutoPopup}
          btShowPrice={btShowPrice}
          onBtShowPrice={onBtShowPrice}
        />
      );
      case 'general':  return (
        <GeneralPanel
          displayTimezone={displayTimezone}
          onDisplayTimezoneChange={onDisplayTimezoneChange ?? (() => {})}
        />
      );
      case 'admin': return (
        <AdminPasswordGate>
          <AdminSettingsPanel />
        </AdminPasswordGate>
      );
      case 'paper': return (
        <PaperPanel
          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}
          onPaperAccountReset={onPaperAccountReset}
          tradingMode={tradingMode}
          onTradingMode={onTradingMode}
          liveAutoTradeEnabled={liveAutoTradeEnabled}
          onLiveAutoTradeEnabled={onLiveAutoTradeEnabled}
          hasUpbitKeys={hasUpbitKeys}
          upbitAccessKeyMasked={upbitAccessKeyMasked}
          onUpbitKeys={onUpbitKeys}
          liveAutoTradeBudgetPct={liveAutoTradeBudgetPct}
          onLiveAutoTradeBudgetPct={onLiveAutoTradeBudgetPct}
        />
      );
      case 'strategy': return (
        <StrategyPanel
          liveMarket={liveMarket}
          liveStrategies={liveStrategies}
          liveSettings={liveSettings}
          watchlistCount={watchlistCount}
          monitoredMarkets={monitoredMarkets}
          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={() => 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>
            <h2 className="stg-content-title">{activeCat.label}</h2>
            <p className="stg-content-desc">{activeCat.desc}</p>
          </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>
        )}
        {active === 'indicators' && (
          <div className="stg-footer stg-footer--hint">
            <span className="stg-footer-hint">보조지표 기본값은 항목을 수정할 때마다 자동으로 저장됩니다.</span>
          </div>
        )}
      </main>
    </div>
  );
};

export default SettingsPage;
