import React, { Suspense, lazy, useEffect, useMemo, useRef, useState, useCallback } from "react";

// Configuration and constants
import { AGENTS, INITIAL_TICKERS } from './config/constants';

// Services
import { ReadOnlyClient } from './services/websocket';

// Hooks
import { useFeedProcessor } from './hooks/useFeedProcessor';

// Styles
import GlobalStyles from './styles/GlobalStyles';

// Components
import NetValueChart from './components/NetValueChart';
import StockLogo from './components/StockLogo';
import Header from './components/Header.jsx';
import WatchlistPanel from './components/WatchlistPanel.jsx';
import RuntimeSettingsPanel from './components/RuntimeSettingsPanel.jsx';

// Utils
import { formatNumber, formatTickerPrice } from './utils/formatters';

const RoomView = lazy(() => import('./components/RoomView'));
const AgentFeed = lazy(() => import('./components/AgentFeed'));
const StatisticsView = lazy(() => import('./components/StatisticsView'));
const StockExplainView = lazy(() => import('./components/StockExplainView.jsx'));
const TraderView = lazy(() => import('./components/TraderView.jsx'));
const EDITABLE_AGENT_WORKSPACE_FILES = ['SOUL.md', 'PROFILE.md', 'AGENTS.md', 'MEMORY.md', 'POLICY.md', 'HEARTBEAT.md', 'ROLE.md', 'STYLE.md'];

function ViewLoadingFallback({ label = '加载中...' }) {
  return (
    <div
      style={{
        minHeight: 240,
        height: '100%',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        border: '1px solid #000000',
        background: '#ffffff',
        fontSize: 12,
        fontWeight: 700,
        letterSpacing: 0.4
      }}
    >
      {label}
    </div>
  );
}

/**
 * Live Trading Intelligence Platform - Read-Only Dashboard
 * Geek Style - Terminal-inspired, minimal, monochrome
 *
 */

