import React, { useState, useEffect, useCallback, lazy, Suspense } from 'react';
import { useIndicatorSettings } from './hooks/useIndicatorSettings';
import { getAppSettingsCache, useAppSettings } from './hooks/useAppSettings';
import { clampVirtualTargetMax } from './utils/virtualTargetLimits';
import { resolveTrendSearchAppSettings } from './utils/trendSearchAppSettings';
import TopMenuBar, { type MenuPage } from './components/TopMenuBar';
import { TradeNotificationProvider } from './contexts/TradeNotificationContext';
import { VerificationIssueNotificationProvider } from './contexts/VerificationIssueNotificationContext';
import { FormalLoginGate } from './components/FormalLoginGate';
import {
  isFormalLogin,
  isTradingMenuPage,
  FORMAL_LOGIN_REQUIRED_MSG,
} from './utils/tradingAccess';
import { NotifyTopMenuBar } from './components/NotifyUiBindings';
import { AppNotificationLayer } from './components/AppNotificationLayer';
import type { Theme } from './types';
import type { ChartRealtimeSource } from './hooks/useChartRealtimeData';
import { useIsMobile } from './hooks/useMediaQuery';
import LoginModal from './components/LoginModal';
import AppDownloadModal from './components/AppDownloadModal';
import SplashScreen from './components/SplashScreen';
import { getAuthSession, setAuthSession, clearAuthSession, type AuthSession } from './utils/auth';
import { isAppEntered, setAppEntered, clearAppEntered } from './utils/appEntry';
import { syncDocumentTheme } from './utils/documentTheme';
import {
  loadLocalTheme,
  migrateDbThemeToLocalIfNeeded,
  saveLocalTheme,
} from './utils/localThemeStorage';
import { useMenuPermissions, invalidateMenuPermissionsCache } from './hooks/useMenuPermissions';
import { firstAllowedTopMenu, normalizeRole, type SettingsCategoryId } from './utils/permissions';
import { clearAdminUnlock } from './utils/adminUnlock';
import type { LoginResponse } from './utils/backendApi';
import { invalidateAppSettingsCache } from './hooks/useAppSettings';
import { invalidateIndicatorSettingsCache } from './hooks/useIndicatorSettings';

const LAST_MENU_KEY = 'gc_last_menu';
const CHART_ONLY_INITIAL: ReadonlySet<MenuPage> = new Set(['dashboard', 'settings', 'verification-board']);

function loadLastMenu(): MenuPage {
  try {
    const saved = localStorage.getItem(LAST_MENU_KEY) as MenuPage | null;
    // 가상매매·투자관리 등 로그인이 필요한 메뉴는 첫 로드 시 복원하지 않음
    if (saved && saved !== 'chart') return saved;
  } catch { /* localStorage 불가 */ }
  return 'chart';
}

function saveLastMenu(page: MenuPage): void {
  try { localStorage.setItem(LAST_MENU_KEY, page); } catch { /* no-op */ }
}
import { useChartWorkspaceContext } from './chart/ChartWorkspaceContext';

const ChartWorkspaceRoot = lazy(() =>
  import('./chart/ChartWorkspaceRoot').then(m => ({ default: m.ChartWorkspaceRoot }))
);
import './App.css';
// virtualTradingDashboard.css는 VirtualTradingPage.tsx에서 import
// paperDashboard.css는 PaperTradingPage.tsx에서 import
// tradeRightPanel.css는 ChartWorkspaceView.tsx에서 import
// backtestDashboard.css는 BacktestHistoryPage.tsx, AnalysisReportPage.tsx에서 import
// appPopup.css는 AppPopup.tsx에서 import

