import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import {
  Box,
  Paper,
  Typography,
  Grid,
  Button,
  Table,
  TableBody,
  TableCell,
  TableContainer,
  TableHead,
  TableRow,
  CircularProgress,
  Alert,
  Chip,
  LinearProgress,
  TextField,
  IconButton,
  Collapse,
  List,
  ListItemButton,
  ListItemIcon,
  ListItemText,
  Divider,
  FormControlLabel,
  FormControl,
  FormGroup,
  InputLabel,
  Select,
  MenuItem,
  Checkbox,
  Tooltip,
  Tabs,
  Tab,
} from '@mui/material';
import {
  CloudDownload,
  PlayArrow,
  Stop,
  Refresh,
  Folder,
  FolderOpen,
  ExpandMore,
  ExpandLess,
  CheckCircle,
  Error as ErrorIcon,
  InsertDriveFile,
  Storage,
  Schedule,
  Info,
  ArrowUpward,
  Home,
  NavigateNext,
  Language,
  Close,
} from '@mui/icons-material';
import {
  dataCollectionApi,
  DataCollectionStatus,
  DataUpdateHistory,
  type FolderInfo,
  DataCollectionSummary,
  type UpbitImportProgress,
  type UpbitByDateBatchPreview,
} from '../services/dataCollectionApi';

/** crix-data / 백엔드 TIME_UNITS과 동일 */
const CRIX_TIME_UNITS: { value: string; label: string }[] = [
  { value: '1m', label: '1m' },
  { value: '3m', label: '3m' },
  { value: '5m', label: '5m' },
  { value: '15m', label: '15m' },
  { value: '30m', label: '30m' },
  { value: '60m', label: '60m' },
  { value: '240m', label: '240m' },
  { value: '1d', label: '1d' },
  { value: '1w', label: '1w' },
  { value: '1M', label: '1M (월봉)' },
];