export default function LiveTradingApp() {
  const [isConnected, setIsConnected] = useState(false);
  const [connectionStatus, setConnectionStatus] = useState('connecting'); // 'connecting' | 'connected' | 'disconnected'
  const [systemStatus, setSystemStatus] = useState('initializing'); // 'initializing' | 'running' | 'completed'
  const [currentDate, setCurrentDate] = useState(null);
  const [progress, setProgress] = useState({ current: 0, total: 0 });
  const [now, setNow] = useState(() => new Date());

  // View toggle: 'traders' | 'room' | 'explain' | 'chart' | 'statistics'
  const [currentView, setCurrentView] = useState('traders');
  const [isInitialAnimating, setIsInitialAnimating] = useState(true);
  const [lastUpdate, setLastUpdate] = useState(new Date());
  const [isUpdating, setIsUpdating] = useState(false);

  // Chart data
  const [chartTab, setChartTab] = useState('all');
  const [portfolioData, setPortfolioData] = useState({
    netValue: 10000,
    pnl: 0,
    equity: [],
    baseline: [], // Baseline strategy (Buy & Hold - Equal Weight)
    baseline_vw: [], // Baseline strategy (Buy & Hold - Value Weighted)
    momentum: [], // Momentum strategy
    strategies: [] // Other strategies
  });

  // Feed data (using hook for simplified processing)
  const { feed, processHistoricalFeed, processFeedEvent, addSystemMessage } = useFeedProcessor();

  // Statistics data
  const [holdings, setHoldings] = useState([]);
  const [trades, setTrades] = useState([]);
  const [stats, setStats] = useState(null);
  const [leaderboard, setLeaderboard] = useState([]);

  // Ticker prices (now from real-time data)
  const [tickers, setTickers] = useState(INITIAL_TICKERS);
  const [rollingTickers, setRollingTickers] = useState({});
  const [priceHistoryByTicker, setPriceHistoryByTicker] = useState({});
  const [ohlcHistoryByTicker, setOhlcHistoryByTicker] = useState({});
  const [explainEventsByTicker, setExplainEventsByTicker] = useState({});
  const [newsByTicker, setNewsByTicker] = useState({});
  const [selectedExplainSymbol, setSelectedExplainSymbol] = useState('');
  const [historySourceByTicker, setHistorySourceByTicker] = useState({});

  // Room bubbles
  const [bubbles, setBubbles] = useState({});

  // Resizable panels
  const [leftWidth, setLeftWidth] = useState(70); // percentage
  const [isResizing, setIsResizing] = useState(false);

  // Market status
  const [serverMode, setServerMode] = useState(null); // 'live' | 'backtest' | null
  const [marketStatus, setMarketStatus] = useState(null); // { status, status_text, ... }
  const [virtualTime, setVirtualTime] = useState(null); // Virtual time from server (for mock mode)
  const [dataSources, setDataSources] = useState(null);
  const [runtimeConfig, setRuntimeConfig] = useState(null);
  const [isWatchlistPanelOpen, setIsWatchlistPanelOpen] = useState(false);
  const [isRuntimeSettingsOpen, setIsRuntimeSettingsOpen] = useState(false);
  const [watchlistDraftSymbols, setWatchlistDraftSymbols] = useState([]);
  const [watchlistInputValue, setWatchlistInputValue] = useState('');
  const [watchlistFeedback, setWatchlistFeedback] = useState(null);
  const [isWatchlistSaving, setIsWatchlistSaving] = useState(false);
  const [scheduleModeDraft, setScheduleModeDraft] = useState('daily');
  const [intervalMinutesDraft, setIntervalMinutesDraft] = useState('60');
  const [triggerTimeDraft, setTriggerTimeDraft] = useState('09:30');
  const [maxCommCyclesDraft, setMaxCommCyclesDraft] = useState('2');
  const [runtimeConfigFeedback, setRuntimeConfigFeedback] = useState(null);
  const [isRuntimeConfigSaving, setIsRuntimeConfigSaving] = useState(false);
  const [selectedSkillAgentId, setSelectedSkillAgentId] = useState(AGENTS[0]?.id || 'portfolio_manager');
  const [agentProfilesByAgent, setAgentProfilesByAgent] = useState({});
  const [agentSkillsByAgent, setAgentSkillsByAgent] = useState({});
  const [skillDetailsByName, setSkillDetailsByName] = useState({});
  const [localSkillDraftsByKey, setLocalSkillDraftsByKey] = useState({});
  const [isAgentSkillsLoading, setIsAgentSkillsLoading] = useState(false);
  const [skillDetailLoadingKey, setSkillDetailLoadingKey] = useState(null);
  const [agentSkillsSavingKey, setAgentSkillsSavingKey] = useState(null);
  const [agentSkillsFeedback, setAgentSkillsFeedback] = useState(null);
  const [selectedWorkspaceFile, setSelectedWorkspaceFile] = useState(EDITABLE_AGENT_WORKSPACE_FILES[0]);
  const [workspaceFilesByAgent, setWorkspaceFilesByAgent] = useState({});
  const [workspaceDraftContent, setWorkspaceDraftContent] = useState('');
  const [isWorkspaceFileLoading, setIsWorkspaceFileLoading] = useState(false);
  const [workspaceFileSavingKey, setWorkspaceFileSavingKey] = useState(null);
  const [workspaceFileFeedback, setWorkspaceFileFeedback] = useState(null);

  const clientRef = useRef(null);
  const containerRef = useRef(null);
  const agentFeedRef = useRef(null);
  const isWatchlistSavingRef = useRef(false);
  const isRuntimeConfigSavingRef = useRef(false);
  const requestedStockHistoryRef = useRef(new Set());

  // Track last virtual time update to calculate increment
  const lastVirtualTimeRef = useRef(null);
  const virtualTimeOffsetRef = useRef(0);

  // Last day history for replay
  const [lastDayHistory, setLastDayHistory] = useState([]);

  const buildTickersFromSymbols = useCallback((symbols, previousTickers = []) => {
    if (!Array.isArray(symbols) || symbols.length === 0) {
      return previousTickers;
    }

    return symbols
      .filter((symbol) => typeof symbol === 'string' && symbol.trim())
      .map((symbol) => {
        const normalized = symbol.trim().toUpperCase();
        const existing = previousTickers.find((ticker) => ticker.symbol === normalized);
        return existing || {
          symbol: normalized,
          price: null,
          change: null
        };
      });
  }, []);

  const normalizePriceHistory = useCallback((payload) => {
    if (!payload || typeof payload !== 'object') {
      return {};
    }

    const normalized = {};
    Object.entries(payload).forEach(([symbol, points]) => {
      const ticker = String(symbol || '').trim().toUpperCase();
      if (!ticker || !Array.isArray(points)) {
        return;
      }

      normalized[ticker] = points
        .map((point) => {
          if (Array.isArray(point) && point.length >= 2) {
            const [label, value] = point;
            const price = Number(value);
            if (!label || !Number.isFinite(price)) return null;
            return {
              timestamp: String(label),
              label: String(label),
              price
            };
          }

          if (point && typeof point === 'object') {
            const rawTimestamp = point.timestamp ?? point.t ?? point.date ?? point.label;
            const price = Number(point.price ?? point.v ?? point.value ?? point.close);
            if (!rawTimestamp || !Number.isFinite(price)) return null;
            return {
              timestamp: String(rawTimestamp),
              label: String(rawTimestamp),
              price
            };
          }

          return null;
        })
        .filter(Boolean)
        .slice(-120);
    });

    return normalized;
  }, []);

  // Determine if LIVE tab should be enabled
  const isLiveEnabled = useMemo(() => {
    if (!marketStatus) return false;
    return marketStatus.status === 'open';
  }, [marketStatus]);

  const displayTickers = useMemo(() => {
    const symbols = runtimeConfig?.tickers;
    if (Array.isArray(symbols) && symbols.length > 0) {
      return buildTickersFromSymbols(symbols, tickers);
    }
    return tickers;
  }, [buildTickersFromSymbols, runtimeConfig, tickers]);

  const runtimeWatchlistSymbols = useMemo(() => {
    const symbols = runtimeConfig?.tickers;
    if (Array.isArray(symbols) && symbols.length > 0) {
      return symbols
        .filter((symbol) => typeof symbol === 'string' && symbol.trim())
        .map((symbol) => symbol.trim().toUpperCase());
    }

    return displayTickers
      .map((ticker) => ticker.symbol)
      .filter((symbol) => typeof symbol === 'string' && symbol.trim());
  }, [displayTickers, runtimeConfig]);

  const runtimeSummaryLabel = useMemo(() => {
    if (!runtimeConfig) {
      return null;
    }

    const scheduleMode = String(runtimeConfig.schedule_mode || 'daily');
    const intervalMinutes = Number(runtimeConfig.interval_minutes || 60);
    const triggerTime = String(runtimeConfig.trigger_time || '09:30');
    const maxCommCycles = Number(runtimeConfig.max_comm_cycles || 2);

    if (scheduleMode === 'intraday') {
      return `调度 intraday / ${intervalMinutes}m / 讨论 ${maxCommCycles} 轮`;
    }

    return `调度 daily / ${triggerTime} ET / 讨论 ${maxCommCycles} 轮`;
  }, [runtimeConfig]);

  const selectedAgentSkills = useMemo(
    () => agentSkillsByAgent[selectedSkillAgentId] || [],
    [agentSkillsByAgent, selectedSkillAgentId]
  );

  const selectedAgentProfile = useMemo(
    () => agentProfilesByAgent[selectedSkillAgentId] || null,
    [agentProfilesByAgent, selectedSkillAgentId]
  );

  const selectedWorkspaceContent = useMemo(
    () => workspaceFilesByAgent[selectedSkillAgentId]?.[selectedWorkspaceFile] || '',
    [selectedSkillAgentId, selectedWorkspaceFile, workspaceFilesByAgent]
  );

  useEffect(() => {
    const symbols = displayTickers
      .map((ticker) => ticker.symbol)
      .filter((symbol) => typeof symbol === 'string' && symbol.trim());

    if (!symbols.length) {
      setSelectedExplainSymbol('');
      return;
    }

    if (!selectedExplainSymbol || !symbols.includes(selectedExplainSymbol)) {
      setSelectedExplainSymbol(symbols[0]);
    }
  }, [displayTickers, selectedExplainSymbol]);

  useEffect(() => {
    if (!runtimeConfig) {
      return;
    }

    setScheduleModeDraft(String(runtimeConfig.schedule_mode || 'daily'));
    setIntervalMinutesDraft(String(runtimeConfig.interval_minutes || 60));
    setTriggerTimeDraft(String(runtimeConfig.trigger_time || '09:30'));
    setMaxCommCyclesDraft(String(runtimeConfig.max_comm_cycles || 2));
  }, [runtimeConfig]);

  const watchlistSuggestions = useMemo(
    () => INITIAL_TICKERS.map((ticker) => ticker.symbol).filter((symbol, index, list) => list.indexOf(symbol) === index),
    []
  );

  const isWatchlistDraftDirty = useMemo(() => {
    if (watchlistInputValue.trim()) {
      return true;
    }

    if (watchlistDraftSymbols.length !== runtimeWatchlistSymbols.length) {
      return true;
    }

    return watchlistDraftSymbols.some((symbol, index) => symbol !== runtimeWatchlistSymbols[index]);
  }, [runtimeWatchlistSymbols, watchlistDraftSymbols, watchlistInputValue]);

  const marketStatusLabel = useMemo(() => {
    if (!marketStatus) {
      return null;
    }

    const raw = typeof marketStatus.status_text === 'string' ? marketStatus.status_text.trim() : '';
    const normalized = raw.toLowerCase();
    const byStatus = {
      open: '开盘',
      closed: '休市',
      premarket: '盘前',
      afterhours: '盘后',
    };
    const byText = {
      'market closed (non-trading day)': '休市',
      'market open': '开盘',
      'market closed': '收盘',
      'pre-market': '盘前',
      'after-hours': '盘后',
      'after hours': '盘后',
      'backtest mode': '回测模式',
    };
    if (normalized && byText[normalized]) {
      return byText[normalized];
    }
    if (marketStatus.status && byStatus[marketStatus.status]) {
      return byStatus[marketStatus.status];
    }
    return raw || '状态未知';
  }, [marketStatus]);

  const providerLabelMap = useMemo(() => ({
    yfinance: 'YFinance',
    finnhub: 'Finnhub',
    financial_datasets: 'Financial Datasets',
    local_csv: 'CSV',
    polygon: 'Polygon',
    mock: 'Mock',
    backtest: 'Backtest'
  }), []);

  const livePriceSourceLabel = useMemo(() => {
    const source = marketStatus?.live_quote_provider;
    if (!source) {
      return null;
    }

    const normalized = String(source).trim().toLowerCase();
    return `实时 ${providerLabelMap[normalized] || String(source).trim()}`;
  }, [marketStatus, providerLabelMap]);

  const historicalPriceSourceLabel = useMemo(() => {
    const source = dataSources?.last_success?.prices;
    if (!source) {
      return null;
    }

    const normalized = String(source).trim().toLowerCase();
    return `历史 ${providerLabelMap[normalized] || String(source).trim()}`;
  }, [dataSources, providerLabelMap]);

  const parseWatchlistInput = useCallback((value) => {
    if (typeof value !== 'string') {
      return [];
    }

    return Array.from(
      new Set(
        value
          .split(/[\s,]+/)
          .map((symbol) => symbol.trim().toUpperCase())
          .filter(Boolean)
      )
    );
  }, []);

  const commitWatchlistInput = useCallback((value) => {
    const parsed = parseWatchlistInput(value);
    if (parsed.length === 0) {
      return [];
    }

    setWatchlistDraftSymbols((prev) => Array.from(new Set([...prev, ...parsed])));
    setWatchlistInputValue('');
    if (watchlistFeedback) {
      setWatchlistFeedback(null);
    }
    return parsed;
  }, [parseWatchlistInput, watchlistFeedback]);

  const handleWatchlistRemove = useCallback((symbolToRemove) => {
    setWatchlistDraftSymbols((prev) => prev.filter((symbol) => symbol !== symbolToRemove));
    if (watchlistFeedback) {
      setWatchlistFeedback(null);
    }
  }, [watchlistFeedback]);

  const handleWatchlistPanelToggle = useCallback(() => {
    setIsRuntimeSettingsOpen(false);
    setIsWatchlistPanelOpen((open) => {
      const nextOpen = !open;
      if (nextOpen) {
        setWatchlistDraftSymbols(runtimeWatchlistSymbols);
        setWatchlistInputValue('');
        setWatchlistFeedback(null);
      }
      return nextOpen;
    });
  }, [runtimeWatchlistSymbols]);

  const handleWatchlistInputChange = useCallback((value) => {
    setWatchlistInputValue(value);
    if (watchlistFeedback) {
      setWatchlistFeedback(null);
    }
  }, [watchlistFeedback]);

  const handleWatchlistInputKeyDown = useCallback((e) => {
    if (e.key === 'Enter' || e.key === ',') {
      e.preventDefault();
      commitWatchlistInput(watchlistInputValue);
    }
  }, [commitWatchlistInput, watchlistInputValue]);

  const handleWatchlistSuggestionClick = useCallback((symbol) => {
    if (watchlistDraftSymbols.includes(symbol)) {
      return;
    }
    setWatchlistDraftSymbols((prev) => [...prev, symbol]);
    if (watchlistFeedback) {
      setWatchlistFeedback(null);
    }
  }, [watchlistDraftSymbols, watchlistFeedback]);

  const handleWatchlistRestoreCurrent = useCallback(() => {
    setWatchlistDraftSymbols(runtimeWatchlistSymbols);
    setWatchlistInputValue('');
    setWatchlistFeedback(null);
  }, [runtimeWatchlistSymbols]);

  const handleWatchlistRestoreDefault = useCallback(() => {
    setWatchlistDraftSymbols(watchlistSuggestions);
    setWatchlistInputValue('');
    setWatchlistFeedback(null);
  }, [watchlistSuggestions]);

  const handleWatchlistSave = useCallback(() => {
    const pendingTickers = parseWatchlistInput(watchlistInputValue);
    const nextTickers = Array.from(new Set([...watchlistDraftSymbols, ...pendingTickers]));
    if (nextTickers.length === 0) {
      setWatchlistFeedback({ type: 'error', text: '至少输入 1 个有效股票代码' });
      return;
    }

    if (!clientRef.current) {
      setWatchlistFeedback({ type: 'error', text: '连接未就绪，稍后重试' });
      return;
    }

    setIsWatchlistSaving(true);
    setWatchlistFeedback(null);
    setWatchlistDraftSymbols(nextTickers);
    setWatchlistInputValue('');
    const success = clientRef.current.send({
      type: 'update_watchlist',
      tickers: nextTickers
    });

    if (!success) {
      setIsWatchlistSaving(false);
      setWatchlistFeedback({ type: 'error', text: '发送失败，请检查连接状态' });
    }
  }, [parseWatchlistInput, watchlistDraftSymbols, watchlistInputValue]);

  const handleManualTrigger = useCallback(() => {
    if (!clientRef.current) {
      addSystemMessage('连接未就绪，无法手动触发');
      return;
    }

    const success = clientRef.current.send({
      type: 'trigger_strategy'
    });

    if (!success) {
      addSystemMessage('手动触发发送失败，请检查连接状态');
      return;
    }

    addSystemMessage('已发送手动触发请求');
  }, [addSystemMessage]);

  const handleRuntimeConfigSave = useCallback(() => {
    if (!clientRef.current) {
      setRuntimeConfigFeedback({ type: 'error', text: '连接未就绪，稍后重试' });
      return;
    }

    const interval = Number(intervalMinutesDraft);
    const maxCommCycles = Number(maxCommCyclesDraft);
    if (!Number.isInteger(interval) || interval <= 0) {
      setRuntimeConfigFeedback({ type: 'error', text: '间隔必须是正整数分钟' });
      return;
    }
    if (!Number.isInteger(maxCommCycles) || maxCommCycles <= 0) {
      setRuntimeConfigFeedback({ type: 'error', text: '讨论轮数必须是正整数' });
      return;
    }

    setIsRuntimeConfigSaving(true);
    setRuntimeConfigFeedback(null);
    const success = clientRef.current.send({
      type: 'update_runtime_config',
      schedule_mode: scheduleModeDraft,
      interval_minutes: interval,
      trigger_time: triggerTimeDraft,
      max_comm_cycles: maxCommCycles
    });

    if (!success) {
      setIsRuntimeConfigSaving(false);
      setRuntimeConfigFeedback({ type: 'error', text: '发送失败，请检查连接状态' });
    }
  }, [intervalMinutesDraft, maxCommCyclesDraft, scheduleModeDraft, triggerTimeDraft]);

  const handleRuntimeDefaultsRestore = useCallback(() => {
    setScheduleModeDraft('daily');
    setIntervalMinutesDraft('60');
    setTriggerTimeDraft('09:30');
    setMaxCommCyclesDraft('2');
    setRuntimeConfigFeedback(null);
  }, []);

  const handleRuntimeSettingsToggle = useCallback(() => {
    setRuntimeConfigFeedback(null);
    setAgentSkillsFeedback(null);
    setWorkspaceFileFeedback(null);
    setIsRuntimeSettingsOpen((prev) => !prev);
    setIsWatchlistPanelOpen(false);
  }, []);

  const requestAgentSkills = useCallback((agentId) => {
    const normalized = typeof agentId === 'string' ? agentId.trim() : '';
    if (!normalized || !clientRef.current) {
      return false;
    }
    setIsAgentSkillsLoading(true);
    setAgentSkillsFeedback(null);
    return clientRef.current.send({
      type: 'get_agent_skills',
      agent_id: normalized
    });
  }, []);

  const requestAgentProfile = useCallback((agentId) => {
    const normalized = typeof agentId === 'string' ? agentId.trim() : '';
    if (!normalized || !clientRef.current) {
      return false;
    }
    return clientRef.current.send({
      type: 'get_agent_profile',
      agent_id: normalized
    });
  }, []);

  const requestSkillDetail = useCallback((skillName) => {
    const normalized = typeof skillName === 'string' ? skillName.trim() : '';
    if (!normalized || !clientRef.current) {
      return false;
    }
    const detailKey = `${selectedSkillAgentId}:${normalized}`;
    setSkillDetailLoadingKey(detailKey);
    return clientRef.current.send({
      type: 'get_skill_detail',
      agent_id: selectedSkillAgentId,
      skill_name: normalized
    });
  }, [selectedSkillAgentId]);

  const handleCreateLocalSkill = useCallback((skillName) => {
    const normalized = typeof skillName === 'string' ? skillName.trim() : '';
    if (!normalized) {
      setAgentSkillsFeedback({ type: 'error', text: '技能名称不能为空' });
      return;
    }
    if (!clientRef.current) {
      setAgentSkillsFeedback({ type: 'error', text: '连接未就绪，稍后重试' });
      return;
    }
    setAgentSkillsSavingKey(`${selectedSkillAgentId}:${normalized}:create`);
    setAgentSkillsFeedback(null);
    const success = clientRef.current.send({
      type: 'create_agent_local_skill',
      agent_id: selectedSkillAgentId,
      skill_name: normalized
    });
    if (!success) {
      setAgentSkillsSavingKey(null);
      setAgentSkillsFeedback({ type: 'error', text: '发送失败，请检查连接状态' });
    }
  }, [selectedSkillAgentId]);

  const handleLocalSkillDraftChange = useCallback((skillName, content) => {
    const detailKey = `${selectedSkillAgentId}:${skillName}`;
    setLocalSkillDraftsByKey((prev) => ({
      ...prev,
      [detailKey]: content
    }));
  }, [selectedSkillAgentId]);

  const handleLocalSkillSave = useCallback((skillName) => {
    if (!clientRef.current) {
      setAgentSkillsFeedback({ type: 'error', text: '连接未就绪，稍后重试' });
      return;
    }
    const detailKey = `${selectedSkillAgentId}:${skillName}`;
    const content = localSkillDraftsByKey[detailKey];
    if (typeof content !== 'string') {
      return;
    }
    setAgentSkillsSavingKey(`${selectedSkillAgentId}:${skillName}:content`);
    setAgentSkillsFeedback(null);
    const success = clientRef.current.send({
      type: 'update_agent_local_skill',
      agent_id: selectedSkillAgentId,
      skill_name: skillName,
      content
    });
    if (!success) {
      setAgentSkillsSavingKey(null);
      setAgentSkillsFeedback({ type: 'error', text: '发送失败，请检查连接状态' });
    }
  }, [localSkillDraftsByKey, selectedSkillAgentId]);

  const handleLocalSkillDelete = useCallback((skillName) => {
    if (!clientRef.current) {
      setAgentSkillsFeedback({ type: 'error', text: '连接未就绪，稍后重试' });
      return;
    }
    setAgentSkillsSavingKey(`${selectedSkillAgentId}:${skillName}:delete`);
    setAgentSkillsFeedback(null);
    const success = clientRef.current.send({
      type: 'delete_agent_local_skill',
      agent_id: selectedSkillAgentId,
      skill_name: skillName
    });
    if (!success) {
      setAgentSkillsSavingKey(null);
      setAgentSkillsFeedback({ type: 'error', text: '发送失败，请检查连接状态' });
    }
  }, [selectedSkillAgentId]);

  const handleRemoveSharedSkill = useCallback((skillName) => {
    if (!clientRef.current) {
      setAgentSkillsFeedback({ type: 'error', text: '连接未就绪，稍后重试' });
      return;
    }
    setAgentSkillsSavingKey(`${selectedSkillAgentId}:${skillName}:remove`);
    setAgentSkillsFeedback(null);
    const success = clientRef.current.send({
      type: 'remove_agent_skill',
      agent_id: selectedSkillAgentId,
      skill_name: skillName
    });
    if (!success) {
      setAgentSkillsSavingKey(null);
      setAgentSkillsFeedback({ type: 'error', text: '发送失败，请检查连接状态' });
    }
  }, [selectedSkillAgentId]);

  const requestWorkspaceFile = useCallback((agentId, filename) => {
    const normalizedAgentId = typeof agentId === 'string' ? agentId.trim() : '';
    const normalizedFilename = typeof filename === 'string' ? filename.trim() : '';
    if (!normalizedAgentId || !normalizedFilename || !clientRef.current) {
      return false;
    }
    setIsWorkspaceFileLoading(true);
    setWorkspaceFileFeedback(null);
    return clientRef.current.send({
      type: 'get_agent_workspace_file',
      agent_id: normalizedAgentId,
      filename: normalizedFilename
    });
  }, []);

  const handleAgentSkillToggle = useCallback((skillName, enabled) => {
    if (!clientRef.current) {
      setAgentSkillsFeedback({ type: 'error', text: '连接未就绪，稍后重试' });
      return;
    }

    const agentId = selectedSkillAgentId;
    setAgentSkillsSavingKey(`${agentId}:${skillName}`);
    setAgentSkillsFeedback(null);
    const success = clientRef.current.send({
      type: 'update_agent_skill',
      agent_id: agentId,
      skill_name: skillName,
      enabled
    });

    if (!success) {
      setAgentSkillsSavingKey(null);
      setAgentSkillsFeedback({ type: 'error', text: '发送失败，请检查连接状态' });
    }
  }, [selectedSkillAgentId]);

  const handleSkillAgentChange = useCallback((agentId) => {
    setSelectedSkillAgentId(agentId);
    requestAgentProfile(agentId);
    requestAgentSkills(agentId);
    requestWorkspaceFile(agentId, selectedWorkspaceFile);
  }, [requestAgentProfile, requestAgentSkills, requestWorkspaceFile, selectedWorkspaceFile]);

  const handleWorkspaceFileChange = useCallback((filename) => {
    setSelectedWorkspaceFile(filename);
    requestWorkspaceFile(selectedSkillAgentId, filename);
  }, [requestWorkspaceFile, selectedSkillAgentId]);

  const handleWorkspaceFileSave = useCallback(() => {
    if (!clientRef.current) {
      setWorkspaceFileFeedback({ type: 'error', text: '连接未就绪，稍后重试' });
      return;
    }
    const key = `${selectedSkillAgentId}:${selectedWorkspaceFile}`;
    setWorkspaceFileSavingKey(key);
    setWorkspaceFileFeedback(null);
    const success = clientRef.current.send({
      type: 'update_agent_workspace_file',
      agent_id: selectedSkillAgentId,
      filename: selectedWorkspaceFile,
      content: workspaceDraftContent
    });
    if (!success) {
      setWorkspaceFileSavingKey(null);
      setWorkspaceFileFeedback({ type: 'error', text: '发送失败，请检查连接状态' });
    }
  }, [selectedSkillAgentId, selectedWorkspaceFile, workspaceDraftContent]);

  useEffect(() => {
    setWorkspaceDraftContent(selectedWorkspaceContent);
  }, [selectedWorkspaceContent]);

  useEffect(() => {
    if (currentView !== 'traders' || !isConnected) {
      return;
    }
    AGENTS.forEach((agent) => {
      if (!agentProfilesByAgent[agent.id]) {
        requestAgentProfile(agent.id);
      }
      if (!agentSkillsByAgent[agent.id]) {
        requestAgentSkills(agent.id);
      }
      if (!workspaceFilesByAgent[agent.id]?.['MEMORY.md']) {
        requestWorkspaceFile(agent.id, 'MEMORY.md');
      }
    });
  }, [agentProfilesByAgent, agentSkillsByAgent, currentView, isConnected, requestAgentProfile, requestAgentSkills, requestWorkspaceFile, workspaceFilesByAgent]);

  const requestStockHistory = useCallback((symbol, { force = false } = {}) => {
    const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
    if (!normalized || !clientRef.current) {
      return false;
    }

    if (!force && requestedStockHistoryRef.current.has(normalized)) {
      return false;
    }

    const success = clientRef.current.send({
      type: 'get_stock_history',
      ticker: normalized,
      lookback_days: 120
    });

    if (success) {
      requestedStockHistoryRef.current.add(normalized);
    }

    return success;
  }, []);

  const requestStockExplainEvents = useCallback((symbol) => {
    const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
    if (!normalized || !clientRef.current) {
      return false;
    }
    return clientRef.current.send({
      type: 'get_stock_explain_events',
      ticker: normalized
    });
  }, []);

  const requestStockNews = useCallback((symbol) => {
    const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
    if (!normalized || !clientRef.current) {
      return false;
    }
    return clientRef.current.send({
      type: 'get_stock_news',
      ticker: normalized,
      lookback_days: 45,
      limit: 12
    });
  }, []);

  const requestStockNewsForDate = useCallback((symbol, date) => {
    const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
    if (!normalized || !date || !clientRef.current) {
      return false;
    }
    return clientRef.current.send({
      type: 'get_stock_news_for_date',
      ticker: normalized,
      date,
      limit: 20
    });
  }, []);

  const requestStockNewsTimeline = useCallback((symbol) => {
    const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
    if (!normalized || !clientRef.current) {
      return false;
    }
    return clientRef.current.send({
      type: 'get_stock_news_timeline',
      ticker: normalized,
      lookback_days: 90
    });
  }, []);

  const requestStockNewsCategories = useCallback((symbol) => {
    const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
    if (!normalized || !clientRef.current) {
      return false;
    }
    return clientRef.current.send({
      type: 'get_stock_news_categories',
      ticker: normalized,
      lookback_days: 90
    });
  }, []);

  const requestStockRangeExplain = useCallback((symbol, startDate, endDate, articleIds = []) => {
    const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
    if (!normalized || !startDate || !endDate || !clientRef.current) {
      return false;
    }
    return clientRef.current.send({
      type: 'get_stock_range_explain',
      ticker: normalized,
      start_date: startDate,
      end_date: endDate,
      article_ids: Array.isArray(articleIds) ? articleIds : []
    });
  }, []);

  const requestStockStory = useCallback((symbol, asOfDate = null) => {
    const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
    if (!normalized || !clientRef.current) {
      return false;
    }
    return clientRef.current.send({
      type: 'get_stock_story',
      ticker: normalized,
      as_of_date: asOfDate
    });
  }, []);

  const requestStockSimilarDays = useCallback((symbol, date, topK = 8) => {
    const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
    if (!normalized || !date || !clientRef.current) {
      return false;
    }
    return clientRef.current.send({
      type: 'get_stock_similar_days',
      ticker: normalized,
      date,
      top_k: topK
    });
  }, []);

  const requestStockEnrich = useCallback((symbol, options = {}) => {
    const normalized = typeof symbol === 'string' ? symbol.trim().toUpperCase() : '';
    if (!normalized || !clientRef.current) {
      return false;
    }
    const startDate = typeof options.startDate === 'string' ? options.startDate.trim() : '';
    const endDate = typeof options.endDate === 'string' ? options.endDate.trim() : '';
    if (!startDate || !endDate) {
      return false;
    }
    setNewsByTicker((prev) => ({
      ...prev,
      [normalized]: {
        ...(prev[normalized] || {}),
        maintenanceStatus: {
          running: true,
          error: null,
          updatedAt: new Date().toISOString(),
          stats: null
        }
      }
    }));
    return clientRef.current.send({
      type: 'run_stock_enrich',
      ticker: normalized,
      start_date: startDate,
      end_date: endDate,
      force: Boolean(options.force),
      only_local_to_llm: Boolean(options.onlyLocalToLlm),
      rebuild_story: Boolean(options.rebuildStory),
      rebuild_similar_days: Boolean(options.rebuildSimilarDays),
      story_date: options.storyDate || null,
      target_date: options.targetDate || null
    });
  }, []);

  // Switch away from LIVE tab when market closes
  useEffect(() => {
    if (!isLiveEnabled && chartTab === 'live') {
      setChartTab('all');
    }
  }, [isLiveEnabled, chartTab]);

  useEffect(() => {
    if (!isWatchlistPanelOpen || !isWatchlistDraftDirty) {
      setWatchlistDraftSymbols(runtimeWatchlistSymbols);
      if (!isWatchlistPanelOpen) {
        setWatchlistInputValue('');
      }
    }
  }, [isWatchlistDraftDirty, isWatchlistPanelOpen, runtimeWatchlistSymbols]);

  useEffect(() => {
    isWatchlistSavingRef.current = isWatchlistSaving;
  }, [isWatchlistSaving]);

  useEffect(() => {
    isRuntimeConfigSavingRef.current = isRuntimeConfigSaving;
  }, [isRuntimeConfigSaving]);

  useEffect(() => {
    if (currentView !== 'explain' || !selectedExplainSymbol) {
      return;
    }
    requestStockHistory(selectedExplainSymbol);
    requestStockExplainEvents(selectedExplainSymbol);
    requestStockNews(selectedExplainSymbol);
    requestStockNewsTimeline(selectedExplainSymbol);
    requestStockNewsCategories(selectedExplainSymbol);
    requestStockStory(selectedExplainSymbol, currentDate);
  }, [
    currentDate,
    currentView,
    requestStockExplainEvents,
    requestStockHistory,
    requestStockNews,
    requestStockNewsCategories,
    requestStockNewsTimeline,
    requestStockStory,
    selectedExplainSymbol
  ]);

  // Clock - use virtual time if available (for mock mode)
  useEffect(() => {
    if (virtualTime) {
      // In mock mode, calculate offset from real time
      const virtualTimeMs = new Date(virtualTime).getTime();
      const realTimeMs = Date.now();
      virtualTimeOffsetRef.current = virtualTimeMs - realTimeMs;
      lastVirtualTimeRef.current = virtualTimeMs;
      setNow(new Date(virtualTime));

      // Update clock every second based on offset
      const id = setInterval(() => {
        const currentRealTime = Date.now();
        const currentVirtualTime = currentRealTime + virtualTimeOffsetRef.current;
        setNow(new Date(currentVirtualTime));
      }, 1000);

      return () => clearInterval(id);
    } else {
      // In live mode, use real time
      const id = setInterval(() => setNow(new Date()), 1000);
      return () => clearInterval(id);
    }
  }, [virtualTime]);

  // Update clock when virtual time changes (recalculate offset)
  useEffect(() => {
    if (virtualTime) {
      const virtualTimeMs = new Date(virtualTime).getTime();
      const realTimeMs = Date.now();
      virtualTimeOffsetRef.current = virtualTimeMs - realTimeMs;
      lastVirtualTimeRef.current = virtualTimeMs;
      setNow(new Date(virtualTime));
    }
  }, [virtualTime]);

  // Track updates with visual feedback
  useEffect(() => {
    setLastUpdate(new Date());
    setIsUpdating(true);
    const timer = setTimeout(() => setIsUpdating(false), 500);
    return () => clearTimeout(timer);
  }, [holdings, stats, trades, portfolioData.netValue]);

  // Initial animation flag for slider speed
  useEffect(() => {
    const completeTimer = setTimeout(() => {
      setIsInitialAnimating(false);
    }, 1800);

    return () => {
      clearTimeout(completeTimer);
    };
  }, []);


  // Helper to check if bubble should still be visible
  // Bubbles persist until replaced by ANY new message (cross-role)
  // When any agent sends a new message, all previous bubbles are cleared
  // Can search by agentId or agentName
  const bubbleFor = (idOrName) => {
    // First try direct lookup by id
    let b = bubbles[idOrName];
    if (b) {
      return b;
    }

    // If not found, search by agentName
    const agent = AGENTS.find(a => a.name === idOrName || a.id === idOrName);
    if (agent) {
      b = bubbles[agent.id];
      if (b) {
        return b;
      }
    }

    return null;
  };

  // Handle jump to message in feed
  const handleJumpToMessage = useCallback((bubble) => {
    // Switch to room tab (if not already there) for better context
    // Then scroll AgentFeed to the message
    if (agentFeedRef.current && agentFeedRef.current.scrollToMessage) {
      agentFeedRef.current.scrollToMessage(bubble);
    }
  }, []);

  // Auto-connect to server on mount
  useEffect(() => {
    // Define pushEvent inside useEffect to avoid dependency issues
    const handlePushEvent = (evt) => {
      if (!evt) return;

      try {
        handleEventInternal(evt);
      } catch (error) {
        console.error('[Event Handler] Error:', error);
      }
    };

    const handleEventInternal = (evt) => {
      if (evt?.type && evt.type !== 'pong') {
        setConnectionStatus('connected');
        setIsConnected(true);
      }

      // Helper: Update tickers from realtime prices
      const updateTickersFromPrices = (realtimePrices) => {
        try {
          setTickers(prevTickers => {
            return prevTickers.map(ticker => {
              const realtimeData = realtimePrices[ticker.symbol];
              if (realtimeData && realtimeData.price !== null && realtimeData.price !== undefined) {
                // Use 'ret' from realtime data (relative to open price) if available
                const newChange = (realtimeData.ret !== null && realtimeData.ret !== undefined)
                  ? realtimeData.ret
                  : (ticker.change !== null && ticker.change !== undefined ? ticker.change : 0);

                return {
                  ...ticker,
                  price: realtimeData.price,
                  change: newChange,
                  open: realtimeData.open || ticker.open
                };
              }
              return ticker;
            });
          });
        } catch (error) {
          console.error('Error updating tickers from prices:', error);
        }
      };

      const handlers = {
        // Error response (for fast forward errors)
        error: (e) => {
          const message = typeof e.message === 'string' ? e.message : '请求失败';
          console.error('[Error]', message);
          setIsAgentSkillsLoading(false);
          setSkillDetailLoadingKey(null);
          setAgentSkillsSavingKey(null);
          setIsWorkspaceFileLoading(false);
          setWorkspaceFileSavingKey(null);
          if (isWatchlistSavingRef.current) {
            setIsWatchlistSaving(false);
            setWatchlistFeedback({ type: 'error', text: message || '更新 watchlist 失败' });
          }
          if (isRuntimeConfigSavingRef.current) {
            setIsRuntimeConfigSaving(false);
            setRuntimeConfigFeedback({ type: 'error', text: message });
          }
          if (message.includes('skill') || message.includes('agent_id')) {
            setAgentSkillsFeedback({ type: 'error', text: message || '更新技能失败' });
          }
          if (message.includes('workspace_file') || message.includes('filename')) {
            setWorkspaceFileFeedback({ type: 'error', text: message || '更新工作区文件失败' });
          }

          // Handle fast forward errors
          if (message.includes('fast forward')) {
            console.warn(`⚠️ ${message}`);
            handlePushEvent({
              type: 'system',
              content: `⚠️ ${message}`,
              timestamp: Date.now()
            });
          }
          addSystemMessage(message);
        },

        // Connection events
        system: (e) => {
          console.log('[System]', e.content);
          if (
            e.content.includes('Connected')
            || e.content.includes('已连接')
          ) {
            setConnectionStatus('connected');
            setIsConnected(true);
          } else if (
            e.content.includes('Disconnected')
            || e.content.includes('断开')
          ) {
            setConnectionStatus('disconnected');
            setIsConnected(false);
          }
          processFeedEvent(e);
        },

        // Pong response from server
        pong: (e) => {
          console.log('[Heartbeat] Pong received');
        },

        // Initial state from server
        initial_state: (e) => {
          try {
            const state = e.state;
            if (!state) return;

            setConnectionStatus('connected');
            setIsConnected(true);
            setSystemStatus(state.status || 'initializing');
            setCurrentDate(state.current_date);

            // 设置服务器模式和市场状态
            if (state.server_mode) {
              setServerMode(state.server_mode);
            }
            if (state.data_sources) {
              setDataSources(state.data_sources);
            }
            if (state.runtime_config) {
              setRuntimeConfig(state.runtime_config);
            }
            if (Array.isArray(state.tickers) && state.tickers.length > 0) {
              setTickers(prevTickers => buildTickersFromSymbols(state.tickers, prevTickers));
            }
            // 检查是否是mock模式
            const isMockMode = state.is_mock_mode === true;
            if (state.market_status) {
              setMarketStatus(state.market_status);
              // 只在Mock模式下，如果市场状态包含虚拟时间，才保存它
              if (isMockMode && state.market_status.current_time) {
                try {
                  const virtualTimeDate = new Date(state.market_status.current_time);
                  setVirtualTime(virtualTimeDate);
                } catch (error) {
                  console.error('Error parsing virtual time from market_status:', error);
                }
              } else {
                // 非Mock模式下清除virtualTime
                setVirtualTime(null);
              }
            }

            if (state.trading_days_total) {
              setProgress({
                current: state.trading_days_completed || 0,
                total: state.trading_days_total
              });
            }

            if (state.portfolio) {
              setPortfolioData(prev => ({
                ...prev,
                netValue: state.portfolio.total_value || prev.netValue,
                pnl: state.portfolio.pnl_percent || 0,
                equity: state.portfolio.equity || prev.equity,
                baseline: state.portfolio.baseline || prev.baseline,
                baseline_vw: state.portfolio.baseline_vw || prev.baseline_vw,
                momentum: state.portfolio.momentum || prev.momentum,
                strategies: state.portfolio.strategies || prev.strategies,
                equity_return: state.portfolio.equity_return || prev.equity_return,
                baseline_return: state.portfolio.baseline_return || prev.baseline_return,
                baseline_vw_return: state.portfolio.baseline_vw_return || prev.baseline_vw_return,
                momentum_return: state.portfolio.momentum_return || prev.momentum_return
              }));
            }

            if (state.dashboard) {
              if (state.dashboard.holdings) setHoldings(state.dashboard.holdings);
              if (state.dashboard.trades) setTrades(state.dashboard.trades);
              if (state.dashboard.stats) setStats(state.dashboard.stats);
              if (state.dashboard.leaderboard) setLeaderboard(state.dashboard.leaderboard);
            }
            if (state.realtime_prices) updateTickersFromPrices(state.realtime_prices);
            if (state.price_history) {
              setPriceHistoryByTicker(normalizePriceHistory(state.price_history));
            }

            // Load and process historical feed data
            if (state.feed_history && Array.isArray(state.feed_history)) {
              console.log(`✅ Loading ${state.feed_history.length} historical events`);
              processHistoricalFeed(state.feed_history);
            }

            // Load last day history for replay
            if (state.last_day_history && Array.isArray(state.last_day_history)) {
              setLastDayHistory(state.last_day_history);
              console.log(`✅ Loaded ${state.last_day_history.length} last day events for replay`);
            }

            console.log('Initial state loaded');
          } catch (error) {
            console.error('Error loading initial state:', error);
          }
        },

        // Market status update
        market_status_update: (e) => {
          if (e.market_status) {
            setMarketStatus(e.market_status);
          }
        },

        data_sources_update: (e) => {
          if (e.data_sources) {
            setDataSources(e.data_sources);
          }
        },

        runtime_assets_reloaded: (e) => {
          if (e.runtime_config_applied) {
            setRuntimeConfig(e.runtime_config_applied);
          }
          if (Array.isArray(e.runtime_config_applied?.tickers)) {
            setTickers(prevTickers => buildTickersFromSymbols(e.runtime_config_applied.tickers, prevTickers));
            setWatchlistDraftSymbols(e.runtime_config_applied.tickers.map((symbol) => String(symbol).trim().toUpperCase()));
            setWatchlistInputValue('');
          }
          if (isWatchlistSavingRef.current) {
            setIsWatchlistSaving(false);
          }
          if (isRuntimeConfigSavingRef.current) {
            setIsRuntimeConfigSaving(false);
            setRuntimeConfigFeedback({ type: 'success', text: '运行配置已保存并生效' });
          }
          const warnings = Array.isArray(e.runtime_config_warnings) ? e.runtime_config_warnings : [];
          warnings.forEach((warning) => addSystemMessage(warning));
          addSystemMessage('运行时配置已热更新');
        },

        agent_skills_loaded: (e) => {
          const agentId = typeof e.agent_id === 'string' ? e.agent_id.trim() : '';
          if (!agentId) {
            setIsAgentSkillsLoading(false);
            return;
          }
          setAgentSkillsByAgent((prev) => ({
            ...prev,
            [agentId]: Array.isArray(e.skills) ? e.skills : []
          }));
          setIsAgentSkillsLoading(false);
          setAgentSkillsSavingKey(null);
        },

        agent_profile_loaded: (e) => {
          const agentId = typeof e.agent_id === 'string' ? e.agent_id.trim() : '';
          if (!agentId) {
            return;
          }
          setAgentProfilesByAgent((prev) => ({
            ...prev,
            [agentId]: e.profile && typeof e.profile === 'object' ? e.profile : {}
          }));
        },

        skill_detail_loaded: (e) => {
          const skillName = typeof e.skill?.skill_name === 'string' ? e.skill.skill_name.trim() : '';
          const agentId = typeof e.agent_id === 'string' ? e.agent_id.trim() : selectedSkillAgentId;
          if (!skillName) {
            setSkillDetailLoadingKey(null);
            return;
          }
          const detailKey = `${agentId}:${skillName}`;
          setSkillDetailsByName((prev) => ({
            ...prev,
            [detailKey]: e.skill
          }));
          setLocalSkillDraftsByKey((prev) => ({
            ...prev,
            [detailKey]: typeof e.skill?.content === 'string' ? e.skill.content : ''
          }));
          setSkillDetailLoadingKey(null);
        },

        agent_skill_updated: (e) => {
          const agentId = typeof e.agent_id === 'string' ? e.agent_id.trim() : '';
          const skillName = typeof e.skill_name === 'string' ? e.skill_name.trim() : '';
          if (!agentId || !skillName) {
            return;
          }
          setAgentSkillsFeedback({
            type: 'success',
            text: `${agentId} ${e.enabled ? '已启用' : '已禁用'} ${skillName}`
          });
        },

        agent_local_skill_created: (e) => {
          const agentId = typeof e.agent_id === 'string' ? e.agent_id.trim() : '';
          const skillName = typeof e.skill_name === 'string' ? e.skill_name.trim() : '';
          setAgentSkillsSavingKey(null);
          if (!agentId || !skillName) {
            return;
          }
          setAgentSkillsFeedback({
            type: 'success',
            text: `${agentId} 已创建本地技能 ${skillName}`
          });
        },

        agent_local_skill_updated: (e) => {
          const agentId = typeof e.agent_id === 'string' ? e.agent_id.trim() : '';
          const skillName = typeof e.skill_name === 'string' ? e.skill_name.trim() : '';
          setAgentSkillsSavingKey(null);
          if (!agentId || !skillName) {
            return;
          }
          setAgentSkillsFeedback({
            type: 'success',
            text: `${agentId} 的本地技能 ${skillName} 已保存`
          });
        },

        agent_local_skill_deleted: (e) => {
          const agentId = typeof e.agent_id === 'string' ? e.agent_id.trim() : '';
          const skillName = typeof e.skill_name === 'string' ? e.skill_name.trim() : '';
          setAgentSkillsSavingKey(null);
          if (!agentId || !skillName) {
            return;
          }
          setSkillDetailsByName((prev) => {
            const next = { ...prev };
            delete next[`${agentId}:${skillName}`];
            return next;
          });
          setLocalSkillDraftsByKey((prev) => {
            const next = { ...prev };
            delete next[`${agentId}:${skillName}`];
            return next;
          });
          setAgentSkillsFeedback({
            type: 'success',
            text: `${agentId} 的本地技能 ${skillName} 已删除`
          });
        },

        agent_skill_removed: (e) => {
          const agentId = typeof e.agent_id === 'string' ? e.agent_id.trim() : '';
          const skillName = typeof e.skill_name === 'string' ? e.skill_name.trim() : '';
          setAgentSkillsSavingKey(null);
          if (!agentId || !skillName) {
            return;
          }
          setAgentSkillsFeedback({
            type: 'success',
            text: `${agentId} 已移除共享技能 ${skillName}`
          });
        },

        agent_workspace_file_loaded: (e) => {
          const agentId = typeof e.agent_id === 'string' ? e.agent_id.trim() : '';
          const filename = typeof e.filename === 'string' ? e.filename.trim() : '';
          if (!agentId || !filename) {
            setIsWorkspaceFileLoading(false);
            return;
          }
          setWorkspaceFilesByAgent((prev) => ({
            ...prev,
            [agentId]: {
              ...(prev[agentId] || {}),
              [filename]: typeof e.content === 'string' ? e.content : ''
            }
          }));
          setIsWorkspaceFileLoading(false);
          setWorkspaceFileSavingKey(null);
        },

        agent_workspace_file_updated: (e) => {
          const agentId = typeof e.agent_id === 'string' ? e.agent_id.trim() : '';
          const filename = typeof e.filename === 'string' ? e.filename.trim() : '';
          if (!agentId || !filename) {
            return;
          }
          setWorkspaceFileFeedback({
            type: 'success',
            text: `${agentId} 的 ${filename} 已保存`
          });
        },

        watchlist_updated: (e) => {
          if (Array.isArray(e.tickers)) {
            const normalizedTickers = e.tickers.map((symbol) => String(symbol).trim().toUpperCase());
            requestedStockHistoryRef.current = new Set(
              Array.from(requestedStockHistoryRef.current).filter((symbol) => normalizedTickers.includes(symbol))
            );
            setRuntimeConfig((prev) => ({
              ...(prev || {}),
              tickers: normalizedTickers
            }));
            setTickers((prevTickers) => buildTickersFromSymbols(normalizedTickers, prevTickers));
            setWatchlistDraftSymbols(normalizedTickers);
            setWatchlistInputValue('');
          }
          setIsWatchlistSaving(false);
          setWatchlistFeedback({
            type: 'success',
            text: `已更新为 ${Array.isArray(e.tickers) ? e.tickers.join(', ') : '最新列表'}`
          });
        },

        stock_history_loaded: (e) => {
          const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
          if (!symbol) {
            return;
          }

          if (Array.isArray(e.prices)) {
            setOhlcHistoryByTicker((prev) => ({
              ...prev,
              [symbol]: e.prices
            }));
            setHistorySourceByTicker((prev) => ({
              ...prev,
              [symbol]: e.source || null
            }));
          }
        },

        stock_explain_events_loaded: (e) => {
          const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
          if (!symbol) {
            return;
          }
          setExplainEventsByTicker((prev) => ({
            ...prev,
            [symbol]: {
              events: Array.isArray(e.events) ? e.events : [],
              signals: Array.isArray(e.signals) ? e.signals : [],
              trades: Array.isArray(e.trades) ? e.trades : []
            }
          }));
        },

        stock_news_loaded: (e) => {
          const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
          if (!symbol) {
            return;
          }
          setNewsByTicker((prev) => ({
            ...prev,
            [symbol]: {
              ...(prev[symbol] || {}),
              items: Array.isArray(e.news) ? e.news : [],
              source: e.source || null,
              startDate: e.start_date || null,
              endDate: e.end_date || null
            }
          }));
          requestStockNewsTimeline(symbol);
        },

        stock_news_for_date_loaded: (e) => {
          const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
          const date = typeof e.date === 'string' ? e.date.trim() : '';
          if (!symbol || !date) {
            return;
          }
          setNewsByTicker((prev) => ({
            ...prev,
            [symbol]: {
              ...(prev[symbol] || {}),
              byDate: {
                ...((prev[symbol] && prev[symbol].byDate) || {}),
                [date]: Array.isArray(e.news) ? e.news : []
              }
            }
          }));
        },

        stock_news_timeline_loaded: (e) => {
          const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
          if (!symbol) {
            return;
          }
          setNewsByTicker((prev) => ({
            ...prev,
            [symbol]: {
              ...(prev[symbol] || {}),
              timeline: Array.isArray(e.timeline) ? e.timeline : [],
              timelineStartDate: e.start_date || null,
              timelineEndDate: e.end_date || null
            }
          }));
        },

        stock_news_categories_loaded: (e) => {
          const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
          if (!symbol) {
            return;
          }
          setNewsByTicker((prev) => ({
            ...prev,
            [symbol]: {
              ...(prev[symbol] || {}),
              categories: e.categories || {},
              categoriesStartDate: e.start_date || null,
              categoriesEndDate: e.end_date || null
            }
          }));
        },

        stock_range_explain_loaded: (e) => {
          const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
          if (!symbol) {
            return;
          }
          const result = e.result && typeof e.result === 'object' ? e.result : null;
          if (!result?.start_date || !result?.end_date) {
            return;
          }
          const cacheKey = `${result.start_date}:${result.end_date}`;
          setNewsByTicker((prev) => ({
            ...prev,
            [symbol]: {
              ...(prev[symbol] || {}),
              rangeExplainCache: {
                ...((prev[symbol] && prev[symbol].rangeExplainCache) || {}),
                [cacheKey]: result
              }
            }
          }));
        },

        stock_story_loaded: (e) => {
          const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
          const asOfDate = typeof e.as_of_date === 'string' ? e.as_of_date.trim() : '';
          if (!symbol || !asOfDate) {
            return;
          }
          setNewsByTicker((prev) => ({
            ...prev,
            [symbol]: {
              ...(prev[symbol] || {}),
              storyCache: {
                ...((prev[symbol] && prev[symbol].storyCache) || {}),
                [asOfDate]: {
                  story: e.story || '',
                  source: e.source || null,
                  asOfDate
                }
              }
            }
          }));
        },

        stock_similar_days_loaded: (e) => {
          const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
          const date = typeof e.target_date === 'string'
            ? e.target_date.trim()
            : typeof e.date === 'string'
              ? e.date.trim()
              : '';
          if (!symbol || !date) {
            return;
          }
          setNewsByTicker((prev) => ({
            ...prev,
            [symbol]: {
              ...(prev[symbol] || {}),
              similarDaysCache: {
                ...((prev[symbol] && prev[symbol].similarDaysCache) || {}),
                [date]: {
                  target_features: e.target_features || {},
                  items: Array.isArray(e.items) ? e.items : [],
                  error: e.error || null
                }
              }
            }
          }));
        },

        stock_enrich_completed: (e) => {
          const symbol = typeof e.ticker === 'string' ? e.ticker.trim().toUpperCase() : '';
          if (!symbol) {
            return;
          }
          const completedAt = new Date().toISOString();
          const historyEntry = {
            timestamp: completedAt,
            startDate: e.start_date || '',
            endDate: e.end_date || '',
            force: Boolean(e.force),
            onlyLocalToLlm: Boolean(e.only_local_to_llm),
            error: e.error || null,
            stats: e.stats || null,
            storyStatus: e.story_status || null,
            similarStatus: e.similar_status || null
          };
          setNewsByTicker((prev) => ({
            ...prev,
            [symbol]: {
              ...(prev[symbol] || {}),
              items: [],
              byDate: {},
              timeline: [],
              categories: {},
              rangeExplainCache: {},
              storyCache: {},
              similarDaysCache: {},
              maintenanceStatus: {
                running: false,
                error: e.error || null,
                updatedAt: completedAt,
                stats: e.stats || null,
                storyStatus: e.story_status || null,
                similarStatus: e.similar_status || null
              },
              maintenanceHistory: [
                historyEntry,
                ...(((prev[symbol] && prev[symbol].maintenanceHistory) || []).slice(0, 7))
              ]
            }
          }));
          if (!e.error) {
            requestStockNews(symbol);
            requestStockNewsTimeline(symbol);
            requestStockNewsCategories(symbol);
          }
        },

        // Real-time price updates
        price_update: (e) => {
          try {
            const { symbol, price, ret, open, portfolio, realtime_prices } = e;

            if (!symbol || !price) {
              console.warn('[Price Update] Missing symbol or price:', e);
              return;
            }

            setConnectionStatus('connected');
            setIsConnected(true);
            console.log(`[Price Update] ${symbol}: $${price} (ret: ${ret !== undefined ? ret.toFixed(2) : 'N/A'}%)`);

            setPriceHistoryByTicker((prev) => {
              const ticker = String(symbol).trim().toUpperCase();
              const nextPoint = {
                timestamp: new Date().toISOString(),
                label: now.toISOString(),
                price: Number(price)
              };
              const existing = Array.isArray(prev[ticker]) ? prev[ticker] : [];
              const lastPoint = existing[existing.length - 1];
              if (lastPoint && Number(lastPoint.price) === Number(nextPoint.price)) {
                return prev;
              }
              return {
                ...prev,
                [ticker]: [...existing, nextPoint].slice(-120)
              };
            });

            // Update ticker price with animation
            setTickers(prevTickers => {
              return prevTickers.map(ticker => {
                if (ticker.symbol === symbol) {
                  const oldPrice = ticker.price;

                  // Use 'ret' from server (relative to open price) if available
                  // Otherwise fallback to calculating change from previous price
                  let newChange = ticker.change;
                  if (ret !== null && ret !== undefined) {
                    // Use server-provided ret (relative to open price)
                    newChange = ret;
                  } else if (oldPrice !== null && oldPrice !== undefined && isFinite(oldPrice)) {
                    // Fallback: calculate change from previous price
                    const priceChange = ((price - oldPrice) / oldPrice) * 100;
                    newChange = (newChange !== null && newChange !== undefined)
                      ? newChange + priceChange
                      : priceChange;
                  } else {
                    // First price received, set change to 0
                    newChange = 0;
                  }

                  // Trigger rolling animation only if price actually changed
                  if (oldPrice !== price) {
                    setRollingTickers(prev => ({ ...prev, [symbol]: true }));
                    setTimeout(() => {
                      setRollingTickers(prev => ({ ...prev, [symbol]: false }));
                    }, 500);
                  }

                  return {
                    ...ticker,
                    price: price,
                    change: newChange,
                    open: open || ticker.open  // Store open price
                  };
                }
                return ticker;
              });
            });

            // Update all tickers from realtime_prices if provided
            if (realtime_prices) {
              updateTickersFromPrices(realtime_prices);
            }

            // Update portfolio value if provided
            if (portfolio && portfolio.total_value) {
              setPortfolioData(prev => ({
                ...prev,
                netValue: portfolio.total_value,
                pnl: portfolio.pnl_percent || 0,
                equity: portfolio.equity || prev.equity  // Update equity curve
              }));
            }
          } catch (error) {
            console.error('[Price Update] Error:', error);
          }
        },

        // Day progress events
        day_start: (e) => {
          setCurrentDate(e.date);
          if (e.progress !== undefined) {
            setProgress(prev => ({
              ...prev,
              current: Math.floor(e.progress * (prev.total || 1))
            }));
          }
          setSystemStatus('running');
          processFeedEvent(e);
        },

        day_complete: (e) => {
          // Update from day result
          const result = e.result;
          if (result && typeof result === 'object') {
            // Update portfolio equity if available
            if (result.portfolio_summary) {
              const summary = result.portfolio_summary;
              setPortfolioData(prev => {
                const newEquity = [...prev.equity];
                // Add new data point
                const dateObj = new Date(e.date);
                newEquity.push({
                  t: dateObj.getTime(),
                  v: summary.total_value || summary.cash || prev.netValue
                });

                return {
                  ...prev,
                  netValue: summary.total_value || summary.cash || prev.netValue,
                  pnl: summary.pnl_percent || 0,
                  equity: newEquity
                };
              });
            }
          }
          processFeedEvent(e);
        },

        day_error: (e) => {
          console.error('Day error:', e.date, e.error);
          processFeedEvent(e);
        },

        conference_start: (e) => {
          processFeedEvent(e);
        },

        conference_end: (e) => {
          processFeedEvent(e);
        },

        agent_message: (e) => {
          const agent = AGENTS.find(a => a.id === e.agentId);

          // Update bubbles for room view
          setBubbles({
            [e.agentId]: {
              text: e.content,
              ts: Date.now(),
              agentName: agent?.name || e.agentName || e.agentId
            }
          });

          processFeedEvent(e);
        },

        conference_message: (e) => {
          const agent = AGENTS.find(a => a.id === e.agentId);

          // Update bubbles for room view
          setBubbles({
            [e.agentId]: {
              text: e.content,
              ts: Date.now(),
              agentName: agent?.name || e.agentName || e.agentId
            }
          });

          processFeedEvent(e);
        },

        memory: (e) => {
          processFeedEvent(e);
        },

        team_summary: (e) => {
          // Update portfolio data silently without creating feed messages
          setPortfolioData(prev => ({
            ...prev,
            netValue: e.balance || prev.netValue,
            pnl: e.pnlPct || 0,
            equity: e.equity || prev.equity,
            baseline: e.baseline || prev.baseline,
            baseline_vw: e.baseline_vw || prev.baseline_vw,
            momentum: e.momentum || prev.momentum,
            equity_return: e.equity_return || prev.equity_return,
            baseline_return: e.baseline_return || prev.baseline_return,
            baseline_vw_return: e.baseline_vw_return || prev.baseline_vw_return,
            momentum_return: e.momentum_return || prev.momentum_return
          }));

          // Portfolio updates are shown in the ticker bar, no need for feed messages
        },

        team_portfolio: (e) => {
          if (e.holdings) setHoldings(e.holdings);
        },

        // ✅ 监听 holdings 更新（服务器广播的事件名）
        team_holdings: (e) => {
          if (e.data && Array.isArray(e.data)) {
            setHoldings(e.data);
            console.log(`✅ Holdings updated: ${e.data.length} positions`);
          }
        },

        team_trades: (e) => {
          // 支持两种格式：完整列表或单笔交易
          if (e.mode === 'full' && e.data && Array.isArray(e.data)) {
            setTrades(e.data);
            console.log(`✅ Trades updated (full): ${e.data.length} trades`);
          } else if (Array.isArray(e.trades)) {
            setTrades(e.trades);
          } else if (e.trade) {
            setTrades(prev => [e.trade, ...prev].slice(0, 100));
          }
        },

        team_stats: (e) => {
          if (e.data) {
            setStats(e.data);
            console.log('✅ Stats updated');
          } else if (e.stats) {
            setStats(e.stats);
          }
        },

        team_leaderboard: (e) => {
          // 服务器发送的格式: { type: 'team_leaderboard', data: [...], timestamp: ... }
          if (Array.isArray(e.data)) {
            setLeaderboard(e.data);
            console.log('✅ Leaderboard updated:', e.data.length, 'agents');
          } else if (Array.isArray(e.rows)) {
            setLeaderboard(e.rows);
          } else if (Array.isArray(e.leaderboard)) {
            setLeaderboard(e.leaderboard);
          }
        },

        // 虚拟时间更新（Mock模式下的时间广播）
        time_update: (e) => {
          if (e.beijing_time_str) {
            const statusEmoji = {
              'market_open': '📊',
              'off_market': '⏸️',
              'non_trading_day': '📅',
              'trade_execution': '💼'
            };

            const emoji = statusEmoji[e.status] || '⏰';
            const isMockMode = e.is_mock_mode === true;
            let logMessage = `${emoji} ${isMockMode ? '虚拟时间' : '时间'}: ${e.beijing_time_str} | 状态: ${e.status}`;

            if (e.hours_to_open !== undefined) {
              logMessage += ` | 距离开盘: ${e.hours_to_open}小时`;
            }
            if (e.hours_to_trade !== undefined) {
              logMessage += ` | 距离交易: ${e.hours_to_trade}小时`;
            }
            if (e.trading_date) {
              logMessage += ` | 交易日: ${e.trading_date}`;
            }

            console.log(logMessage);

            // 只在Mock模式下保存虚拟时间（用于图表过滤和UI显示）
            if (isMockMode && e.beijing_time) {
              try {
                const virtualTimeDate = new Date(e.beijing_time);
                setVirtualTime(virtualTimeDate);
              } catch (error) {
                console.error('Error parsing virtual time:', error);
              }
            } else {
              // 非Mock模式下清除virtualTime
              setVirtualTime(null);
            }
          }

          // 更新市场状态（如果包含在time_update中）
          if (e.market_status) {
            setMarketStatus(e.market_status);
          }
        },

        // 时间快进事件（Mock模式）
        time_fast_forwarded: (e) => {
          console.log(`⏩ 时间已快进 ${e.minutes} 分钟: ${e.old_time_str} → ${e.new_time_str}`);

          // 更新虚拟时间
          if (e.new_time) {
            try {
              const virtualTimeDate = new Date(e.new_time);
              setVirtualTime(virtualTimeDate);

              // 添加到feed显示
              handlePushEvent({
                type: 'system',
                content: `⏩ 时间快进 ${e.minutes} 分钟: ${e.old_time_str} → ${e.new_time_str}`,
                timestamp: Date.now()
              });
            } catch (error) {
              console.error('Error parsing fast forwarded time:', error);
            }
          }
        },

        // 快进成功响应
        fast_forward_success: (e) => {
          console.log(`✅ ${e.message}`);
        },
      };

      // Call handler or do nothing
      try {
        const handler = handlers[evt.type];
        if (handler) {
          handler(evt);
        } else {
          console.log('[handleEvent] Unknown event type:', evt.type);
        }
      } catch (error) {
        console.error('[handleEvent] Error handling event:', evt.type, error);
      }
    };

    // Create and connect WebSocket client
    const client = new ReadOnlyClient(handlePushEvent);
    clientRef.current = client;
    client.connect();
    setConnectionStatus('connecting');

    return () => {
      // Cleanup on unmount
      if (clientRef.current) {
        clientRef.current.disconnect();
      }
    };
  }, [
    addSystemMessage,
    buildTickersFromSymbols,
    processFeedEvent,
    processHistoricalFeed,
    requestStockNewsCategories,
    requestStockNewsTimeline
  ]); // Only reconnect if handlers change

  // Resizing handlers
  const handleMouseDown = (e) => {
    e.preventDefault();
    setIsResizing(true);
  };

  useEffect(() => {
    if (!isResizing) return;

    const handleMouseMove = (e) => {
      if (!containerRef.current) return;
      const containerRect = containerRef.current.getBoundingClientRect();
      const newLeftWidth = ((e.clientX - containerRect.left) / containerRect.width) * 100;

      // Limit between 30% and 85%
      if (newLeftWidth >= 30 && newLeftWidth <= 85) {
        setLeftWidth(newLeftWidth);
      }
    };

    const handleMouseUp = () => {
      setIsResizing(false);
    };

    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseup', handleMouseUp);

    return () => {
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseup', handleMouseUp);
    };
  }, [isResizing]);

  return (
    <div className="app">
      <GlobalStyles />

      {/* Header */}
      <div className="header">
        <Header />

        <div className="header-right" style={{ display: 'flex', alignItems: 'center', gap: 24, marginLeft: 'auto', flexWrap: 'wrap', minWidth: 0 }}>
          {/* Mock Mode Indicator */}
          {virtualTime && (
            <div style={{
              display: 'flex',
              alignItems: 'center',
              gap: 6,
              padding: '4px 10px',
              borderRadius: 4,
              background: '#FF9800',
              border: '1px solid #FFB74D'
            }}>
              <span style={{ fontSize: '14px' }}></span>
              <span style={{
                fontSize: '11px',
                fontWeight: 600,
                color: '#FFFFFF',
                fontFamily: '"Courier New", monospace',
                letterSpacing: '0.5px'
              }}>
                模拟模式
              </span>
            </div>
          )}


          {/* Clock Display (only in Mock mode) */}
          {virtualTime && (
            <div style={{
              display: 'flex',
              alignItems: 'center',
              gap: 8
            }}>
              <div style={{
                display: 'flex',
                flexDirection: 'column',
                alignItems: 'flex-end',
                gap: 2,
                padding: '4px 12px',
                borderRadius: 4,
                background: '#1A237E',
                border: '1px solid #3F51B5'
              }}>
                <span style={{
                  fontSize: '11px',
                  color: '#999',
                  fontFamily: '"Courier New", monospace',
                  textTransform: 'uppercase',
                  letterSpacing: '0.5px'
                }}>
                  虚拟时间
                </span>
                <span style={{
                  fontSize: '14px',
                  fontWeight: 700,
                  color: '#FFFFFF',
                  fontFamily: '"Courier New", monospace',
                  letterSpacing: '1px'
                }}>
                  {now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })}
                </span>
                <span style={{
                  fontSize: '10px',
                  color: '#999',
                  fontFamily: '"Courier New", monospace'
                }}>
                  {now.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}
                </span>
              </div>

              {/* Fast Forward Button (only in Mock mode) */}
              <button
                onClick={() => {
                  if (clientRef.current) {
                    const success = clientRef.current.send({
                      type: 'fast_forward_time',
                      minutes: 30
                    });
                    if (!success) {
                      console.error('Failed to send fast forward request');
                    }
                  }
                }}
                style={{
                  padding: '6px 12px',
                  borderRadius: 4,
                  background: '#3F51B5',
                  border: '1px solid #5C6BC0',
                  color: '#FFFFFF',
                  fontSize: '12px',
                  fontFamily: '"Courier New", monospace',
                  fontWeight: 600,
                  cursor: 'pointer',
                  transition: 'all 0.2s',
                  display: 'flex',
                  alignItems: 'center',
                  gap: 4,
                  textTransform: 'uppercase',
                  letterSpacing: '0.5px'
                }}
                onMouseEnter={(e) => {
                  e.target.style.background = '#5C6BC0';
                  e.target.style.borderColor = '#7986CB';
                }}
                onMouseLeave={(e) => {
                  e.target.style.background = '#3F51B5';
                  e.target.style.borderColor = '#5C6BC0';
                }}
                title="快进30分钟 (Mock模式)"
              >
                ⏩ +30min
              </button>
            </div>
          )}

          {/* Unified Status Indicator */}
          <div className="header-status-inline">
            <span className={`status-dot ${isConnected ? (isUpdating ? 'updating' : 'live') : 'offline'}`} />
            <span className={`status-text ${isConnected ? 'live' : 'offline'}`}>
              {isConnected ? (isUpdating ? '同步中' : '在线') : '离线'}
            </span>
            {marketStatus && (
              <>
                <span className="status-sep">·</span>
                <span className={`market-text ${serverMode === 'backtest' ? 'backtest' : (marketStatus.status === 'open' ? 'open' : 'closed')}`}>
                  {marketStatusLabel}
                </span>
              </>
            )}
            {livePriceSourceLabel && (
              <>
                <span className="status-sep">·</span>
                <span className="market-text backtest">
                  {livePriceSourceLabel}
                </span>
              </>
            )}
            {historicalPriceSourceLabel && (
              <>
                <span className="status-sep">·</span>
                <span className="market-text backtest">
                  {historicalPriceSourceLabel}
                </span>
              </>
            )}
            {runtimeSummaryLabel && (
              <>
                <span className="status-sep">·</span>
                <span className="market-text backtest" title="当前运行配置">
                  {runtimeSummaryLabel}
                </span>
              </>
            )}
            <span className="status-sep">·</span>
            <span className="time-text">{now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false })}</span>
          </div>

          {serverMode !== 'backtest' && (
            <button
              onClick={handleManualTrigger}
              disabled={!isConnected}
              style={{
                padding: '6px 12px',
                borderRadius: 4,
                background: isConnected ? '#111111' : '#8a8a8a',
                border: '1px solid #111111',
                color: '#FFFFFF',
                fontSize: '11px',
                fontFamily: '"Courier New", monospace',
                fontWeight: 700,
                cursor: isConnected ? 'pointer' : 'not-allowed',
                letterSpacing: '0.4px',
                textTransform: 'uppercase'
              }}
              title="手动触发一轮分析与交易决策"
            >
              手动运行
            </button>
          )}

          <WatchlistPanel
            isOpen={isWatchlistPanelOpen}
            isConnected={isConnected}
            isSaving={isWatchlistSaving}
            draftSymbols={watchlistDraftSymbols}
            inputValue={watchlistInputValue}
            feedback={watchlistFeedback}
            suggestions={watchlistSuggestions}
            onToggle={handleWatchlistPanelToggle}
            onClose={() => setIsWatchlistPanelOpen(false)}
            onInputChange={handleWatchlistInputChange}
            onInputKeyDown={handleWatchlistInputKeyDown}
            onAdd={() => commitWatchlistInput(watchlistInputValue)}
            onRemove={handleWatchlistRemove}
            onRestoreCurrent={handleWatchlistRestoreCurrent}
            onRestoreDefault={handleWatchlistRestoreDefault}
            onSuggestionClick={handleWatchlistSuggestionClick}
            onSave={handleWatchlistSave}
          />

          <RuntimeSettingsPanel
            isOpen={isRuntimeSettingsOpen}
            isConnected={isConnected}
            isSaving={isRuntimeConfigSaving}
            feedback={runtimeConfigFeedback}
            runtimeConfig={runtimeConfig}
            scheduleMode={scheduleModeDraft}
            intervalMinutes={intervalMinutesDraft}
            triggerTime={triggerTimeDraft}
            maxCommCycles={maxCommCyclesDraft}
            onToggle={handleRuntimeSettingsToggle}
            onClose={() => setIsRuntimeSettingsOpen(false)}
            onScheduleModeChange={setScheduleModeDraft}
            onIntervalMinutesChange={setIntervalMinutesDraft}
            onTriggerTimeChange={setTriggerTimeDraft}
            onMaxCommCyclesChange={setMaxCommCyclesDraft}
            onSave={handleRuntimeConfigSave}
            onRestoreDefaults={handleRuntimeDefaultsRestore}
          />
        </div>
      </div>

      {/* Main Content */}
      <>
        {/* Ticker Bar */}
        <div className="ticker-bar">
          <div className="ticker-track">
            {[0, 1].map((groupIdx) => (
              <div key={groupIdx} className="ticker-group">
                {displayTickers.map(ticker => (
                  <div key={`${ticker.symbol}-${groupIdx}`} className="ticker-item">
                    <StockLogo ticker={ticker.symbol} size={16} />
                    <span className="ticker-symbol">{ticker.symbol}</span>
                    <span className="ticker-price">
                      <span className={`ticker-price-value ${rollingTickers[ticker.symbol] ? 'rolling' : ''}`}>
                        {ticker.price !== null && ticker.price !== undefined
                          ? `$${formatTickerPrice(ticker.price)}`
                          : '-'}
                      </span>
                    </span>
                    <span className={`ticker-change ${
                      ticker.change === null || ticker.change === undefined
                        ? ''
                        : ticker.change >= 0 ? 'positive' : 'negative'
                    }`}>
                      {ticker.change !== null && ticker.change !== undefined
                        ? `${ticker.change >= 0 ? '+' : ''}${ticker.change.toFixed(2)}%`
                        : '-'}
                    </span>
                  </div>
                ))}
              </div>
            ))}
          </div>
          <div className="portfolio-value">
            <span className="portfolio-label">投资组合</span>
            <span className="portfolio-amount">${formatNumber(portfolioData.netValue)}</span>
          </div>
        </div>

        <div className="main-container" ref={containerRef}>
          {/* Left Panel: Three-View Toggle (Room/Chart/Statistics) */}
          <div className="left-panel" style={{ width: `${leftWidth}%` }}>
            <div className="chart-section">
              <div className="view-container">
                <div className="view-nav-bar">
                  <button
                    className={`view-nav-btn ${currentView === 'traders' ? 'active' : ''}`}
                    onClick={() => setCurrentView('traders')}
                  >
                    交易员
                  </button>

                  <button
                    className={`view-nav-btn ${currentView === 'room' ? 'active' : ''}`}
                    onClick={() => setCurrentView('room')}
                  >
                    交易室
                  </button>

                  <button
                    className={`view-nav-btn ${currentView === 'explain' ? 'active' : ''}`}
                    onClick={() => setCurrentView('explain')}
                  >
                    个股分析
                  </button>

                  <button
                    className={`view-nav-btn ${currentView === 'chart' ? 'active' : ''}`}
                    onClick={() => setCurrentView('chart')}
                  >
                    业绩图表
                  </button>

                  <button
                    className={`view-nav-btn ${currentView === 'statistics' ? 'active' : ''}`}
                    onClick={() => setCurrentView('statistics')}
                  >
                    统计
                  </button>
                </div>

                <div className={`view-slider-five ${
                  currentView === 'traders'
                    ? 'show-traders'
                    : currentView === 'room'
                    ? 'show-room'
                    : currentView === 'explain'
                      ? 'show-explain'
                      : currentView === 'statistics'
                        ? 'show-statistics'
                        : 'show-chart'
                } ${!isInitialAnimating ? 'normal-speed' : ''}`}>
                  <div className="view-panel">
                    <Suspense fallback={<ViewLoadingFallback label="加载交易员视图..." />}>
                      <TraderView
                        agents={AGENTS}
                        agentProfilesByAgent={agentProfilesByAgent}
                        agentSkillsByAgent={agentSkillsByAgent}
                        workspaceFilesByAgent={workspaceFilesByAgent}
                        selectedAgentId={selectedSkillAgentId}
                        selectedAgentProfile={selectedAgentProfile}
                        selectedAgentSkills={selectedAgentSkills}
                        skillDetailsByName={skillDetailsByName}
                        localSkillDraftsByKey={localSkillDraftsByKey}
                        skillDetailLoadingKey={skillDetailLoadingKey}
                        editableFiles={EDITABLE_AGENT_WORKSPACE_FILES}
                        selectedWorkspaceFile={selectedWorkspaceFile}
                        workspaceFileContent={selectedWorkspaceContent}
                        workspaceDraftContent={workspaceDraftContent}
                        isConnected={isConnected}
                        isAgentSkillsLoading={isAgentSkillsLoading}
                        agentSkillsSavingKey={agentSkillsSavingKey}
                        agentSkillsFeedback={agentSkillsFeedback}
                        isWorkspaceFileLoading={isWorkspaceFileLoading}
                        workspaceFileSavingKey={workspaceFileSavingKey}
                        workspaceFileFeedback={workspaceFileFeedback}
                        onAgentChange={handleSkillAgentChange}
                        onCreateLocalSkill={handleCreateLocalSkill}
                        onSkillDetailRequest={requestSkillDetail}
                        onLocalSkillDraftChange={handleLocalSkillDraftChange}
                        onLocalSkillDelete={handleLocalSkillDelete}
                        onLocalSkillSave={handleLocalSkillSave}
                        onRemoveSharedSkill={handleRemoveSharedSkill}
                        onSkillToggle={handleAgentSkillToggle}
                        onWorkspaceFileChange={handleWorkspaceFileChange}
                        onWorkspaceDraftChange={setWorkspaceDraftContent}
                        onWorkspaceFileSave={handleWorkspaceFileSave}
                      />
                    </Suspense>
                  </div>

                  {/* Room View Panel */}
                  <div className="view-panel">
                    <Suspense fallback={<ViewLoadingFallback label="加载交易室..." />}>
                      <RoomView
                        bubbles={bubbles}
                        bubbleFor={bubbleFor}
                        leaderboard={leaderboard}
                        feed={feed}
                        onJumpToMessage={handleJumpToMessage}
                      />
                    </Suspense>
                  </div>

                  {/* Stock Explain View Panel */}
                  <div className="view-panel">
                    <Suspense fallback={<ViewLoadingFallback label="加载个股分析..." />}>
                      <StockExplainView
                        tickers={displayTickers}
                        holdings={holdings}
                        trades={trades}
                        leaderboard={leaderboard}
                        feed={feed}
                        priceHistoryByTicker={priceHistoryByTicker}
                        ohlcHistoryByTicker={ohlcHistoryByTicker}
                        selectedSymbol={selectedExplainSymbol}
                        onSelectedSymbolChange={setSelectedExplainSymbol}
                        selectedHistorySource={historySourceByTicker[selectedExplainSymbol] || null}
                        explainEventsSnapshot={explainEventsByTicker[selectedExplainSymbol] || null}
                        newsSnapshot={newsByTicker[selectedExplainSymbol] || null}
                        onRequestRangeExplain={requestStockRangeExplain}
                        onRequestNewsForDate={requestStockNewsForDate}
                        onRequestStory={requestStockStory}
                        currentDate={currentDate}
                        onRequestSimilarDays={requestStockSimilarDays}
                        onRequestStockEnrich={requestStockEnrich}
                      />
                    </Suspense>
                  </div>

                  {/* Chart View Panel */}
                  <div className="view-panel">
                    <div className="chart-container">
                      {/* Floating Timeframe Tabs */}
                      <div className="chart-tabs-floating">
                        <button
                          className={`chart-tab ${chartTab === 'all' ? 'active' : ''}`}
                          onClick={() => setChartTab('all')}
                        >
                          日线
                        </button>
                        {/* <button
                          className={`chart-tab ${chartTab === 'live' ? 'active' : ''} ${!isLiveEnabled ? 'disabled' : ''}`}
                          onClick={() => isLiveEnabled && setChartTab('live')}
                          disabled={!isLiveEnabled}
                          title={!isLiveEnabled ? 'Live chart available during market hours only' : ''}
                        >
                          LIVE
                        </button> */}
                      </div>

                      <NetValueChart
                        equity={portfolioData.equity}
                        baseline={portfolioData.baseline}
                        baseline_vw={portfolioData.baseline_vw}
                        momentum={portfolioData.momentum}
                        strategies={portfolioData.strategies}
                        equity_return={portfolioData.equity_return}
                        baseline_return={portfolioData.baseline_return}
                        baseline_vw_return={portfolioData.baseline_vw_return}
                        momentum_return={portfolioData.momentum_return}
                        chartTab={chartTab}
                        virtualTime={virtualTime}
                      />
                    </div>
                  </div>

                  {/* Statistics View Panel */}
                  <div className="view-panel">
                    <Suspense fallback={<ViewLoadingFallback label="加载统计视图..." />}>
                      <StatisticsView
                        trades={trades}
                        holdings={holdings}
                        stats={stats}
                        baseline_vw={portfolioData.baseline_vw}
                        equity={portfolioData.equity}
                        leaderboard={leaderboard}
                      />
                    </Suspense>
                  </div>
                </div>
              </div>
            </div>
          </div>

          {/* Resizer */}
          <div
            className={`resizer ${isResizing ? 'resizing' : ''}`}
            onMouseDown={handleMouseDown}
          />

          {/* Right Panel: Agent Feed */}
          <div className="right-panel" style={{ width: `${100 - leftWidth}%` }}>
            <Suspense fallback={<ViewLoadingFallback label="加载消息流..." />}>
              <AgentFeed ref={agentFeedRef} feed={feed} leaderboard={leaderboard} />
            </Suspense>
          </div>
        </div>
      </>
    </div>
  );
}