const DashboardPage = lazy(() => import('./components/DashboardPage'));
const StrategyPage = lazy(() => import('./components/StrategyPage'));
const StrategyEditorPage = lazy(() => import('./components/StrategyEditorPage'));
const TradeNotificationListPage = lazy(() => import('./components/TradeNotificationListPage'));
const BacktestHistoryPage = lazy(() => import('./components/BacktestHistoryPage').then(m => ({ default: m.BacktestHistoryPage })));
const AnalysisReportPage = lazy(() => import('./components/analysisReport/AnalysisReportPage').then(m => ({ default: m.AnalysisReportPage })));
const SettingsPage = lazy(() => import('./components/SettingsPage'));
const PaperTradingPage = lazy(() => import('./components/PaperTradingPage'));
const VirtualTradingPage = lazy(() => import('./components/VirtualTradingPage'));
const TrendSearchPage = lazy(() => import('./components/TrendSearchPage'));
const VerificationBoardPage = lazy(() => import('./components/VerificationBoardPage'));

function PageLoadFallback() {
  return <div className="page-load-fallback" role="status">화면 로딩 중…</div>;
}

function AppMainContent({
  theme,
  onThemeToggle,
  sessionKey,
  formalLogin,
  requireFormalLogin,
  authUser,
  guestMode,
  loginOpen,
  setLoginOpen,
  appDownloadOpen,
  setAppDownloadOpen,
  menuPage,
  setMenuPage,
  guardedSetMenuPage,
  settingsInitialCategory,
  setSettingsInitialCategory,
  verificationFocusIssueId,
  setVerificationFocusIssueId,
  menuPermissions,
  canMenu,
  appDefaults,
  appSettingsLoaded,
  saveAppDef,
  getIndicatorParams,
  saveIndicatorParams,
  getIndicatorVisual,
  saveIndicatorVisual,
  saveAllIndicatorDefaults,
  indicatorSettingsRevision,
  handleLoginSuccess,
  handleLogout,
  goToMarketChart,
  chartSlot,
}: {
  theme: Theme;
  onThemeToggle: () => void;
  sessionKey: number;
  formalLogin: boolean;
  requireFormalLogin: () => void;
  authUser: AuthSession | null;
  guestMode: boolean;
  loginOpen: boolean;
  setLoginOpen: (v: boolean) => void;
  appDownloadOpen: boolean;
  setAppDownloadOpen: (v: boolean) => void;
  menuPage: MenuPage;
  setMenuPage: (p: MenuPage) => void;
  guardedSetMenuPage: (p: MenuPage) => void;
  settingsInitialCategory: SettingsCategoryId | undefined;
  setSettingsInitialCategory: (c: SettingsCategoryId | undefined) => void;
  verificationFocusIssueId: number | null;
  setVerificationFocusIssueId: (id: number | null) => void;
  menuPermissions: ReturnType<typeof useMenuPermissions>['permissions'];
  canMenu: ReturnType<typeof useMenuPermissions>['can'];
  appDefaults: ReturnType<typeof useAppSettings>['defaults'];
  appSettingsLoaded: boolean;
  saveAppDef: ReturnType<typeof useAppSettings>['save'];
  getIndicatorParams: ReturnType<typeof useIndicatorSettings>['getParams'];
  saveIndicatorParams: ReturnType<typeof useIndicatorSettings>['saveParams'];
  getIndicatorVisual: ReturnType<typeof useIndicatorSettings>['getVisualConfig'];
  saveIndicatorVisual: ReturnType<typeof useIndicatorSettings>['saveVisual'];
  saveAllIndicatorDefaults: ReturnType<typeof useIndicatorSettings>['saveAllIndicatorDefaults'];
  indicatorSettingsRevision: number;
  handleLoginSuccess: (res: LoginResponse) => void;
  handleLogout: () => void;
  /** 차트로 이동하는 App 레벨 콜백 */
  goToMarketChart: (market: string) => void;
  /** 차트 화면 슬롯 (lazy ChartWorkspaceRoot) */
  chartSlot: React.ReactNode;
}) {
  const isMobile = useIsMobile();
  const {
    symbol,
    indicators,
    refreshPaperAccount,
    handlePaperOrderFilled,
    paperRefreshKey,
    mode,
    marketTickers,
    settingsPageProps,
  } = useChartWorkspaceContext();

  const paperTradingEnabled = appDefaults.paperTradingEnabled ?? true;
  const paperAutoTradeEnabled = appDefaults.paperAutoTradeEnabled ?? false;

  const handleFullscreen = () => {
    if (!document.fullscreenElement) document.documentElement.requestFullscreen().catch(() => {});
    else document.exitFullscreen().catch(() => {});
  };

  return (
      <VerificationIssueNotificationProvider
        enabled={canMenu('verification-board')}
        notifyEnabled={appDefaults.verificationIssueNotify}
      >
        <div className={`app ${theme} mode-${mode}${isMobile ? ' app--mobile' : ''}`}>
          <NotifyTopMenuBar
            activePage={menuPage}
            theme={theme}
            onPage={guardedSetMenuPage}
            onTheme={onThemeToggle}
            onOpenAppDownload={() => setAppDownloadOpen(true)}
            authUser={authUser}
            guestMode={guestMode}
            menuPermissions={menuPermissions}
            onLoginClick={() => setLoginOpen(true)}
            onLogout={handleLogout}
            tradeAlertPopupPosition={appDefaults.tradeAlertPopupPosition}
            tradeAlertPopupLayout={appDefaults.tradeAlertPopupLayout}
            tradeAlertPopupGridCols={appDefaults.tradeAlertPopupGridCols}
            onTradeAlertPopupPosition={v => saveAppDef({ tradeAlertPopupPosition: v })}
            onTradeAlertPopupLayout={v => saveAppDef({ tradeAlertPopupLayout: v })}
            onTradeAlertPopupGridCols={v => saveAppDef({ tradeAlertPopupGridCols: v })}
            onFullscreen={handleFullscreen}
          />

          <LoginModal
            open={loginOpen}
            onClose={() => setLoginOpen(false)}
            onSuccess={handleLoginSuccess}
          />

          {appDownloadOpen && (
            <AppDownloadModal onClose={() => setAppDownloadOpen(false)} />
          )}

          {menuPage === 'dashboard' && (
            <Suspense fallback={<PageLoadFallback />}>
              <DashboardPage theme={theme} onGoChart={m => { goToMarketChart(m); setMenuPage('chart'); }} />
            </Suspense>
          )}

          {menuPage === 'strategy' && (
            formalLogin
              ? (
                <Suspense fallback={<PageLoadFallback />}>
                  <StrategyPage theme={theme} activeIndicators={indicators} />
                </Suspense>
              )
              : <FormalLoginGate onLogin={requireFormalLogin} />
          )}

          {menuPage === 'strategy-editor' && (
            formalLogin
              ? (
                <Suspense fallback={<PageLoadFallback />}>
                  <StrategyEditorPage theme={theme} />
                </Suspense>
              )
              : <FormalLoginGate onLogin={requireFormalLogin} />
          )}

          {menuPage === 'backtest' && (
            formalLogin
              ? (
                <Suspense fallback={<PageLoadFallback />}>
                  <BacktestHistoryPage theme={theme} />
                </Suspense>
              )
              : <FormalLoginGate onLogin={requireFormalLogin} />
          )}

          {menuPage === 'analysis-report' && (
            formalLogin
              ? (
                <Suspense fallback={<PageLoadFallback />}>
                  <AnalysisReportPage theme={theme} />
                </Suspense>
              )
              : <FormalLoginGate onLogin={requireFormalLogin} />
          )}

          {menuPage === 'paper' && (
            formalLogin ? (
              <Suspense fallback={<PageLoadFallback />}>
                <PaperTradingPage
                  theme={theme}
                  tickers={marketTickers}
                  refreshKey={paperRefreshKey}
                  defaultMarket={symbol}
                  paperTradingEnabled={paperTradingEnabled}
                  paperAutoTradeEnabled={paperAutoTradeEnabled}
                  onPaperOrderFilled={handlePaperOrderFilled}
                  onOpenSettings={() => {
                    if (!formalLogin) { requireFormalLogin(); return; }
                    setSettingsInitialCategory('trading');
                    setMenuPage('settings');
                  }}
                  onGoChart={m => {
                    goToMarketChart(m);
                    setMenuPage('chart');
                  }}
                />
              </Suspense>
            ) : (
              <FormalLoginGate onLogin={requireFormalLogin} title="투자관리" />
            )
          )}

          {menuPage === 'virtual' && (
            formalLogin ? (
              <Suspense fallback={<PageLoadFallback />}>
                <VirtualTradingPage
                  theme={theme}
                  tickers={marketTickers}
                  defaultMarket={symbol}
                  paperTradingEnabled={paperTradingEnabled}
                  paperAutoTradeEnabled={paperAutoTradeEnabled}
                  paperAutoTradeBudgetPct={appDefaults.paperAutoTradeBudgetPct}
                  onPaperAutoTradeEnabled={v => saveAppDef({ paperAutoTradeEnabled: v })}
                  onPaperOrderFilled={handlePaperOrderFilled}
                />
              </Suspense>
            ) : (
              <FormalLoginGate onLogin={requireFormalLogin} title="가상매매" />
            )
          )}

          {menuPage === 'trend-search' && (
            <Suspense fallback={<PageLoadFallback />}>
              <TrendSearchPage
                theme={theme}
                chartRealtimeSource={(appDefaults.chartRealtimeSource ?? 'BACKEND_STOMP') as ChartRealtimeSource}
                chartCandleAreaPriceLabels={appDefaults.chartCandleAreaPriceLabels}
                chartSeriesPriceLabels={appDefaults.chartSeriesPriceLabels}
                tickers={marketTickers}
              />
            </Suspense>
          )}

          {menuPage === 'verification-board' && (
            <Suspense fallback={<PageLoadFallback />}>
              <VerificationBoardPage
                theme={theme}
                focusIssueId={verificationFocusIssueId}
                onFocusIssueConsumed={() => setVerificationFocusIssueId(null)}
              />
            </Suspense>
          )}

          {menuPage === 'notifications' && canMenu('notifications') && (
            formalLogin ? (
              <Suspense fallback={<PageLoadFallback />}>
                <TradeNotificationListPage
                  theme={theme}
                  tickers={marketTickers}
                  defaultMarket={symbol}
                  paperTradingEnabled={paperTradingEnabled}
                  paperAutoTradeEnabled={paperAutoTradeEnabled}
                  onPaperOrderFilled={handlePaperOrderFilled}
                  onGoToChart={market => {
                    goToMarketChart(market);
                    setMenuPage('chart');
                  }}
                />
              </Suspense>
            ) : (
              <FormalLoginGate onLogin={requireFormalLogin} title="알림목록" />
            )
          )}

          {menuPage === 'settings' && canMenu('settings') && (
            <Suspense fallback={<PageLoadFallback />}>
              <SettingsPage
                isFormalLogin={formalLogin}
                onRequireFormalLogin={requireFormalLogin}
                theme={theme}
                menuPermissions={menuPermissions}
                btAutoPopup={appDefaults.btAutoPopup}
                onBtAutoPopup={v => saveAppDef({ btAutoPopup: v })}
                btShowPrice={appDefaults.btShowPrice}
                onBtShowPrice={v => saveAppDef({ btShowPrice: v })}
                liveMarket={symbol}
                tradeAlertPopup={appDefaults.tradeAlertPopup}
                onTradeAlertPopup={v => saveAppDef({ tradeAlertPopup: v })}
                tradeAlertSoundEnabled={appDefaults.tradeAlertSoundEnabled}
                onTradeAlertSoundEnabled={v => saveAppDef({ tradeAlertSoundEnabled: v })}
                tradeAlertSound={appDefaults.tradeAlertSound}
                onTradeAlertSound={v => saveAppDef({ tradeAlertSound: v })}
                tradeAlertPopupPosition={appDefaults.tradeAlertPopupPosition}
                onTradeAlertPopupPosition={v => saveAppDef({ tradeAlertPopupPosition: v })}
                tradeAlertPopupLayout={appDefaults.tradeAlertPopupLayout}
                onTradeAlertPopupLayout={v => saveAppDef({ tradeAlertPopupLayout: v })}
                tradeAlertPopupGridCols={appDefaults.tradeAlertPopupGridCols}
                onTradeAlertPopupGridCols={v => saveAppDef({ tradeAlertPopupGridCols: v })}
                getIndicatorParams={getIndicatorParams}
                saveIndicatorParams={saveIndicatorParams}
                getIndicatorVisual={getIndicatorVisual}
                saveIndicatorVisual={saveIndicatorVisual}
                saveAllIndicatorDefaults={saveAllIndicatorDefaults}
                indicatorSettingsRevision={indicatorSettingsRevision}
                paperTradingEnabled={paperTradingEnabled}
                onPaperTradingEnabled={v => saveAppDef({ paperTradingEnabled: v })}
                paperInitialCapital={appDefaults.paperInitialCapital}
                onPaperInitialCapital={v => saveAppDef({ paperInitialCapital: v })}
                paperFeeRatePct={appDefaults.paperFeeRatePct}
                onPaperFeeRatePct={v => saveAppDef({ paperFeeRatePct: v })}
                paperSlippagePct={appDefaults.paperSlippagePct}
                onPaperSlippagePct={v => saveAppDef({ paperSlippagePct: v })}
                paperMinOrderKrw={appDefaults.paperMinOrderKrw}
                onPaperMinOrderKrw={v => saveAppDef({ paperMinOrderKrw: v })}
                paperAutoTradeEnabled={appDefaults.paperAutoTradeEnabled}
                onPaperAutoTradeEnabled={v => saveAppDef({ paperAutoTradeEnabled: v })}
                paperAutoTradeBudgetPct={appDefaults.paperAutoTradeBudgetPct}
                onPaperAutoTradeBudgetPct={v => saveAppDef({ paperAutoTradeBudgetPct: v })}
                paperOrderFillMode={appDefaults.paperOrderFillMode}
                onPaperOrderFillMode={v => saveAppDef({ paperOrderFillMode: v })}
                paperOrderAutoCancelPct={appDefaults.paperOrderAutoCancelPct}
                onPaperOrderAutoCancelPct={v => saveAppDef({ paperOrderAutoCancelPct: v })}
                virtualTargetMaxCount={appDefaults.virtualTargetMaxCount}
                onVirtualTargetMaxCount={v => saveAppDef({ virtualTargetMaxCount: clampVirtualTargetMax(v) })}
                trendSearchSettings={appDefaults.trendSearchSettings}
                onTrendSearchSettingsChange={s => saveAppDef({
                  trendSearchSettings: resolveTrendSearchAppSettings(s),
                })}
                tradingMode={appDefaults.tradingMode}
                onTradingMode={v => saveAppDef({ tradingMode: v })}
                liveAutoTradeEnabled={appDefaults.liveAutoTradeEnabled}
                onLiveAutoTradeEnabled={v => saveAppDef({ liveAutoTradeEnabled: v })}
                hasUpbitKeys={appDefaults.hasUpbitKeys}
                upbitAccessKeyMasked={appDefaults.upbitAccessKeyMasked}
                onUpbitKeys={(access, secret) => {
                  const patch: Parameters<typeof saveAppDef>[0] = {};
                  const a = access.trim();
                  const s = secret.trim();
                  const isMasked = (v: string) => v.startsWith('····') || v.startsWith('****');
                  if (a && !isMasked(a)) patch.upbitAccessKey = a;
                  if (s && !isMasked(s)) patch.upbitSecretKey = s;
                  if (Object.keys(patch).length) saveAppDef(patch);
                }}
                chartRealtimeSource={appDefaults.chartRealtimeSource}
                onChartRealtimeSource={v => saveAppDef({ chartRealtimeSource: v })}
                liveAutoTradeBudgetPct={appDefaults.liveAutoTradeBudgetPct}
                onLiveAutoTradeBudgetPct={v => saveAppDef({ liveAutoTradeBudgetPct: v })}
                fcmPushEnabled={appDefaults.fcmPushEnabled}
                onFcmPushEnabled={v => saveAppDef({ fcmPushEnabled: v })}
                verificationIssueNotify={appDefaults.verificationIssueNotify}
                onVerificationIssueNotify={v => saveAppDef({ verificationIssueNotify: v })}
                onFcmTest={async () => {
                  const { sendFcmTest } = await import('./utils/backendApi');
                  const r = await sendFcmTest();
                  window.alert(r?.available ? 'FCM 테스트 요청을 보냈습니다.' : 'FCM 서버가 비활성 상태입니다.');
                }}
                initialCategory={settingsInitialCategory}
                {...settingsPageProps}
              />
            </Suspense>
          )}

          {chartSlot}

          <AppNotificationLayer
            menuPage={menuPage}
            onPage={guardedSetMenuPage}
            onGoToChart={goToMarketChart}
            onGoToVerificationIssue={issueId => {
              setVerificationFocusIssueId(issueId);
              guardedSetMenuPage('verification-board');
            }}
            tradingMode={appDefaults.tradingMode}
            hasUpbitKeys={appDefaults.hasUpbitKeys}
            paperTradingEnabled={paperTradingEnabled}
            liveAutoTradeBudgetPct={appDefaults.liveAutoTradeBudgetPct}
            paperAutoTradeBudgetPct={appDefaults.paperAutoTradeBudgetPct}
            tradeAlertPopupPosition={appDefaults.tradeAlertPopupPosition}
            tradeAlertPopupLayout={appDefaults.tradeAlertPopupLayout}
            tradeAlertPopupGridCols={appDefaults.tradeAlertPopupGridCols}
            onOrderDone={() => {
              refreshPaperAccount(symbol);
              handlePaperOrderFilled();
            }}
          />
        </div>
      </VerificationIssueNotificationProvider>
  );
}