const DataCollectionPage: React.FC = () => {
  const [status, setStatus] = useState<DataCollectionStatus | null>(null);
  const [history, setHistory] = useState<DataUpdateHistory[]>([]);
  const [summary, setSummary] = useState<DataCollectionSummary | null>(null);
  const [folderStructure, setFolderStructure] = useState<FolderInfo | null>(null);
  const [currentPath, setCurrentPath] = useState<string>('/data');
  const [selectedFolder, setSelectedFolder] = useState<string>('/data/upbit_candle_data');
  const [reprocessAll, setReprocessAll] = useState<boolean>(false);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set(['/data']));
  const [pathInput, setPathInput] = useState<string>('');
  const [showPathInput, setShowPathInput] = useState<boolean>(false);
  const [leftTab, setLeftTab] = useState<'local' | 'upbit' | 'upbitDate'>('local');
  const [dateBatchYear, setDateBatchYear] = useState(new Date().getFullYear());
  const [dateBatchMonth, setDateBatchMonth] = useState(new Date().getMonth() + 1);
  /** false: 특정 일만, true: 해당 월 전체(가능한 일) */
  const [dateBatchWholeMonth, setDateBatchWholeMonth] = useState(false);
  const [dateBatchDay, setDateBatchDay] = useState(() => {
    const t = new Date();
    return Math.max(1, t.getDate() - 1);
  });
  const [dateBatchTfs, setDateBatchTfs] = useState<string[]>(['1m', '3m', '5m']);
  const [datePreview, setDatePreview] = useState<UpbitByDateBatchPreview | null>(null);
  const [datePreviewLoading, setDatePreviewLoading] = useState(false);
  // 업비트 crix-data 가상 탐색
  const [upbitRoot, setUpbitRoot] = useState<FolderInfo | null>(null);
  const [upbitFolderCache, setUpbitFolderCache] = useState<Record<string, FolderInfo[]>>({});
  const [upbitExpanded, setUpbitExpanded] = useState<Set<string>>(new Set(['upbit-site']));
  const [selectedUpbitPath, setSelectedUpbitPath] = useState<string>('upbit-site');
  const [upbitLoading, setUpbitLoading] = useState(false);
  const [upbitImporting, setUpbitImporting] = useState(false);
  const [upbitImportJob, setUpbitImportJob] = useState<UpbitImportProgress | null>(null);
  const upbitPollRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const [infoAlert, setInfoAlert] = useState<string | null>(null);

  // 폴더 구조만 빠르게 로드 (화면 진입 시) — 좌측 "로컬" 탭 전용
  const loadFolderStructure = useCallback(async (path?: string) => {
    try {
      setError(null);
      setLoading(true);
      const targetPath = path || currentPath;
      const folderData = await dataCollectionApi.getFolderStructure(targetPath);
      setFolderStructure(folderData);
    } catch (err: any) {
      console.error('폴더 구조 로드 실패:', err);
      setError('폴더 구조를 불러오는데 실패했습니다.');
    } finally {
      setLoading(false);
    }
  }, [currentPath]);

  const loadUpbitBrowse = useCallback(async (path?: string) => {
    try {
      setError(null);
      setUpbitLoading(true);
      const p = path || 'upbit-site';
      const node = await dataCollectionApi.browseUpbitArchive(p);
      if (p === 'upbit-site' || p === undefined) {
        setUpbitRoot(node);
        setUpbitFolderCache((prev) => ({ ...prev, [node.path]: node.children || [] }));
      } else {
        setUpbitFolderCache((prev) => ({ ...prev, [p]: node.children || [] }));
      }
    } catch (e: any) {
      console.error('업비트 경로 조회 실패', e);
      setError('업비트 경로를 불러오는데 실패했습니다. (' + (e?.message || '') + ')');
    } finally {
      setUpbitLoading(false);
    }
  }, []);

  // 상위 폴더로 이동
  const goToParentFolder = () => {
    const parentPath = currentPath.split('/').slice(0, -1).join('/') || '/';
    setCurrentPath(parentPath);
    setExpandedFolders(new Set([parentPath]));
  };

  // 특정 경로로 이동
  const navigateToPath = (path: string) => {
    setCurrentPath(path);
    setExpandedFolders(new Set([path]));
  };

  // 입력한 경로로 직접 이동
  const handlePathInputSubmit = () => {
    if (pathInput.trim()) {
      const normalizedPath = pathInput.trim().replace(/\\/g, '/');
      setCurrentPath(normalizedPath);
      setExpandedFolders(new Set([normalizedPath]));
      setShowPathInput(false);
      loadFolderStructure(normalizedPath);
    }
  };

  // 경로 입력 시 Enter 키 처리
  const handlePathInputKeyPress = (e: React.KeyboardEvent) => {
    if (e.key === 'Enter') {
      handlePathInputSubmit();
    } else if (e.key === 'Escape') {
      setShowPathInput(false);
      setPathInput(currentPath);
    }
  };

  // Windows 드라이브, macOS 또는 루트 경로 옵션
  const getQuickPaths = () => {
    // 현재 경로에서 OS 판단 (Windows: C:, D: 등)
    const isWindows = currentPath.includes(':') || folderStructure?.path?.includes(':');
    // macOS 경로 판단 (/host/Users 또는 /host/Volumes로 시작)
    const isMacOS = currentPath.startsWith('/host/Users') || currentPath.startsWith('/host/Volumes');
    
    if (isWindows) {
      return [
        { label: 'C:', path: 'C:/' },
        { label: 'D:', path: 'D:/' },
        { label: 'E:', path: 'E:/' },
      ];
    } else {
      // macOS + Linux/Docker 공통 경로
      return [
        { label: '/', path: '/' },
        { label: '/data', path: '/data' },
        { label: '/home', path: '/home' },
        // macOS 경로 (Docker 볼륨 마운트)
        { label: '🍎 Users', path: '/host/Users' },
        { label: '🍎 Volumes', path: '/host/Volumes' },
      ];
    }
  };

  // 현재 경로를 breadcrumb으로 분리
  const getPathParts = () => {
    // Windows 경로 지원 (C:/, D:/ 등)
    const normalizedPath = currentPath.replace(/\\/g, '/');
    
    // Windows 드라이브 처리 (C:/ 형태)
    if (normalizedPath.match(/^[A-Za-z]:\//)) {
      const drive = normalizedPath.substring(0, 2); // C: 또는 D:
      const rest = normalizedPath.substring(3).split('/').filter(p => p);
      
      const parts = [{ name: drive, path: drive + '/' }];
      rest.forEach((part, index) => {
        parts.push({
          name: part,
          path: drive + '/' + rest.slice(0, index + 1).join('/'),
        });
      });
      return parts;
    }
    
    // Unix 경로
    const parts = normalizedPath.split('/').filter(p => p);
    return parts.map((part, index) => ({
      name: part,
      path: '/' + parts.slice(0, index + 1).join('/'),
    }));
  };

  // 히스토리 및 요약 데이터 로드 (수집 완료 후)
  const loadHistoryAndSummary = useCallback(async () => {
    try {
      const [historyData, summaryData] = await Promise.all([
        dataCollectionApi.getHistory(20),
        dataCollectionApi.getSummary(),
      ]);
      setHistory(historyData);
      setSummary(summaryData);
    } catch (err: any) {
      console.error('히스토리 로드 실패:', err);
    }
  }, []);

  // 화면 진입 시 현재 진행 중인 수집 상태 확인
  const checkCurrentStatus = useCallback(async () => {
    try {
      const currentStatus = await dataCollectionApi.getStatus();
      // 진행 중이거나 데이터가 있으면 상태 설정
      if (currentStatus.isRunning || currentStatus.processedFiles > 0) {
        setStatus(currentStatus);
      }
    } catch (err) {
      console.error('상태 조회 실패:', err);
    }
  }, []);

  // 화면 진입 시: 로컬 탭이면 로컬 폴더 + 진행 상태
  useEffect(() => {
    if (leftTab === 'local') {
      loadFolderStructure(currentPath);
    }
    checkCurrentStatus();
  }, [currentPath, leftTab, loadFolderStructure, checkCurrentStatus]);

  // 업비트 탭 전환 시 아카이브 루트(마켓 목록) 1회 로드
  useEffect(() => {
    if (leftTab === 'upbit' && !upbitRoot) {
      loadUpbitBrowse('upbit-site');
    }
  }, [leftTab, upbitRoot, loadUpbitBrowse]);

  // 업비트(날짜별) — 예정 zip 수 미리보기
  useEffect(() => {
    if (leftTab !== 'upbitDate' || dateBatchTfs.length === 0) {
      return;
    }
    const t = setTimeout(() => {
      (async () => {
        setDatePreviewLoading(true);
        try {
          const p = await dataCollectionApi.previewUpbitByDate({
            year: dateBatchYear,
            month: dateBatchMonth,
            timeUnits: dateBatchTfs,
            ...(!dateBatchWholeMonth ? { day: dateBatchDay } : {}),
          });
          setDatePreview(p);
        } catch (e) {
          console.error('날짜·봉 미리보기 실패', e);
          setDatePreview(null);
        } finally {
          setDatePreviewLoading(false);
        }
      })();
    }, 450);
    return () => clearTimeout(t);
  }, [leftTab, dateBatchYear, dateBatchMonth, dateBatchDay, dateBatchWholeMonth, dateBatchTfs]);

  // 처리 중인 경우 자동 새로고침
  useEffect(() => {
    let interval: NodeJS.Timeout | null = null;
    if (status?.isRunning) {
      interval = setInterval(async () => {
        try {
          const statusData = await dataCollectionApi.getStatus();
          setStatus(statusData);
          // 완료되면 히스토리/요약 로드 및 폴더 구조 새로고침
          if (!statusData.isRunning) {
            loadFolderStructure();
            loadHistoryAndSummary();
          }
        } catch (err) {
          console.error('상태 새로고침 실패:', err);
        }
      }, 1000);
    }
    return () => {
      if (interval) clearInterval(interval);
    };
  }, [status?.isRunning, loadFolderStructure, loadHistoryAndSummary]);

  const handleStartCollection = async () => {
    try {
      setError(null);
      const result = await dataCollectionApi.startCollection({
        targetPath: selectedFolder,
        processAll: reprocessAll,
      });
      setStatus(result);
    } catch (err: any) {
      setError('데이터 수집 시작 실패: ' + err.message);
    }
  };

  const handleStopCollection = async () => {
    try {
      const result = await dataCollectionApi.stopCollection();
      setStatus(result);
    } catch (err: any) {
      setError('데이터 수집 중단 실패: ' + err.message);
    }
  };

  // 폴더 내용 캐시 (lazy loading용)
  const [folderCache, setFolderCache] = useState<Record<string, FolderInfo[]>>({});
  const [loadingFolders, setLoadingFolders] = useState<Set<string>>(new Set());
  const [upbitLoadingFolders, setUpbitLoadingFolders] = useState<Set<string>>(new Set());
  const [selectedUpbit, setSelectedUpbit] = useState<FolderInfo | null>(null);

  const toggleFolder = async (path: string, e: React.MouseEvent) => {
    e.stopPropagation();
    
    const isCurrentlyExpanded = expandedFolders.has(path);
    
    if (!isCurrentlyExpanded && !folderCache[path]) {
      // 폴더를 처음 확장할 때 하위 폴더 로드
      setLoadingFolders(prev => new Set(prev).add(path));
      try {
        const subFolder = await dataCollectionApi.getFolderStructure(path);
        setFolderCache(prev => ({
          ...prev,
          [path]: subFolder.children || []
        }));
      } catch (err) {
        console.error('하위 폴더 로드 실패:', err);
      } finally {
        setLoadingFolders(prev => {
          const next = new Set(prev);
          next.delete(path);
          return next;
        });
      }
    }
    
    setExpandedFolders(prev => {
      const next = new Set(prev);
      if (next.has(path)) {
        next.delete(path);
      } else {
        next.add(path);
      }
      return next;
    });
  };

  const handleSelectFolder = (path: string) => {
    setSelectedFolder(path);
  };

  const handleSelectUpbit = (f: FolderInfo) => {
    setSelectedUpbitPath(f.path);
    setSelectedUpbit(f);
  };

  const toggleUpbitFolder = async (path: string, e: React.MouseEvent) => {
    e.stopPropagation();
    const isCurrentlyExpanded = upbitExpanded.has(path);
    if (!isCurrentlyExpanded && upbitFolderCache[path] == null) {
      setUpbitLoadingFolders((prev) => new Set(prev).add(path));
      try {
        const node = await dataCollectionApi.browseUpbitArchive(path);
        setUpbitFolderCache((prev) => ({
          ...prev,
          [path]: node.children || [],
        }));
      } catch (err) {
        console.error('업비트 하위 경로 실패', err);
      } finally {
        setUpbitLoadingFolders((prev) => {
          const n = new Set(prev);
          n.delete(path);
          return n;
        });
      }
    }
    setUpbitExpanded((prev) => {
      const next = new Set(prev);
      if (next.has(path)) {
        next.delete(path);
      } else {
        next.add(path);
      }
      return next;
    });
  };

  const renderFolderTree = (folder: FolderInfo, level: number = 0) => {
    const isExpanded = expandedFolders.has(folder.path);
    const isLoading = loadingFolders.has(folder.path);
    // hasChildren이 true이거나 children이 있는 경우
    const hasChildren = folder.hasChildren || (folder.children && folder.children.length > 0);
    // 캐시된 하위 폴더 또는 원래 children 사용
    const childrenToRender = folderCache[folder.path] || folder.children || [];
    const isSelected = selectedFolder === folder.path;

    return (
      <Box key={folder.path}>
        <ListItemButton
          onClick={() => handleSelectFolder(folder.path)}
          selected={isSelected}
          sx={{
            pl: level * 2 + 1,
            borderRadius: 1,
            mb: 0.5,
            bgcolor: isSelected ? 'primary.dark' : 'transparent',
            '&.Mui-selected': {
              bgcolor: 'primary.dark',
              '&:hover': {
                bgcolor: 'primary.main',
              }
            }
          }}
        >
          {hasChildren && (
            <IconButton
              size="small"
              onClick={(e) => toggleFolder(folder.path, e)}
              sx={{ mr: 0.5, p: 0.25 }}
              disabled={isLoading}
            >
              {isLoading ? (
                <CircularProgress size={16} />
              ) : isExpanded ? (
                <ExpandLess fontSize="small" />
              ) : (
                <ExpandMore fontSize="small" />
              )}
            </IconButton>
          )}
          {!hasChildren && <Box sx={{ width: 28 }} />}
          
          <ListItemIcon sx={{ minWidth: 36 }}>
            {isExpanded ? (
              <FolderOpen sx={{ color: isSelected ? 'primary.contrastText' : 'primary.main' }} />
            ) : (
              <Folder sx={{ color: isSelected ? 'primary.contrastText' : 'action.active' }} />
            )}
          </ListItemIcon>
          
          <ListItemText 
            primary={folder.name}
            primaryTypographyProps={{
              variant: 'body2',
              fontWeight: isSelected ? 600 : 400,
            }}
          />
          
          {(folder.fileCount > 0 || folder.pendingCount > 0) && (
            <Tooltip title={`총 ${folder.fileCount}개 / 미처리 ${folder.pendingCount}개`}>
              <Box sx={{ display: 'flex', gap: 0.5 }}>
                {folder.pendingCount > 0 ? (
                  <Chip
                    label={`${folder.pendingCount}개 대기`}
                    size="small"
                    color="warning"
                    sx={{ height: 20, '& .MuiChip-label': { px: 0.5, fontSize: '0.7rem' } }}
                  />
                ) : folder.fileCount > 0 ? (
                  <Chip
                    icon={<CheckCircle sx={{ fontSize: 12 }} />}
                    label="완료"
                    size="small"
                    color="success"
                    variant="outlined"
                    sx={{ height: 20, '& .MuiChip-label': { px: 0.5, fontSize: '0.7rem' } }}
                  />
                ) : null}
              </Box>
            </Tooltip>
          )}
        </ListItemButton>
        
        <Collapse in={isExpanded}>
          {childrenToRender.map((child) => renderFolderTree(child, level + 1))}
        </Collapse>
      </Box>
    );
  };

  const renderUpbitFolderTree = (folder: FolderInfo, level: number = 0) => {
    const isFile = folder.type === 'FILE' || folder.name.endsWith('.zip');
    const isExpanded = upbitExpanded.has(folder.path);
    const isLoading = upbitLoadingFolders.has(folder.path);
    const hasChildren =
      !isFile && (folder.hasChildren || (folder.children && folder.children.length > 0));
    const childrenToRender = upbitFolderCache[folder.path] || folder.children || [];
    const isSelected = selectedUpbitPath === folder.path;

    return (
      <Box key={folder.path}>
        <ListItemButton
          onClick={() => handleSelectUpbit(folder)}
          selected={isSelected}
          sx={{
            pl: level * 2 + 1,
            borderRadius: 1,
            mb: 0.5,
            bgcolor: isSelected ? 'primary.dark' : 'transparent',
            '&.Mui-selected': {
              bgcolor: 'primary.dark',
              '&:hover': { bgcolor: 'primary.main' },
            },
          }}
        >
          {!isFile && hasChildren && (
            <IconButton
              size="small"
              onClick={(e) => toggleUpbitFolder(folder.path, e)}
              sx={{ mr: 0.5, p: 0.25 }}
              disabled={isLoading || (status?.isRunning ?? false)}
            >
              {isLoading ? (
                <CircularProgress size={16} />
              ) : isExpanded ? (
                <ExpandLess fontSize="small" />
              ) : (
                <ExpandMore fontSize="small" />
              )}
            </IconButton>
          )}
          {(!hasChildren || isFile) && <Box sx={{ width: 28 }} />}

          <ListItemIcon sx={{ minWidth: 36 }}>
            {isFile ? (
              <InsertDriveFile sx={{ color: isSelected ? 'primary.contrastText' : 'action.active' }} fontSize="small" />
            ) : isExpanded ? (
              <FolderOpen sx={{ color: isSelected ? 'primary.contrastText' : 'primary.main' }} />
            ) : (
              <Folder sx={{ color: isSelected ? 'primary.contrastText' : 'action.active' }} />
            )}
          </ListItemIcon>

          <ListItemText
            primary={folder.name}
            primaryTypographyProps={{ variant: 'body2', fontWeight: isSelected ? 600 : 400 }}
          />

          {isFile && (folder.fileCount > 0 || (folder.processedCount ?? 0) > 0) && (
            <Tooltip title="DB 히스토리 기준">
              <Box sx={{ display: 'flex', gap: 0.5 }}>
                {(folder.pendingCount ?? 0) > 0 ? (
                  <Chip
                    label="미적재"
                    size="small"
                    color="warning"
                    sx={{ height: 20, '& .MuiChip-label': { px: 0.5, fontSize: '0.7rem' } }}
                  />
                ) : (
                  <Chip
                    icon={<CheckCircle sx={{ fontSize: 12 }} />}
                    label="적재됨"
                    size="small"
                    color="success"
                    variant="outlined"
                    sx={{ height: 20, '& .MuiChip-label': { px: 0.5, fontSize: '0.7rem' } }}
                  />
                )}
              </Box>
            </Tooltip>
          )}
        </ListItemButton>

        {!isFile && (
          <Collapse in={isExpanded}>
            {childrenToRender.map((child) => renderUpbitFolderTree(child, level + 1))}
          </Collapse>
        )}
      </Box>
    );
  };

  // 선택된 폴더 정보 찾기 (캐시 포함)
  const findFolderInfo = (folder: FolderInfo | null, path: string): FolderInfo | null => {
    if (!folder) return null;
    if (folder.path === path) return folder;
    
    // 원래 children에서 검색
    if (folder.children) {
      for (const child of folder.children) {
        const found = findFolderInfo(child, path);
        if (found) return found;
      }
    }
    
    // 캐시된 폴더에서 검색
    const cachedChildren = folderCache[folder.path];
    if (cachedChildren) {
      for (const child of cachedChildren) {
        if (child.path === path) return child;
        const found = findFolderInfo(child, path);
        if (found) return found;
      }
    }
    
    return null;
  };

  const selectedFolderInfo = findFolderInfo(folderStructure, selectedFolder);

  const yearOptions = useMemo(() => {
    const y0 = 2017;
    const y1 = new Date().getFullYear() + 1;
    return Array.from({ length: y1 - y0 + 1 }, (_, i) => y0 + i);
  }, []);

  const daysInMonth = useMemo(
    () => new Date(dateBatchYear, dateBatchMonth, 0).getDate(),
    [dateBatchYear, dateBatchMonth]
  );

  // 연·월 바뀌면 일자를 해당 월 말일로 맞춤
  useEffect(() => {
    setDateBatchDay((d) => Math.min(Math.max(1, d), daysInMonth));
  }, [dateBatchYear, dateBatchMonth, daysInMonth]);

  // 예상 완료 시간 계산
  const formatEstimatedTime = (estimatedEndTime: string | null | undefined) => {
    if (!estimatedEndTime) return '-';
    const end = new Date(estimatedEndTime);
    const now = new Date();
    const diffMs = end.getTime() - now.getTime();
    if (diffMs <= 0) return '곧 완료';
    const diffSec = Math.floor(diffMs / 1000);
    const min = Math.floor(diffSec / 60);
    const sec = diffSec % 60;
    if (min > 0) return `약 ${min}분 ${sec}초`;
    return `약 ${sec}초`;
  };

  const clearUpbitPoll = useCallback(() => {
    if (upbitPollRef.current != null) {
      clearInterval(upbitPollRef.current);
      upbitPollRef.current = null;
    }
  }, []);

  useEffect(() => () => clearUpbitPoll(), [clearUpbitPoll]);

  const toggleDateBatchTf = (v: string) => {
    setDateBatchTfs((prev) => {
      if (prev.includes(v)) {
        return prev.filter((x) => x !== v);
      }
      const next = [...prev, v];
      const order = CRIX_TIME_UNITS.map((x) => x.value);
      return next.sort((a, b) => order.indexOf(a) - order.indexOf(b));
    });
  };

  const handleUpbitDateBatch = async () => {
    if (dateBatchTfs.length === 0) {
      return;
    }
    setError(null);
    setInfoAlert(null);
    setUpbitImporting(true);
    clearUpbitPoll();
    try {
      const { jobId } = await dataCollectionApi.startUpbitImportByDate({
        year: dateBatchYear,
        month: dateBatchMonth,
        timeUnits: dateBatchTfs,
        reprocess: reprocessAll,
        ...(!dateBatchWholeMonth ? { day: dateBatchDay } : {}),
      });
      setUpbitImportJob({
        jobId,
        phase: 'RUNNING',
        percent: 0,
        message: '전 마켓 zip 순차 다운로드·DB 적재 준비 중…',
        done: false,
      });
      upbitPollRef.current = setInterval(async () => {
        try {
          const p = await dataCollectionApi.getUpbitImportProgress(jobId);
          setUpbitImportJob({
            jobId: p.jobId,
            phase: p.phase,
            percent: p.percent,
            message: p.message,
            done: p.done,
            result: p.result,
            totalPlanned: p.totalPlanned,
            currentIndex: p.currentIndex,
          });
          if (p.done) {
            clearUpbitPoll();
            setUpbitImporting(false);
            if (p.result?.success) {
              setError(null);
              setInfoAlert(
                p.result.message
                  ? `${p.result.message} (총 캔들·row ${(p.result.recordCount ?? 0).toLocaleString()})`
                  : '배치 완료'
              );
              await loadHistoryAndSummary();
            } else {
              setError(p.result?.message || p.message || '배치 적재에 실패했습니다.');
            }
          }
        } catch (e: any) {
          clearUpbitPoll();
          setUpbitImporting(false);
          setError('진행 조회 실패: ' + (e?.message || String(e)));
          setUpbitImportJob((j) => (j ? { ...j, done: true, message: e?.message || '오류' } : j));
        }
      }, 400);
    } catch (e: any) {
      clearUpbitPoll();
      setUpbitImporting(false);
      setError('배치 시작 실패: ' + (e?.message || String(e)));
      setUpbitImportJob(null);
    }
  };

  const handleUpbitImport = async () => {
    if (!selectedUpbit || !selectedUpbit.path.endsWith('.zip')) {
      return;
    }
    const importPath = selectedUpbit.path;
    setError(null);
    setInfoAlert(null);
    setUpbitImporting(true);
    clearUpbitPoll();
    try {
      const { jobId } = await dataCollectionApi.startUpbitImport(importPath, reprocessAll);
      setUpbitImportJob({
        jobId,
        phase: 'RUNNING',
        percent: 0,
        message: '서버에서 zip 다운로드 및 DB 저장을 수행 중…',
        done: false,
      });
      upbitPollRef.current = setInterval(async () => {
        try {
          const p = await dataCollectionApi.getUpbitImportProgress(jobId);
          setUpbitImportJob({
            jobId: p.jobId,
            phase: p.phase,
            percent: p.percent,
            message: p.message,
            done: p.done,
            result: p.result,
            totalPlanned: p.totalPlanned,
            currentIndex: p.currentIndex,
          });
          if (p.done) {
            clearUpbitPoll();
            setUpbitImporting(false);
            if (p.result?.success) {
              setError(null);
              setInfoAlert(
                p.result.message
                  ? `${p.result.message} (${p.result.recordCount}건)`
                  : `처리됨 (${p.result.recordCount}건)`
              );
              await loadHistoryAndSummary();
              const parent = importPath.split('/').slice(0, -1).join('/');
              const node = await dataCollectionApi.browseUpbitArchive(parent);
              setUpbitFolderCache((prev) => ({ ...prev, [parent]: node.children || [] }));
              setSelectedUpbit((s) => (s && s.path === importPath
                ? { ...s, processedCount: 1, pendingCount: 0, type: 'FILE' as const }
                : s));
            } else {
              setError(p.result?.message || p.message || '업비트 적재에 실패했습니다.');
            }
          }
        } catch (e: any) {
          clearUpbitPoll();
          setUpbitImporting(false);
          setError('진행 조회 실패: ' + (e?.message || String(e)));
          setUpbitImportJob((j) => (j ? { ...j, done: true, message: e?.message || '오류' } : j));
        }
      }, 400);
    } catch (e: any) {
      clearUpbitPoll();
      setUpbitImporting(false);
      setError('작업 시작 실패: ' + (e?.message || String(e)));
      setUpbitImportJob(null);
    }
  };

  return (
    <Box className="fade-in">
      <Typography variant="h4" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
        <CloudDownload color="primary" />
        데이터 수집
      </Typography>
      <Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
        로컬 ZIP을 스캔하거나, 업비트 crix-data에서 <strong>종목별</strong>로 zip을 고르거나, <strong>날짜·봉(다중)</strong>으로
        전 KRW 마켓을 한 번에 순차 적재할 수 있습니다.
      </Typography>

      {infoAlert && (
        <Alert severity="success" sx={{ mb: 2 }} onClose={() => setInfoAlert(null)}>
          {infoAlert}
        </Alert>
      )}
      {error && <Alert severity="error" sx={{ mb: 3 }}>{error}</Alert>}

      <Grid container spacing={2}>
        {/* 좌측: 폴더 트리 */}
        <Grid item xs={12} md={5} lg={4}>
          <Paper 
            elevation={0} 
            sx={{ 
              p: 2, 
              bgcolor: 'background.paper', 
              border: '1px solid rgba(100, 149, 237, 0.2)',
              height: 'calc(100vh - 250px)',
              minHeight: 500,
              display: 'flex',
              flexDirection: 'column',
            }}
          >
            <Tabs
              value={leftTab}
              onChange={(_, v) => setLeftTab(v as 'local' | 'upbit' | 'upbitDate')}
              variant="scrollable"
              scrollButtons="auto"
              sx={{ borderBottom: 1, borderColor: 'divider', mb: 1, minHeight: 42 }}
            >
              <Tab value="local" label="로컬" />
              <Tab
                value="upbit"
                label="업비트(종목별)"
                icon={<Language fontSize="small" />}
                iconPosition="start"
              />
              <Tab
                value="upbitDate"
                label="업비트(날짜별)"
                icon={<Schedule fontSize="small" />}
                iconPosition="start"
              />
            </Tabs>

            {leftTab === 'local' && (
            <Box sx={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0 }}>
            <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
              <Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
                <Storage fontSize="small" />
                로컬 폴더
              </Typography>
              <Box>
                <Tooltip title="상위 폴더">
                  <span>
                    <IconButton 
                      size="small" 
                      onClick={goToParentFolder} 
                      disabled={currentPath === '/' || status?.isRunning}
                    >
                      <ArrowUpward />
                    </IconButton>
                  </span>
                </Tooltip>
                <Tooltip title="Docker 컨테이너 (/data)">
                  <IconButton 
                    size="small" 
                    onClick={() => navigateToPath('/data')} 
                    disabled={status?.isRunning}
                  >
                    <Home />
                  </IconButton>
                </Tooltip>
                <Tooltip title="새로고침">
                  <IconButton size="small" onClick={() => loadFolderStructure(currentPath)} disabled={status?.isRunning}>
                    <Refresh />
                  </IconButton>
                </Tooltip>
              </Box>
            </Box>

            {/* 빠른 경로 선택 버튼 */}
            <Box sx={{ display: 'flex', gap: 0.5, mb: 1, flexWrap: 'wrap' }}>
              <Tooltip title="Docker 컨테이너 기본 데이터 경로">
                <Chip
                  label="📦 /data"
                  size="small"
                  color={currentPath.startsWith('/data') ? 'primary' : 'default'}
                  variant={currentPath.startsWith('/data') ? 'filled' : 'outlined'}
                  onClick={() => {
                    navigateToPath('/data');
                    loadFolderStructure('/data');
                  }}
                  disabled={status?.isRunning}
                  sx={{ cursor: 'pointer' }}
                />
              </Tooltip>
              <Tooltip title="Windows 로컬 D: 드라이브 (Docker에서 /host/d로 마운트됨)">
                <Chip
                  label="💻 로컬 D:"
                  size="small"
                  color={currentPath.startsWith('/host/d') ? 'secondary' : 'default'}
                  variant={currentPath.startsWith('/host/d') ? 'filled' : 'outlined'}
                  onClick={() => {
                    navigateToPath('/host/d');
                    loadFolderStructure('/host/d');
                  }}
                  disabled={status?.isRunning}
                  sx={{ cursor: 'pointer' }}
                />
              </Tooltip>
              <Tooltip title="Windows 로컬 C: 드라이브 (Docker에서 /host/c로 마운트됨)">
                <Chip
                  label="💻 로컬 C:"
                  size="small"
                  color={currentPath.startsWith('/host/c') ? 'secondary' : 'default'}
                  variant={currentPath.startsWith('/host/c') ? 'filled' : 'outlined'}
                  onClick={() => {
                    navigateToPath('/host/c');
                    loadFolderStructure('/host/c');
                  }}
                  disabled={status?.isRunning}
                  sx={{ cursor: 'pointer' }}
                />
              </Tooltip>
              <Tooltip title="경로 직접 입력">
                <Chip
                  label="✏️ 경로 입력"
                  size="small"
                  variant="outlined"
                  onClick={() => {
                    setPathInput(currentPath);
                    setShowPathInput(!showPathInput);
                  }}
                  disabled={status?.isRunning}
                  sx={{ cursor: 'pointer' }}
                />
              </Tooltip>
            </Box>
            
            {/* 안내 메시지 */}
            <Alert severity="info" sx={{ mb: 1, py: 0.5 }} icon={<Info fontSize="small" />}>
              <Typography variant="caption">
                로컬 PC 경로: D:\dev → /host/d/dev | Docker 경로: /data
              </Typography>
            </Alert>

            {/* 경로 직접 입력 필드 */}
            {showPathInput && (
              <Box sx={{ mb: 1 }}>
                <TextField
                  fullWidth
                  size="small"
                  placeholder="경로 입력 (예: /host/d/dev/upbit/upbit_candle_data)"
                  value={pathInput}
                  onChange={(e) => setPathInput(e.target.value)}
                  onKeyDown={handlePathInputKeyPress}
                  autoFocus
                  InputProps={{
                    endAdornment: (
                      <Button 
                        size="small" 
                        variant="contained" 
                        onClick={handlePathInputSubmit}
                        sx={{ ml: 1 }}
                      >
                        이동
                      </Button>
                    ),
                  }}
                  helperText="로컬 PC: /host/d/... (D:), /host/c/... (C:) | Docker: /data/..."
                />
              </Box>
            )}

            {/* Breadcrumb 경로 표시 */}
            <Box 
              sx={{ 
                display: 'flex', 
                alignItems: 'center', 
                flexWrap: 'wrap',
                gap: 0.5,
                mb: 1,
                p: 1,
                bgcolor: 'rgba(100, 149, 237, 0.05)',
                borderRadius: 1,
                fontSize: '0.85rem',
              }}
            >
              <Chip
                label="/"
                size="small"
                variant={currentPath === '/' ? 'filled' : 'outlined'}
                onClick={() => navigateToPath('/')}
                sx={{ cursor: 'pointer', height: 24 }}
              />
              {getPathParts().map((part, index) => (
                <React.Fragment key={part.path}>
                  <NavigateNext fontSize="small" sx={{ color: 'text.secondary' }} />
                  <Chip
                    label={part.name}
                    size="small"
                    variant={currentPath === part.path ? 'filled' : 'outlined'}
                    color={currentPath === part.path ? 'primary' : 'default'}
                    onClick={() => navigateToPath(part.path)}
                    sx={{ cursor: 'pointer', height: 24 }}
                  />
                </React.Fragment>
              ))}
            </Box>
            
            <Divider sx={{ mb: 1 }} />
            
            <Box sx={{ flex: 1, overflow: 'auto' }}>
              {loading ? (
                <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
                  <CircularProgress size={32} />
                </Box>
              ) : folderStructure ? (
                <List dense disablePadding>
                  {renderFolderTree(folderStructure)}
                </List>
              ) : (
                <Typography color="text.secondary" sx={{ p: 2, textAlign: 'center' }}>
                  폴더 정보를 불러올 수 없습니다.
                </Typography>
              )}
            </Box>
            </Box>
            )}

            {leftTab === 'upbit' && (
            <Box sx={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, pt: 0.5 }}>
              <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}>
                <Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', gap: 1, fontSize: '1rem' }}>
                  <Language fontSize="small" />
                  업비트 캔들(종목별)
                </Typography>
                <Tooltip title="다시 로드 (마켓 목록)">
                  <span>
                    <IconButton
                      size="small"
                      onClick={() => {
                        setUpbitRoot(null);
                        setUpbitFolderCache({});
                        setUpbitExpanded(new Set(['upbit-site']));
                        loadUpbitBrowse('upbit-site');
                      }}
                      disabled={upbitLoading}
                    >
                      <Refresh />
                    </IconButton>
                  </span>
                </Tooltip>
              </Box>
              <Alert severity="info" sx={{ mb: 1, py: 0.5 }} icon={<Info fontSize="small" />}>
                <Typography variant="caption" component="span">
                  <strong>crix-data.upbit.com</strong> 과 동일한 구조입니다. 마켓 → 봉(1d,1m,…) → 연 → 월 → 일별 zip. zip을
                  선택한 뒤 오른쪽에서 &quot;선택한 zip DB 적재&quot;를 누릅니다.
                </Typography>
              </Alert>
              <Box sx={{ flex: 1, overflow: 'auto' }}>
                {upbitLoading && !upbitRoot ? (
                  <Box sx={{ display: 'flex', justifyContent: 'center', p: 4 }}>
                    <CircularProgress size={32} />
                  </Box>
                ) : upbitRoot ? (
                  <List dense disablePadding>
                    {renderUpbitFolderTree(upbitRoot)}
                  </List>
                ) : (
                  <Typography color="text.secondary" sx={{ p: 2, textAlign: 'center' }}>
                    목록을 불러올 수 없습니다. 새로고침 해 보세요.
                  </Typography>
                )}
              </Box>
            </Box>
            )}

            {leftTab === 'upbitDate' && (
            <Box sx={{ display: 'flex', flexDirection: 'column', flex: 1, minHeight: 0, pt: 0.5, overflow: 'auto' }}>
              <Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', gap: 1, fontSize: '1rem', mb: 1 }}>
                <Schedule fontSize="small" />
                연·월·일·봉(다중) — 전 KRW 마켓
              </Typography>
              <Alert severity="info" sx={{ mb: 1.5, py: 0.5 }} icon={<Info fontSize="small" />}>
                <Typography variant="caption" component="div">
                  <strong>특정 하루</strong>만 받거나 <strong>이 달 전체</strong>를 선택할 수 있습니다. 체크한{' '}
                  <strong>시간봉</strong>마다 <strong>모든 KRW 마켓</strong> 일별 zip을 crix-data에서 <strong>순차</strong>{' '}
                  다운로드·DB 적재합니다. (T+1: 당일·미래는 zip 0건일 수 있음)
                </Typography>
              </Alert>
              <FormControlLabel
                control={
                  <Checkbox
                    size="small"
                    checked={dateBatchWholeMonth}
                    onChange={(_, c) => setDateBatchWholeMonth(c)}
                    disabled={upbitImporting}
                  />
                }
                label={<Typography variant="body2">이 달 전체(가능한 모든 일)</Typography>}
                sx={{ mb: 1 }}
              />
              <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 1.5, alignItems: 'center' }}>
                <FormControl size="small" sx={{ minWidth: 100 }}>
                  <InputLabel>연도</InputLabel>
                  <Select
                    label="연도"
                    value={dateBatchYear}
                    onChange={(e) => setDateBatchYear(Number(e.target.value))}
                  >
                    {yearOptions.map((y) => (
                      <MenuItem key={y} value={y}>
                        {y}년
                      </MenuItem>
                    ))}
                  </Select>
                </FormControl>
                <FormControl size="small" sx={{ minWidth: 88 }}>
                  <InputLabel>월</InputLabel>
                  <Select
                    label="월"
                    value={dateBatchMonth}
                    onChange={(e) => setDateBatchMonth(Number(e.target.value))}
                  >
                    {Array.from({ length: 12 }, (_, i) => i + 1).map((m) => (
                      <MenuItem key={m} value={m}>
                        {m}월
                      </MenuItem>
                    ))}
                  </Select>
                </FormControl>
                {!dateBatchWholeMonth && (
                  <FormControl size="small" sx={{ minWidth: 80 }}>
                    <InputLabel>일</InputLabel>
                    <Select
                      label="일"
                      value={dateBatchDay}
                      onChange={(e) => setDateBatchDay(Number(e.target.value))}
                    >
                      {Array.from({ length: daysInMonth }, (_, i) => i + 1).map((d) => (
                        <MenuItem key={d} value={d}>
                          {d}일
                        </MenuItem>
                      ))}
                    </Select>
                  </FormControl>
                )}
              </Box>
              <Typography variant="caption" color="text.secondary" sx={{ mb: 0.5, display: 'block' }}>
                시간봉(복수 선택)
              </Typography>
              <FormGroup
                row
                sx={{ gap: 0.5, mb: 1, flexWrap: 'wrap' }}
              >
                {CRIX_TIME_UNITS.map((tf) => (
                  <FormControlLabel
                    key={tf.value}
                    control={
                      <Checkbox
                        size="small"
                        checked={dateBatchTfs.includes(tf.value)}
                        onChange={() => toggleDateBatchTf(tf.value)}
                        disabled={upbitImporting}
                      />
                    }
                    label={<Typography variant="body2">{tf.label}</Typography>}
                  />
                ))}
              </FormGroup>
              {datePreviewLoading && (
                <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, py: 1 }}>
                  <CircularProgress size={18} />
                  <Typography variant="caption" color="text.secondary">
                    예정 건수 계산 중…
                  </Typography>
                </Box>
              )}
              {!datePreviewLoading && datePreview && (
                <Alert severity="info" sx={{ py: 0.5 }}>
                  <Typography variant="body2">
                    예정: <strong>{datePreview.totalZips.toLocaleString()}</strong>개 zip (KRW 마켓{' '}
                    {datePreview.marketCount}개)
                  </Typography>
                  {datePreview.message && (
                    <Typography variant="caption" color="text.secondary" display="block" sx={{ mt: 0.5 }}>
                      {datePreview.message}
                    </Typography>
                  )}
                </Alert>
              )}
            </Box>
            )}
          </Paper>
        </Grid>

        {/* 우측: 설정 및 진행 상태 */}
        <Grid item xs={12} md={7} lg={8}>
          {/* 선택된 폴더 정보 */}
          <Paper 
            elevation={0} 
            sx={{ 
              p: 3, 
              mb: 2, 
              bgcolor: 'background.paper', 
              border: '1px solid rgba(100, 149, 237, 0.2)' 
            }}
          >
            <Typography variant="h6" gutterBottom>선택 항목</Typography>
            <Box sx={{ 
              p: 2, 
              bgcolor: 'action.hover', 
              borderRadius: 1, 
              fontFamily: 'monospace',
              fontSize: '0.9rem',
              mb: 2,
              wordBreak: 'break-all'
            }}>
              {leftTab === 'local'
                ? selectedFolder
                : leftTab === 'upbit'
                  ? selectedUpbitPath
                  : `${dateBatchYear}-${String(dateBatchMonth).padStart(2, '0')}-${
                      dateBatchWholeMonth ? '(월전체)' : String(dateBatchDay).padStart(2, '0')
                    } · 봉: ${dateBatchTfs.join(', ') || '(미선택)'}`}
            </Box>
            {leftTab === 'upbit' && selectedUpbit?.downloadUrl && (
              <Typography variant="caption" color="text.secondary" sx={{ display: 'block', mb: 2, wordBreak: 'break-all' }}>
                URL: {selectedUpbit.downloadUrl}
              </Typography>
            )}

            {leftTab === 'local' && selectedFolderInfo && (
              <Grid container spacing={2}>
                <Grid item xs={4}>
                  <Typography variant="body2" color="text.secondary">총 파일</Typography>
                  <Typography variant="h5" color="primary">{selectedFolderInfo.fileCount}</Typography>
                </Grid>
                <Grid item xs={4}>
                  <Typography variant="body2" color="text.secondary">처리 완료</Typography>
                  <Typography variant="h5" color="success.main">{selectedFolderInfo.processedCount}</Typography>
                </Grid>
                <Grid item xs={4}>
                  <Typography variant="body2" color="text.secondary">대기 중</Typography>
                  <Typography variant="h5" color="warning.main">{selectedFolderInfo.pendingCount}</Typography>
                </Grid>
              </Grid>
            )}

            {leftTab === 'upbitDate' && datePreview && !datePreviewLoading && (
              <Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
                예정 zip {datePreview.totalZips.toLocaleString()}개 · KRW 마켓 {datePreview.marketCount}개
              </Typography>
            )}

            {leftTab === 'upbit' && selectedUpbit && (
              <Grid container spacing={2} sx={{ mb: 1 }}>
                <Grid item xs={6}>
                  <Typography variant="body2" color="text.secondary">유형</Typography>
                  <Typography>{selectedUpbit.type === 'FILE' ? 'ZIP 파일' : '폴더'}</Typography>
                </Grid>
                {selectedUpbit.type === 'FILE' && (
                  <Grid item xs={6}>
                    <Typography variant="body2" color="text.secondary">DB 기록</Typography>
                    <Typography>
                      {(selectedUpbit.processedCount ?? 0) > 0 || (selectedUpbit.pendingCount ?? 0) === 0
                        ? '이미 적재됨(히스토리 있음) 가능'
                        : '미적재'}
                    </Typography>
                  </Grid>
                )}
              </Grid>
            )}
          </Paper>

          {/* 수집 설정 */}
          <Paper 
            elevation={0} 
            sx={{ 
              p: 3, 
              mb: 2, 
              bgcolor: 'background.paper', 
              border: '1px solid rgba(100, 149, 237, 0.2)' 
            }}
          >
            <Typography variant="h6" gutterBottom>수집 설정</Typography>
            <Alert severity="info" sx={{ mb: 2 }}>
              종목별로 순차 처리되어 메모리 문제 없이 대용량 데이터를 안전하게 처리합니다.
              이미 저장된 데이터는 자동으로 건너뜁니다.
            </Alert>
            <Grid container spacing={2} alignItems="center">
              <Grid item xs={12} sm={6}>
                <Tooltip title="이미 처리된 파일도 다시 처리합니다. 기존 데이터는 업데이트됩니다.">
                  <FormControlLabel
                    control={
                      <Checkbox
                        checked={reprocessAll}
                        onChange={(e) => setReprocessAll(e.target.checked)}
                        disabled={!!status?.isRunning || upbitImporting}
                      />
                    }
                    label={
                      <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
                        <span>재처리 모드 (UPSERT) - 기존 데이터도 업데이트</span>
                        <Info fontSize="small" color="action" />
                      </Box>
                    }
                  />
                </Tooltip>
              </Grid>
              <Grid item xs={12} sm={6}>
                <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
                  {leftTab === 'local' && (
                    status?.isRunning ? (
                    <Button
                      variant="contained"
                      color="error"
                      startIcon={<Stop />}
                      onClick={handleStopCollection}
                      fullWidth
                      size="large"
                    >
                      중단
                    </Button>
                    ) : (
                    <Button
                      variant="contained"
                      color="primary"
                      startIcon={<PlayArrow />}
                      onClick={handleStartCollection}
                      fullWidth
                      size="large"
                      disabled={!selectedFolder}
                    >
                      수집 시작 (하위폴더 포함)
                    </Button>
                    )
                  )}
                  {leftTab === 'upbit' && (
                    <Button
                      variant="contained"
                      color="primary"
                      startIcon={<CloudDownload />}
                      onClick={handleUpbitImport}
                      fullWidth
                      size="large"
                      disabled={
                        !selectedUpbit
                        || selectedUpbit.type !== 'FILE'
                        || !selectedUpbit.path.endsWith('.zip')
                        || upbitImporting
                        || (status?.isRunning ?? false)
                      }
                    >
                      {upbitImporting ? '다운로드·적재 중…' : '선택한 zip을 DB에 저장 (다운로드)'}
                    </Button>
                  )}
                  {leftTab === 'upbitDate' && (
                    <Button
                      variant="contained"
                      color="primary"
                      startIcon={<CloudDownload />}
                      onClick={handleUpbitDateBatch}
                      fullWidth
                      size="large"
                      disabled={
                        dateBatchTfs.length === 0
                        || upbitImporting
                        || (status?.isRunning ?? false)
                      }
                    >
                      {upbitImporting
                        ? '전 마켓 순차 다운로드·적재 중…'
                        : '선택한 연·월·봉으로 DB 적재 시작'}
                    </Button>
                  )}
                </Box>
              </Grid>
            </Grid>
            {leftTab === 'upbit' && (
              <Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
                로컬의 &quot;수집&quot;은 비활성화됩니다. 좌측에서 일별 zip을 선택하세요(전날 T+1까지).
              </Typography>
            )}
            {leftTab === 'upbitDate' && (
              <Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
                대량 작업이므로 서버·네트워크 부하에 따라 오래 걸릴 수 있습니다. 하단 진행률로 zip 단위로 확인하세요.
              </Typography>
            )}
          </Paper>

          {/* 처리 진행 상태 */}
          {status?.isRunning && (
            <Paper 
              elevation={0} 
              sx={{ 
                p: 3, 
                mb: 2, 
                bgcolor: 'rgba(100, 149, 237, 0.1)', 
                border: '1px solid rgba(100, 149, 237, 0.4)' 
              }}
            >
              <Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
                <CircularProgress size={20} />
                데이터 수집 진행 중... {status.currentMarket && `(${status.currentMarket})`}
              </Typography>
              
              {/* 종목 진행률 */}
              {status.totalMarkets > 0 && (
                <Box sx={{ mb: 2 }}>
                  <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
                    <Typography variant="body2" color="text.secondary">
                      종목 진행: {status.processedMarkets} / {status.totalMarkets}
                    </Typography>
                    <Typography variant="body2" fontWeight={600} color="primary">
                      {((status.processedMarkets / status.totalMarkets) * 100).toFixed(1)}%
                    </Typography>
                  </Box>
                  <LinearProgress 
                    variant="determinate" 
                    value={(status.processedMarkets / status.totalMarkets) * 100} 
                    sx={{ height: 8, borderRadius: 4 }}
                    color="primary"
                  />
                </Box>
              )}
              
              {/* 파일 진행률 */}
              <Box sx={{ mb: 3 }}>
                <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
                  <Typography variant="body2" color="text.secondary">
                    파일 진행: {status.processedFiles.toLocaleString()} / {status.totalFiles.toLocaleString()}
                  </Typography>
                  <Typography variant="body2" fontWeight={600}>
                    {status.progressPercent.toFixed(1)}%
                  </Typography>
                </Box>
                <LinearProgress 
                  variant="determinate" 
                  value={status.progressPercent} 
                  sx={{ height: 8, borderRadius: 4 }}
                  color="secondary"
                />
              </Box>
              
              <Grid container spacing={2} sx={{ mb: 2 }}>
                <Grid item xs={6} sm={3}>
                  <Typography variant="body2" color="text.secondary">성공</Typography>
                  <Typography variant="h6" color="success.main">{status.successCount}</Typography>
                </Grid>
                <Grid item xs={6} sm={3}>
                  <Typography variant="body2" color="text.secondary">실패</Typography>
                  <Typography variant="h6" color="error.main">{status.failCount}</Typography>
                </Grid>
                <Grid item xs={6} sm={3}>
                  <Typography variant="body2" color="text.secondary">건너뜀</Typography>
                  <Typography variant="h6">{status.skipCount}</Typography>
                </Grid>
                <Grid item xs={6} sm={3}>
                  <Typography variant="body2" color="text.secondary">총 레코드</Typography>
                  <Typography variant="h6">{status.totalRecords.toLocaleString()}</Typography>
                </Grid>
              </Grid>

              <Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
                <Schedule fontSize="small" color="action" />
                <Typography variant="body2" color="text.secondary">
                  예상 남은 시간: <strong>{formatEstimatedTime(status.estimatedEndTime)}</strong>
                </Typography>
              </Box>
              
              <Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
                현재 처리 중: <strong>{status.currentFile}</strong>
              </Typography>
              
              {status.recentLogs && status.recentLogs.length > 0 && (
                <Box sx={{ 
                  mt: 2, 
                  p: 2, 
                  bgcolor: 'rgba(0,0,0,0.3)', 
                  borderRadius: 1, 
                  maxHeight: 120, 
                  overflow: 'auto' 
                }}>
                  {status.recentLogs.slice(-5).map((log, i) => (
                    <Typography 
                      key={i} 
                      variant="body2" 
                      sx={{ 
                        fontFamily: 'monospace', 
                        fontSize: '0.75rem',
                        color: log.includes('성공') ? 'success.light' : 
                               log.includes('실패') ? 'error.light' : 'text.secondary'
                      }}
                    >
                      {log}
                    </Typography>
                  ))}
                </Box>
              )}
            </Paper>
          )}

          {/* 업비트 crix zip — 다운로드·DB 적재 진행률 */}
          {upbitImportJob && (
            <Paper
              elevation={0}
              sx={{
                p: 3,
                mb: 2,
                bgcolor: 'rgba(20, 40, 60, 0.35)',
                border: '1px solid rgba(100, 149, 237, 0.45)',
              }}
            >
              <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 0.5 }}>
                <Typography variant="h6" sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
                  {upbitImporting ? <CircularProgress size={20} /> : <CheckCircle color="action" fontSize="small" />}
                  업비트 zip → DB 적재
                </Typography>
                <Tooltip title="닫기">
                  <IconButton
                    size="small"
                    onClick={() => {
                      clearUpbitPoll();
                      setUpbitImportJob(null);
                      setUpbitImporting(false);
                    }}
                  >
                    <Close fontSize="small" />
                  </IconButton>
                </Tooltip>
              </Box>
              <Box sx={{ mb: 1 }}>
                <Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 0.5 }}>
                  <Typography variant="body2" color="text.secondary" sx={{ flex: 1, pr: 1 }}>
                    {upbitImportJob.message}
                  </Typography>
                  <Typography variant="body2" fontWeight={600}>
                    {upbitImportJob.percent}%
                  </Typography>
                </Box>
                <LinearProgress
                  variant="determinate"
                  value={Math.max(0, Math.min(100, upbitImportJob.percent))}
                  sx={{ height: 8, borderRadius: 1 }}
                />
              </Box>
              <Typography variant="caption" color="text.secondary" display="block">
                단계: {upbitImportJob.phase}
                {upbitImportJob.totalPlanned != null && upbitImportJob.totalPlanned > 0 && (
                  <span>
                    {' '}
                    — zip: {upbitImportJob.currentIndex ?? 0} / {upbitImportJob.totalPlanned}
                  </span>
                )}
                {upbitImportJob.done && upbitImportJob.result
                  ? ` — 레코드: ${(upbitImportJob.result.recordCount ?? 0).toLocaleString()}`
                  : ''}
                {upbitImportJob.done &&
                  upbitImportJob.result != null &&
                  (upbitImportJob.result.batchTotalZips != null && upbitImportJob.result.batchTotalZips > 0) && (
                    <span>
                      {' '}
                      (성공 zip {upbitImportJob.result.batchSuccessZips ?? 0} / 실패{' '}
                      {upbitImportJob.result.batchFailZips ?? 0} / 스킵 {upbitImportJob.result.batchSkipZips ?? 0})
                    </span>
                  )}
              </Typography>
            </Paper>
          )}

          {/* 수집 현황 요약 */}
          <Paper 
            elevation={0} 
            sx={{ 
              p: 3, 
              mb: 2, 
              bgcolor: 'background.paper', 
              border: '1px solid rgba(100, 149, 237, 0.2)' 
            }}
          >
            <Typography variant="h6" gutterBottom>전체 수집 현황</Typography>
            {summary ? (
              <Grid container spacing={2}>
                <Grid item xs={6} sm={3}>
                  <Typography variant="body2" color="text.secondary">총 처리 파일</Typography>
                  <Typography variant="h5">{summary.totalProcessedFiles.toLocaleString()}</Typography>
                </Grid>
                <Grid item xs={6} sm={3}>
                  <Typography variant="body2" color="text.secondary">총 레코드</Typography>
                  <Typography variant="h5">{summary.totalRecords.toLocaleString()}</Typography>
                </Grid>
                <Grid item xs={6} sm={3}>
                  <Typography variant="body2" color="text.secondary">성공</Typography>
                  <Typography variant="h5" color="success.main">{summary.successCount}</Typography>
                </Grid>
                <Grid item xs={6} sm={3}>
                  <Typography variant="body2" color="text.secondary">실패</Typography>
                  <Typography variant="h5" color="error.main">{summary.failCount}</Typography>
                </Grid>
              </Grid>
            ) : (
              <Typography color="text.secondary">수집된 데이터가 없습니다.</Typography>
            )}
          </Paper>

          {/* 처리 히스토리 */}
          <Paper 
            elevation={0} 
            sx={{ 
              p: 3, 
              bgcolor: 'background.paper', 
              border: '1px solid rgba(100, 149, 237, 0.2)' 
            }}
          >
            <Typography variant="h6" gutterBottom>최근 처리 히스토리</Typography>
            <TableContainer sx={{ maxHeight: 300 }}>
              <Table stickyHeader size="small">
                <TableHead>
                  <TableRow>
                    <TableCell width={40}>상태</TableCell>
                    <TableCell>마켓</TableCell>
                    <TableCell>시간</TableCell>
                    <TableCell>날짜</TableCell>
                    <TableCell align="right">레코드</TableCell>
                  </TableRow>
                </TableHead>
                <TableBody>
                  {history.length > 0 ? (
                    history.slice(0, 20).map((item) => (
                      <TableRow key={item.id} hover>
                        <TableCell>
                          {item.status === 'SUCCESS' ? (
                            <CheckCircle color="success" fontSize="small" />
                          ) : (
                            <ErrorIcon color="error" fontSize="small" />
                          )}
                        </TableCell>
                        <TableCell>{item.market}</TableCell>
                        <TableCell>{item.timeUnit}</TableCell>
                        <TableCell>{item.dataDate}</TableCell>
                        <TableCell align="right">{item.recordCount.toLocaleString()}</TableCell>
                      </TableRow>
                    ))
                  ) : (
                    <TableRow>
                      <TableCell colSpan={5} align="center" sx={{ py: 4 }}>
                        <Typography color="text.secondary">처리 히스토리가 없습니다.</Typography>
                      </TableCell>
                    </TableRow>
                  )}
                </TableBody>
              </Table>
            </TableContainer>
          </Paper>
        </Grid>
      </Grid>
    </Box>
  );
};

export default DataCollectionPage;