function App() {
  const [sessionKey, setSessionKey] = useState(0);
  const [appEntered, setAppEnteredState] = useState(() => isAppEntered());
  const [authUser, setAuthUser] = useState<AuthSession | null>(() =>
    isAppEntered() ? getAuthSession() : null,
  );
  const [guestMode, setGuestMode] = useState(() =>
    isAppEntered() && !getAuthSession(),
  );
  const formalLogin = isFormalLogin(authUser, guestMode);
  const [loginOpen, setLoginOpen] = useState(false);

  const requireFormalLogin = useCallback(() => {
    window.alert(FORMAL_LOGIN_REQUIRED_MSG);
    setLoginOpen(true);
  }, []);
  const [appDownloadOpen, setAppDownloadOpen] = useState(false);

  const { defaults: appDefaults, isLoaded: appSettingsLoaded, save: saveAppDef } = useAppSettings(sessionKey);
  const { permissions: menuPermissions, isLoaded: menuPermsLoaded, can: canMenu } = useMenuPermissions(sessionKey);

  const [theme, setTheme] = useState<Theme>(loadLocalTheme);
  const [menuPage, setMenuPage] = useState<MenuPage>(loadLastMenu);
  const [settingsInitialCategory, setSettingsInitialCategory] = useState<SettingsCategoryId | undefined>();
  const [verificationFocusIssueId, setVerificationFocusIssueId] = useState<number | null>(null);

  // 차트 lazy mount 관련 상태: 처음 chart 메뉴 진입 시 마운트, 이후 유지
  const [chartEverActivated, setChartEverActivated] = useState(() => loadLastMenu() === 'chart');
  const [pendingChartMarket, setPendingChartMarket] = useState<string | null>(null);

  // menuPage 변경 시 localStorage 저장 + chart 활성화 추적
  const setMenuPagePersist = useCallback((page: MenuPage) => {
    saveLastMenu(page);
    setMenuPage(page);
    if (page === 'chart') setChartEverActivated(true);
  }, []);

  // 외부(대시보드·알림 등)에서 특정 마켓으로 차트 이동 요청
  const handleGoToMarketChart = useCallback((market: string) => {
    setPendingChartMarket(market);
    setMenuPagePersist('chart');
    setChartEverActivated(true);
  }, [setMenuPagePersist]);

  useEffect(() => {
    if (menuPage === 'chart') setChartEverActivated(true);
  }, [menuPage]);

  // 지표 설정: 차트·전략·알림 관련 페이지 첫 진입 시 로드 (이후 캐시 유지)
  const INDICATOR_SETTINGS_PAGES: MenuPage[] = ['chart', 'strategy-editor', 'notifications', 'strategy', 'backtest', 'analysis-report', 'settings'];
  const [indicatorSettingsEnabled, setIndicatorSettingsEnabled] = useState(
    () => INDICATOR_SETTINGS_PAGES.includes(menuPage),
  );
  useEffect(() => {
    if (!indicatorSettingsEnabled && INDICATOR_SETTINGS_PAGES.includes(menuPage)) {
      setIndicatorSettingsEnabled(true);
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [menuPage, indicatorSettingsEnabled]);

  const {
    getParams: getIndicatorParams,
    saveParams: saveIndicatorParams,
    getVisualConfig: getIndicatorVisual,
    saveVisual: saveIndicatorVisual,
    saveAllIndicatorDefaults,
    settingsRevision: indicatorSettingsRevision,
  } = useIndicatorSettings(sessionKey, indicatorSettingsEnabled);
  const getParams = getIndicatorParams;
  const saveParams = saveIndicatorParams;
  const getVisualConfig = getIndicatorVisual;
  const saveVisual = saveIndicatorVisual;

  useEffect(() => {
    if (menuPage !== 'settings') setSettingsInitialCategory(undefined);
  }, [menuPage]);

  const enterMainApp = useCallback(() => {
    setAppEntered();
    setAppEnteredState(true);
  }, []);

  const handleLoginSuccess = useCallback((res: LoginResponse) => {
    const session: AuthSession = {
      userId: res.userId,
      username: res.username,
      displayName: res.displayName,
      role: normalizeRole(res.role),
    };
    setAuthSession(session);
    setAuthUser(session);
    setGuestMode(false);
    clearAdminUnlock();
    invalidateAppSettingsCache();
    invalidateIndicatorSettingsCache();
    invalidateMenuPermissionsCache();
    setSessionKey(k => k + 1);
    enterMainApp();
  }, [enterMainApp]);

  const handleGuestEnter = useCallback(() => {
    clearAuthSession();
    setAuthUser(null);
    setGuestMode(true);
    clearAdminUnlock();
    invalidateAppSettingsCache();
    invalidateIndicatorSettingsCache();
    invalidateMenuPermissionsCache();
    setSessionKey(k => k + 1);
    enterMainApp();
  }, [enterMainApp]);

  const handleLogout = useCallback(() => {
    clearAuthSession();
    setAuthUser(null);
    setGuestMode(false);
    clearAdminUnlock();
    invalidateAppSettingsCache();
    invalidateIndicatorSettingsCache();
    invalidateMenuPermissionsCache();
    setSessionKey(k => k + 1);
    clearAppEntered();
    setAppEnteredState(false);
    setLoginOpen(false);
  }, []);

  const guardedSetMenuPage = useCallback((page: MenuPage) => {
    if (!formalLogin && isTradingMenuPage(page)) {
      requireFormalLogin();
      return;
    }
    if (page === 'notifications' && !canMenu('notifications')) return;
    if (page !== 'notifications' && !canMenu(page)) {
      setMenuPagePersist(firstAllowedTopMenu(menuPermissions));
      return;
    }
    setMenuPagePersist(page);
  }, [canMenu, menuPermissions, formalLogin, requireFormalLogin, setMenuPagePersist]);

  useEffect(() => {
    if (!menuPermsLoaded) return;
    if (menuPage === 'notifications') {
      if (!canMenu('notifications')) setMenuPagePersist(firstAllowedTopMenu(menuPermissions));
      return;
    }
    if (!canMenu(menuPage)) {
      setMenuPagePersist(firstAllowedTopMenu(menuPermissions));
    }
  }, [menuPage, menuPermsLoaded, menuPermissions, canMenu, setMenuPagePersist]);

  const handleThemeToggle = useCallback(() => {
    setTheme(t => {
      const next: Theme = t === 'dark' ? 'blue' : t === 'blue' ? 'light' : 'dark';
      saveLocalTheme(next);
      return next;
    });
  }, []);

  useEffect(() => {
    syncDocumentTheme(theme);
  }, [theme]);

  useEffect(() => {
    if (!appSettingsLoaded) return;
    setTheme(migrateDbThemeToLocalIfNeeded(getAppSettingsCache().defaultTheme));
  }, [appSettingsLoaded]);

  const openNotificationsPage = useCallback(
    () => guardedSetMenuPage('notifications'),
    [guardedSetMenuPage],
  );

  if (!appEntered) {
    return (
      <SplashScreen
        onLoginSuccess={handleLoginSuccess}
        onGuest={handleGuestEnter}
      />
    );
  }

  // 차트 슬롯: 처음 chart 페이지 진입 시 마운트, 이후 visible 토글
  const chartSlot = chartEverActivated ? (
    <Suspense fallback={null}>
      <ChartWorkspaceRoot
        visible={menuPage === 'chart'}
        pendingChartMarket={pendingChartMarket}
        onPendingChartMarketConsumed={() => setPendingChartMarket(null)}
        menuPage={menuPage}
        sessionKey={sessionKey}
        formalLogin={formalLogin}
        requireFormalLogin={requireFormalLogin}
        theme={theme}
        onThemeToggle={handleThemeToggle}
        appDefaults={appDefaults as unknown as import('./utils/backendApi').AppSettingsDto}
        appSettingsLoaded={appSettingsLoaded}
        saveAppDef={saveAppDef}
        getParams={getParams}
        saveParams={saveParams}
        getVisualConfig={getVisualConfig}
        saveVisual={saveVisual}
        saveAllIndicatorDefaults={saveAllIndicatorDefaults}
        indicatorSettingsRevision={indicatorSettingsRevision}
        onOpenNotificationsPage={openNotificationsPage}
        onOpenAppDownload={() => setAppDownloadOpen(true)}
      />
    </Suspense>
  ) : null;

  return (
    <TradeNotificationProvider
      popupEnabled={appDefaults.tradeAlertPopup ?? true}
      soundEnabled={appDefaults.tradeAlertSoundEnabled ?? true}
      soundId={appDefaults.tradeAlertSound ?? 'bell'}
      settingsSessionKey={authUser?.userId ?? 0}
    >
      <AppMainContent
        theme={theme}
        onThemeToggle={handleThemeToggle}
        sessionKey={sessionKey}
        formalLogin={formalLogin}
        requireFormalLogin={requireFormalLogin}
        authUser={authUser}
        guestMode={guestMode}
        loginOpen={loginOpen}
        setLoginOpen={setLoginOpen}
        appDownloadOpen={appDownloadOpen}
        setAppDownloadOpen={setAppDownloadOpen}
        menuPage={menuPage}
        setMenuPage={setMenuPagePersist}
        guardedSetMenuPage={guardedSetMenuPage}
        settingsInitialCategory={settingsInitialCategory}
        setSettingsInitialCategory={setSettingsInitialCategory}
        verificationFocusIssueId={verificationFocusIssueId}
        setVerificationFocusIssueId={setVerificationFocusIssueId}
        menuPermissions={menuPermissions}
        canMenu={canMenu}
        appDefaults={appDefaults}
        appSettingsLoaded={appSettingsLoaded}
        saveAppDef={saveAppDef}
        getIndicatorParams={getIndicatorParams}
        saveIndicatorParams={saveIndicatorParams}
        getIndicatorVisual={getIndicatorVisual}
        saveIndicatorVisual={saveIndicatorVisual}
        saveAllIndicatorDefaults={saveAllIndicatorDefaults}
        indicatorSettingsRevision={indicatorSettingsRevision}
        handleLoginSuccess={handleLoginSuccess}
        handleLogout={handleLogout}
        goToMarketChart={handleGoToMarketChart}
        chartSlot={chartSlot}
      />
    </TradeNotificationProvider>
  );
}

export default App;
